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    
020    package org.crsh.telnet.term.console;
021    
022    import org.crsh.telnet.term.CodeType;
023    import org.crsh.telnet.term.Term;
024    import org.crsh.telnet.term.TermEvent;
025    import org.crsh.telnet.term.spi.TermIO;
026    import org.crsh.text.Screenable;
027    import org.crsh.text.Style;
028    
029    import java.io.IOException;
030    import java.util.LinkedList;
031    import java.util.logging.Level;
032    import java.util.logging.Logger;
033    
034    /**
035     * Implements the {@link Term interface}.
036     */
037    public 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    }