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
020package org.crsh.telnet.term.console;
021
022import org.crsh.telnet.term.CodeType;
023import org.crsh.telnet.term.Term;
024import org.crsh.telnet.term.TermEvent;
025import org.crsh.telnet.term.spi.TermIO;
026import org.crsh.text.Screenable;
027import org.crsh.text.Style;
028
029import java.io.IOException;
030import java.util.LinkedList;
031import java.util.logging.Level;
032import java.util.logging.Logger;
033
034/**
035 * Implements the {@link Term interface}.
036 */
037public class ConsoleTerm implements Term {
038
039  /** . */
040  private final Logger log = Logger.getLogger(ConsoleTerm.class.getName());
041
042  /** . */
043  private final LinkedList<CharSequence> history;
044
045  /** . */
046  private CharSequence historyBuffer;
047
048  /** . */
049  private int historyCursor;
050
051  /** . */
052  private final TermIO io;
053
054  /** . */
055  private final TermIOBuffer buffer;
056
057  /** . */
058  private final TermIOWriter writer;
059
060  public ConsoleTerm(final TermIO io) {
061    this.history = new LinkedList<CharSequence>();
062    this.historyBuffer = null;
063    this.historyCursor = -1;
064    this.io = io;
065    this.buffer = new TermIOBuffer(io);
066    this.writer = new TermIOWriter(io);
067  }
068
069  public int getWidth() {
070    return io.getWidth();
071  }
072
073  public int getHeight() {
074    return io.getHeight();
075  }
076
077  public String getProperty(String name) {
078    return io.getProperty(name);
079  }
080
081  public void setEcho(boolean echo) {
082    buffer.setEchoing(echo);
083  }
084
085  public boolean takeAlternateBuffer() throws IOException {
086    return io.takeAlternateBuffer();
087  }
088
089  public boolean releaseAlternateBuffer() throws IOException {
090    return io.releaseAlternateBuffer();
091  }
092
093  public TermEvent read() throws IOException {
094
095    //
096    while (true) {
097      int code = io.read();
098      CodeType type = io.decode(code);
099      switch (type) {
100        case CLOSE:
101          return TermEvent.close();
102        case BACKSPACE:
103          buffer.del();
104          break;
105        case UP:
106        case DOWN:
107          int nextHistoryCursor = historyCursor +  (type == CodeType.UP ? + 1 : -1);
108          if (nextHistoryCursor >= -1 && nextHistoryCursor < history.size()) {
109            CharSequence s = nextHistoryCursor == -1 ? historyBuffer : history.get(nextHistoryCursor);
110            while (buffer.moveRight()) {
111              // Do nothing
112            }
113            CharSequence t = buffer.replace(s);
114            if (historyCursor == -1) {
115              historyBuffer = t;
116            }
117            if (nextHistoryCursor == -1) {
118              historyBuffer = null;
119            }
120            historyCursor = nextHistoryCursor;
121          }
122          break;
123        case RIGHT:
124          buffer.moveRight();
125          break;
126        case LEFT:
127          buffer.moveLeft();
128          break;
129        case BREAK:
130          log.log(Level.FINE, "Want to cancel evaluation");
131          buffer.clear();
132          return TermEvent.brk();
133        case CHAR:
134          if (code >= 0 && code < 128) {
135            buffer.append((char)code);
136          } else {
137            log.log(Level.FINE, "Unhandled char " + code);
138          }
139          break;
140        case TAB:
141          log.log(Level.FINE, "Tab");
142          return TermEvent.complete(buffer.getBufferToCursor());
143        case BACKWARD_WORD: {
144          int cursor = buffer.getCursor();
145          int pos = cursor;
146          // Skip any white char first
147          while (pos > 0 && buffer.charAt(pos - 1) == ' ') {
148            pos--;
149          }
150          // Skip until next white char
151          while (pos > 0 && buffer.charAt(pos - 1) != ' ') {
152            pos--;
153          }
154          if (pos < cursor) {
155            buffer.moveLeft(cursor - pos);
156          }
157          break;
158        }
159        case FORWARD_WORD: {
160          int size = buffer.getSize();
161          int cursor = buffer.getCursor();
162          int pos = cursor;
163          // Skip any white char first
164          while (pos < size && buffer.charAt(pos) == ' ') {
165            pos++;
166          }
167          // Skip until next white char
168          while (pos < size && buffer.charAt(pos) != ' ') {
169            pos++;
170          }
171          if (pos > cursor) {
172            buffer.moveRight(pos - cursor);
173          }
174          break;
175        }
176        case BEGINNING_OF_LINE: {
177          int cursor = buffer.getCursor();
178          if (cursor > 0) {
179            buffer.moveLeft(cursor);
180          }
181          break;
182        }
183        case END_OF_LINE: {
184          int cursor = buffer.getSize() - buffer.getCursor();
185          if (cursor > 0) {
186            buffer.moveRight  (cursor);
187          }
188          break;
189        }
190      }
191
192      //
193      if (buffer.hasNext()) {
194        historyCursor = -1;
195        historyBuffer = null;
196        CharSequence input = buffer.next();
197        return TermEvent.readLine(input);
198      }
199    }
200  }
201
202  public Appendable getDirectBuffer() {
203    return buffer;
204  }
205
206  public void addToHistory(CharSequence line) {
207    history.addFirst(line);
208  }
209
210  public CharSequence getBuffer() {
211    return buffer.getBufferToCursor();
212  }
213
214  public void flush() {
215    try {
216      io.flush();
217    }
218    catch (IOException e) {
219      log.log(Level.FINE, "Exception thrown during term flush()", e);
220    }
221  }
222
223  public void close() {
224    try {
225      log.log(Level.FINE, "Closing connection");
226      io.flush();
227      io.close();
228    } catch (IOException e) {
229      log.log(Level.FINE, "Exception thrown during term close()", e);
230    }
231  }
232
233  @Override
234  public Screenable append(CharSequence s) throws IOException {
235    writer.write(s);
236    return this;
237  }
238
239  @Override
240  public Appendable append(char c) throws IOException {
241    writer.write(c);
242    return this;
243  }
244
245  @Override
246  public Appendable append(CharSequence csq, int start, int end) throws IOException {
247    writer.write(csq.subSequence(start, end));
248    return this;
249  }
250
251  @Override
252  public Screenable append(Style style) throws IOException {
253    io.write((style));
254    return this;
255  }
256
257  @Override
258  public Screenable cls() throws IOException {
259    io.cls();
260    return this;
261  }
262}