001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    package org.crsh.console;
020    
021    import org.crsh.cli.impl.line.LineParser;
022    import org.crsh.cli.impl.line.MultiLineVisitor;
023    import org.crsh.text.Style;
024    
025    import java.io.IOException;
026    import java.util.ArrayList;
027    import java.util.LinkedList;
028    
029    /**
030     * An editor state machine.
031     *
032     * todo:
033     * - undo
034     * - optimize operation with an improvement of {@link org.crsh.console.EditorBuffer}
035     *
036     * @author Julien Viet
037     */
038    class Editor extends Plugin {
039    
040      /** . */
041      final Console console;
042    
043      /** . */
044      final EditorBuffer buffer;
045    
046      /** . */
047      final MultiLineVisitor visitor;
048    
049      /** The line parser : updated on enter key. */
050      final LineParser lineParser;
051    
052      /** . */
053      final LinkedList<String> history;
054    
055      /** . */
056      private Mode mode;
057    
058      /** . */
059      int historyCursor;
060    
061      /** . */
062      String historyBuffer;
063    
064      /** The buffer that holds what we kill. */
065      final StringBuilder killBuffer;
066    
067      /** . */
068      private final ArrayList<Runnable> modeListeners;
069    
070      Editor(Console console) {
071        this(console, true);
072      }
073    
074      Editor(Console console, boolean echo) {
075    
076    
077        //
078        EditorBuffer buffer = new EditorBuffer(echo ? console.driver : NULL);
079    
080        //
081        this.console = console;
082        this.buffer = buffer;
083        this.visitor = new MultiLineVisitor();
084        this.lineParser = new LineParser(visitor);
085        this.history = new LinkedList<String>();
086        this.historyCursor = -1;
087        this.historyBuffer = null;
088        this.killBuffer = new StringBuilder();
089        this.mode = Mode.EMACS;
090        this.modeListeners = new ArrayList<Runnable>();
091      }
092    
093      Mode getMode() {
094        return mode;
095      }
096    
097      void setMode(Mode mode) {
098        this.mode = mode;
099        for (Runnable listener : modeListeners) {
100          listener.run();
101        }
102      }
103    
104      void addModeListener(Runnable runnable) {
105        modeListeners.add(runnable);
106      }
107    
108      void addToHistory(String line) {
109        history.addFirst(line);
110      }
111    
112      /**
113       * Returns the right cursor bound depending on the current mode.
114       *
115       * @return the current bound
116       */
117      int getCursorBound() {
118        if (console.getMode() == Mode.EMACS) {
119          return buffer.getSize();
120        } else {
121          return Math.max(0, buffer.getSize() - 1);
122        }
123      }
124    
125      String getKillBuffer() {
126        return killBuffer.toString();
127      }
128    
129      void setKillBuffer(CharSequence s) {
130        if (s == null) {
131          throw new NullPointerException("No null buffer content");
132        }
133        killBuffer.setLength(0);
134        killBuffer.append(s);
135      }
136    
137      boolean isEmpty() {
138        return buffer.getSize() == 0 && buffer.getLines().size() == 1;
139      }
140    
141      String getCurrentLine() {
142        return buffer.getLine();
143      }
144    
145      int getCurrentPosition() {
146        return buffer.getCursor();
147      }
148    
149      String append(EditorAction action, int[] sequence) {
150        try {
151          return action.execute(this, buffer, sequence, true);
152        }
153        catch (IOException e) {
154          AssertionError ae = new AssertionError("Not yet supported");
155          ae.initCause(e);
156          throw ae;
157        }
158      }
159    
160      void reset() {
161        lineParser.reset();
162        buffer.reset();
163        historyCursor = -1;
164      }
165    
166      // Null impl for echo
167      private static final ConsoleDriver NULL = new ConsoleDriver() {
168        @Override public int getWidth() { return 80; }
169        @Override public int getHeight() { return 40; }
170        @Override public String getProperty(String name) { return null; }
171        @Override public boolean takeAlternateBuffer() throws IOException { return false; }
172        @Override public boolean releaseAlternateBuffer() throws IOException { return false; }
173        @Override public void flush() throws IOException { }
174        @Override public void write(CharSequence s) throws IOException { }
175        @Override public void write(CharSequence s, int start, int end) throws IOException { }
176        @Override public void write(char c) throws IOException { }
177        @Override public void write(Style d) throws IOException { }
178        @Override public void writeDel() throws IOException { }
179        @Override public void writeCRLF() throws IOException { }
180        @Override public void cls() throws IOException { }
181        @Override public boolean moveRight(char c) throws IOException { return true; }
182        @Override public boolean moveLeft() throws IOException { return true; }
183        @Override public void close() throws IOException { }
184      };
185    }