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.term.console; 021 022 import org.crsh.term.CodeType; 023 import org.crsh.term.Term; 024 import org.crsh.term.TermEvent; 025 import org.crsh.term.spi.TermIO; 026 import org.crsh.text.CLS; 027 import org.crsh.text.Chunk; 028 import org.crsh.text.Style; 029 import org.crsh.text.Text; 030 031 import java.io.IOException; 032 import java.util.LinkedList; 033 import java.util.logging.Level; 034 import java.util.logging.Logger; 035 036 /** 037 * Implements the {@link Term interface}. 038 */ 039 public 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 provide(Chunk element) throws IOException { 240 if (element == null) { 241 throw new NullPointerException("No null chunk accepted"); 242 } 243 if (element instanceof Text) { 244 Text textChunk = (Text)element; 245 writer.write(textChunk.getText()); 246 } else if (element instanceof Style) { 247 io.write(((Style)element)); 248 } else if (element instanceof CLS) { 249 io.cls(); 250 } else { 251 throw new UnsupportedOperationException("todo"); 252 } 253 } 254 }