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