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 }