001 /*
002 * Copyright (C) 2003-2009 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 java.io.IOException;
023 import java.util.LinkedList;
024 import java.util.NoSuchElementException;
025
026 /**
027 * <p>This class provides an abstraction for a console. This implementation wraps the input and output of a terminal
028 * based on a bidirectional io.</p>
029 *
030 * <p>Interactions between terminal and console are done though the {@link ViewReader} and {@link ViewWriter}
031 * classes.</p>
032 *
033 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
034 * @version $Revision$
035 */
036 public final class Console {
037
038 /** . */
039 private char[] buffer;
040
041 /** . */
042 private int size;
043
044 /** Cursor Position, always equal to {@link #size} unless the underlying *.IO class supports editing. */
045 private int curAt;
046
047 /** . */
048 private LinkedList<CharSequence> lines;
049
050 /** Do we have a issued a CR previously? */
051 private boolean previousCR;
052
053 /** Whether or not we do echoing. */
054 private boolean echoing;
055
056 /** . */
057 private final ViewWriter viewWriter;
058
059 /** . */
060 private final ViewReader viewReader = new ViewReader() {
061
062 @Override
063 public CharSequence replace(CharSequence s) throws IOException {
064 StringBuilder builder = new StringBuilder();
065 for (int i = appendDel();i != -1;i = appendDel()) {
066 builder.append((char)i);
067 }
068 appendData(s);
069 return builder.reverse().toString();
070 }
071
072 @Override
073 public void write(char c) throws IOException {
074 appendData(c);
075 }
076
077 @Override
078 public void write(CharSequence s) throws IOException {
079 appendData(s.toString());
080 }
081
082 @Override
083 public void del() throws IOException {
084 appendDel();
085 }
086
087 @Override
088 public void moveRight() throws IOException {
089 Console.this.moveRight();
090 }
091
092 @Override
093 public void moveLeft() throws IOException {
094 Console.this.moveLeft();
095 }
096 };
097
098 /** . */
099 private final ConsoleReader reader = new ConsoleReader() {
100 @Override
101 public int getSize() {
102 return size;
103 }
104
105 @Override
106 public boolean hasNext() {
107 return lines.size() > 0;
108 }
109
110 @Override
111 public CharSequence next() {
112 if (lines.size() > 0) {
113 return lines.removeFirst();
114 } else {
115 throw new NoSuchElementException();
116 }
117 }
118 };
119
120 /** . */
121 private final ConsoleWriter writer = new ConsoleWriter() {
122
123 //
124 private boolean previousCR;
125
126 @Override
127 public void write(CharSequence s) throws IOException {
128 for (int i = 0;i < s.length();i++) {
129 char c = s.charAt(i);
130 writeNoFlush(c);
131 }
132 viewWriter.flush();
133 }
134
135 public void write(char c) throws IOException {
136 writeNoFlush(c);
137 viewWriter.flush();
138 }
139
140 private void writeNoFlush(char c) throws IOException {
141 if (previousCR && c == '\n') {
142 previousCR = false;
143 } else if (c == '\r' || c == '\n') {
144 previousCR = c == '\r';
145 viewWriter.writeCRLF();
146 } else {
147 viewWriter.write(c);
148 }
149 }
150 };
151
152 public Console(ViewWriter viewWriter) {
153 this.buffer = new char[128];
154 this.size = 0;
155 this.curAt = 0;
156 this.lines = new LinkedList<CharSequence>();
157 this.previousCR = false;
158 this.echoing = true;
159 this.viewWriter = viewWriter;
160 }
161
162 /**
163 * Clears the buffer without doing any echoing.
164 */
165 public void clearBuffer() {
166 this.previousCR = false;
167 this.curAt = 0;
168 this.size = 0;
169 }
170
171 public CharSequence getBuffer() {
172 return new String(buffer, 0, size);
173 }
174
175 public CharSequence getBufferToCursor() {
176 return new String(buffer, 0, curAt);
177 }
178
179 public boolean isEchoing() {
180 return echoing;
181 }
182
183 public void setEchoing(boolean echoing) {
184 Console.this.echoing = echoing;
185 }
186
187 /**
188 * Returns the console reader.
189 *
190 * @return the console reader
191 */
192 public ConsoleReader getReader() {
193 return reader;
194 }
195
196 public ViewReader getViewReader() {
197 return viewReader;
198 }
199
200 public ConsoleWriter getWriter() {
201 return writer;
202 }
203
204 private void appendData(CharSequence s) throws IOException {
205 for (int i = 0;i < s.length();i++) {
206 appendData(s.charAt(i));
207 }
208 }
209
210 private void appendData(char c) throws IOException {
211 if (previousCR && c == '\n') {
212 previousCR = false;
213 } else if (c == '\r' || c == '\n') {
214 previousCR = c == '\r';
215 String line = new String(buffer, 0, size);
216 lines.add(line);
217 size = 0;
218 curAt = size;
219 echoCRLF();
220 } else {
221 push(c);
222 }
223 }
224
225 private int appendDel() throws IOException {
226 if (curAt == size){
227 int popped = pop();
228
229 //
230 if (popped != -1) {
231 // previous = buffer[size];
232 echoDel();
233 } else {
234 // previous = -1;
235 }
236
237 //
238 return popped;
239 } else {
240 // Cursor in body
241 if (curAt == 0) {
242 // previous = -1;
243 return -1;
244 }
245 // remove previous char from buffer
246 for (int idx = curAt-1; idx < size; idx++) {
247 buffer[idx] = buffer[idx+1];
248 }
249 buffer[size-1] = ' ';
250 // Adjust cursor at and size
251 --size;
252 moveLeft();
253 // Redisplay from cursor to end
254 String disp = new String(buffer, curAt, size - curAt + 1);
255 viewWriter.write(disp);
256 // position cursor one to left from where started
257 int saveCurAt = curAt;
258 curAt = size + 1; // Size before delete
259 while (curAt > saveCurAt) {
260 moveLeft();
261 }
262 if (curAt == 0) {
263 return -1;
264 }
265 return buffer[curAt - 1];
266 }
267 }
268
269 private void moveRight() throws IOException {
270 if (curAt < size) {
271 if (viewWriter.writeMoveRight(buffer[curAt]))
272 {
273 viewWriter.flush();
274 curAt++;
275 }
276 }
277 }
278
279 private void moveLeft() throws IOException {
280 if (curAt > 0) {
281 if (viewWriter.writeMoveLeft())
282 {
283 viewWriter.flush();
284 curAt--;
285 }
286 }
287 }
288
289 private void echo(char c) throws IOException {
290 if (echoing) {
291 viewWriter.write(c);
292 viewWriter.flush();
293 }
294 }
295
296 private void echo(String s) throws IOException {
297 if (echoing) {
298 viewWriter.write(s);
299 viewWriter.flush();
300 }
301 }
302
303 private void echoDel() throws IOException {
304 if (echoing) {
305 viewWriter.writeDel();
306 viewWriter.flush();
307 }
308 }
309
310 private void echoCRLF() throws IOException {
311 if (echoing) {
312 viewWriter.writeCRLF();
313 viewWriter.flush();
314 }
315 }
316
317 private int pop() {
318 if (size > 0) {
319 --size;
320 curAt = size;
321 return buffer[size];
322 } else {
323 curAt = size;
324 return -1;
325 }
326 }
327
328 private void push(char c) throws IOException {
329 if (size >= buffer.length) {
330 char[] tmp = new char[buffer.length * 2 + 1];
331 System.arraycopy(buffer, 0, tmp, 0, buffer.length);
332 Console.this.buffer = tmp;
333 }
334 if (curAt == size) {
335 buffer[size++] = c;
336 curAt = size;
337 // previous = c;
338 echo(c);
339 } else {
340 // In body
341 // Shift buffer one from position before cursor
342 for (int idx = size-1; idx > curAt - 1; idx--) {
343 buffer[idx+1] = buffer[idx];
344 }
345 buffer[curAt] = c;
346 // Adjust size and display from inserted character to end
347 ++size;
348 String disp = new String(buffer, curAt, size - curAt);
349 viewWriter.write(disp);
350 // Move cursor to original character
351 int saveCurAt = ++curAt;
352 curAt = size;
353 while (curAt > saveCurAt) {
354 moveLeft();
355 }
356 // previous = buffer[curAt-1];
357 }
358 }
359 }