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}