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 TermEvent read() throws IOException {
088
089 //
090 while (true) {
091 int code = io.read();
092 CodeType type = io.decode(code);
093 switch (type) {
094 case CLOSE:
095 return TermEvent.close();
096 case BACKSPACE:
097 buffer.del();
098 break;
099 case UP:
100 case DOWN:
101 int nextHistoryCursor = historyCursor + (type == CodeType.UP ? + 1 : -1);
102 if (nextHistoryCursor >= -1 && nextHistoryCursor < history.size()) {
103 CharSequence s = nextHistoryCursor == -1 ? historyBuffer : history.get(nextHistoryCursor);
104 while (buffer.moveRight()) {
105 // Do nothing
106 }
107 CharSequence t = buffer.replace(s);
108 if (historyCursor == -1) {
109 historyBuffer = t;
110 }
111 if (nextHistoryCursor == -1) {
112 historyBuffer = null;
113 }
114 historyCursor = nextHistoryCursor;
115 }
116 break;
117 case RIGHT:
118 buffer.moveRight();
119 break;
120 case LEFT:
121 buffer.moveLeft();
122 break;
123 case BREAK:
124 log.log(Level.FINE, "Want to cancel evaluation");
125 buffer.clear();
126 return TermEvent.brk();
127 case CHAR:
128 if (code >= 0 && code < 128) {
129 buffer.append((char)code);
130 } else {
131 log.log(Level.FINE, "Unhandled char " + code);
132 }
133 break;
134 case TAB:
135 log.log(Level.FINE, "Tab");
136 return TermEvent.complete(buffer.getBufferToCursor());
137 case BACKWARD_WORD: {
138 int cursor = buffer.getCursor();
139 int pos = cursor;
140 // Skip any white char first
141 while (pos > 0 && buffer.charAt(pos - 1) == ' ') {
142 pos--;
143 }
144 // Skip until next white char
145 while (pos > 0 && buffer.charAt(pos - 1) != ' ') {
146 pos--;
147 }
148 if (pos < cursor) {
149 buffer.moveLeft(cursor - pos);
150 }
151 break;
152 }
153 case FORWARD_WORD: {
154 int size = buffer.getSize();
155 int cursor = buffer.getCursor();
156 int pos = cursor;
157 // Skip any white char first
158 while (pos < size && buffer.charAt(pos) == ' ') {
159 pos++;
160 }
161 // Skip until next white char
162 while (pos < size && buffer.charAt(pos) != ' ') {
163 pos++;
164 }
165 if (pos > cursor) {
166 buffer.moveRight(pos - cursor);
167 }
168 break;
169 }
170 case BEGINNING_OF_LINE: {
171 int cursor = buffer.getCursor();
172 if (cursor > 0) {
173 buffer.moveLeft(cursor);
174 }
175 break;
176 }
177 case END_OF_LINE: {
178 int cursor = buffer.getSize() - buffer.getCursor();
179 if (cursor > 0) {
180 buffer.moveRight (cursor);
181 }
182 break;
183 }
184 }
185
186 //
187 if (buffer.hasNext()) {
188 historyCursor = -1;
189 historyBuffer = null;
190 CharSequence input = buffer.next();
191 return TermEvent.readLine(input);
192 }
193 }
194 }
195
196 public Appendable getDirectBuffer() {
197 return buffer;
198 }
199
200 public void addToHistory(CharSequence line) {
201 history.addFirst(line);
202 }
203
204 public CharSequence getBuffer() {
205 return buffer.getBufferToCursor();
206 }
207
208 public void flush() {
209 try {
210 io.flush();
211 }
212 catch (IOException e) {
213 log.log(Level.FINE, "Exception thrown during term flush()", e);
214 }
215 }
216
217 public void close() {
218 try {
219 log.log(Level.FINE, "Closing connection");
220 io.flush();
221 io.close();
222 } catch (IOException e) {
223 log.log(Level.FINE, "Exception thrown during term close()", e);
224 }
225 }
226
227 public void provide(Chunk element) throws IOException {
228 if (element == null) {
229 throw new NullPointerException("No null chunk accepted");
230 }
231 if (element instanceof Text) {
232 Text textChunk = (Text)element;
233 writer.write(textChunk.getText());
234 } else if (element instanceof Style) {
235 io.write(((Style)element));
236 } else if (element instanceof CLS) {
237 io.cls();
238 } else {
239 throw new UnsupportedOperationException("todo");
240 }
241 }
242 }