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 */
019package org.crsh.console;
020
021import org.crsh.cli.impl.line.LineParser;
022import org.crsh.cli.impl.line.MultiLineVisitor;
023import org.crsh.text.Style;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import 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 */
038class 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}