001 /*
002 * Copyright (c) 2002-2006, Marc Prud'hommeaux. All rights reserved.
003 *
004 * This software is distributable under the BSD license. See the terms of the
005 * BSD license in the documentation provided with this software.
006 */
007 package jline;
008
009 import java.awt.*;
010 import java.awt.datatransfer.*;
011
012 import java.io.*;
013 import java.util.*;
014 import java.util.List;
015
016
017 /**
018 * A reader for console applications. It supports custom tab-completion,
019 * saveable command history, and command line editing. On some
020 * platforms, platform-specific commands will need to be
021 * issued before the reader will function properly. See
022 * {@link Terminal#initializeTerminal} for convenience methods for
023 * issuing platform-specific setup commands.
024 *
025 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
026 */
027 public class ConsoleReader implements ConsoleOperations {
028 String prompt;
029 private boolean useHistory = true;
030 public static final String CR = System.getProperty("line.separator");
031
032 /**
033 * Map that contains the operation name to keymay operation mapping.
034 */
035 public static SortedMap KEYMAP_NAMES;
036
037 static {
038 Map names = new TreeMap();
039
040 names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG));
041 names.put("MOVE_TO_END", new Short(MOVE_TO_END));
042 names.put("PREV_CHAR", new Short(PREV_CHAR));
043 names.put("NEWLINE", new Short(NEWLINE));
044 names.put("KILL_LINE", new Short(KILL_LINE));
045 names.put("PASTE", new Short(PASTE));
046 names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN));
047 names.put("NEXT_HISTORY", new Short(NEXT_HISTORY));
048 names.put("PREV_HISTORY", new Short(PREV_HISTORY));
049 names.put("REDISPLAY", new Short(REDISPLAY));
050 names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV));
051 names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD));
052 names.put("NEXT_CHAR", new Short(NEXT_CHAR));
053 names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR));
054 names.put("SEARCH_PREV", new Short(SEARCH_PREV));
055 names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR));
056 names.put("SEARCH_NEXT", new Short(SEARCH_NEXT));
057 names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD));
058 names.put("TO_END_WORD", new Short(TO_END_WORD));
059 names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV));
060 names.put("PASTE_PREV", new Short(PASTE_PREV));
061 names.put("REPLACE_MODE", new Short(REPLACE_MODE));
062 names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE));
063 names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR));
064 names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD));
065 names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR));
066 names.put("ADD", new Short(ADD));
067 names.put("PREV_WORD", new Short(PREV_WORD));
068 names.put("CHANGE_META", new Short(CHANGE_META));
069 names.put("DELETE_META", new Short(DELETE_META));
070 names.put("END_WORD", new Short(END_WORD));
071 names.put("NEXT_CHAR", new Short(NEXT_CHAR));
072 names.put("INSERT", new Short(INSERT));
073 names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT));
074 names.put("PASTE_NEXT", new Short(PASTE_NEXT));
075 names.put("REPLACE_CHAR", new Short(REPLACE_CHAR));
076 names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR));
077 names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR));
078 names.put("UNDO", new Short(UNDO));
079 names.put("NEXT_WORD", new Short(NEXT_WORD));
080 names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR));
081 names.put("CHANGE_CASE", new Short(CHANGE_CASE));
082 names.put("COMPLETE", new Short(COMPLETE));
083 names.put("EXIT", new Short(EXIT));
084
085 KEYMAP_NAMES = new TreeMap(Collections.unmodifiableMap(names));
086 }
087
088 /**
089 * The map for logical operations.
090 */
091 private final short[] keybindings;
092
093 /**
094 * If true, issue an audible keyboard bell when appropriate.
095 */
096 private boolean bellEnabled = true;
097
098 /**
099 * The current character mask.
100 */
101 private Character mask = null;
102
103 /**
104 * The null mask.
105 */
106 private static final Character NULL_MASK = new Character((char) 0);
107
108 /**
109 * The number of tab-completion candidates above which a warning
110 * will be prompted before showing all the candidates.
111 */
112 private int autoprintThreshhold = Integer.getInteger
113 ("jline.completion.threshold", 100).intValue(); // same default as bash
114
115 /**
116 * The Terminal to use.
117 */
118 private final Terminal terminal;
119 private CompletionHandler completionHandler =
120 new CandidateListCompletionHandler();
121 InputStream in;
122 final Writer out;
123 final CursorBuffer buf = new CursorBuffer();
124 static PrintWriter debugger;
125 History history = new History();
126 final List completors = new LinkedList();
127 private Character echoCharacter = null;
128
129 /**
130 * Create a new reader using {@link FileDescriptor#in} for input
131 * and {@link System#out} for output. {@link FileDescriptor#in} is
132 * used because it has a better chance of being unbuffered.
133 */
134 public ConsoleReader() throws IOException {
135 this(new FileInputStream(FileDescriptor.in),
136 new PrintWriter(System.out));
137 }
138
139 /**
140 * Create a new reader using the specified {@link InputStream}
141 * for input and the specific writer for output, using the
142 * default keybindings resource.
143 */
144 public ConsoleReader(final InputStream in, final Writer out)
145 throws IOException {
146 this(in, out, null);
147 }
148
149 public ConsoleReader(final InputStream in, final Writer out,
150 final InputStream bindings) throws IOException {
151 this(in, out, bindings, Terminal.getTerminal());
152 }
153
154 /**
155 * Create a new reader.
156 *
157 * @param in the input
158 * @param out the output
159 * @param bindings the key bindings to use
160 * @param term the terminal to use
161 */
162 public ConsoleReader(InputStream in, Writer out, InputStream bindings,
163 Terminal term) throws IOException {
164 this.terminal = term;
165 setInput(in);
166 this.out = out;
167
168 if (bindings == null) {
169 String bindingFile =
170 System.getProperty("jline.keybindings",
171 new File(System.getProperty("user.home",
172 ".jlinebindings.properties")).getAbsolutePath());
173
174 if (!(new File(bindingFile).isFile())) {
175 bindings = ConsoleReader.class.
176 getResourceAsStream("keybindings.properties");
177 } else {
178 bindings = new FileInputStream(new File(bindingFile));
179 }
180 }
181
182 this.keybindings = new short[Byte.MAX_VALUE * 2];
183
184 Arrays.fill(this.keybindings, UNKNOWN);
185
186 /**
187 * Loads the key bindings. Bindings file is in the format:
188 *
189 * keycode: operation name
190 */
191 if (bindings != null) {
192 Properties p = new Properties();
193 p.load(bindings);
194 bindings.close();
195
196 for (Iterator i = p.keySet().iterator(); i.hasNext();) {
197 String val = (String) i.next();
198
199 try {
200 Short code = new Short(val);
201 String op = (String) p.getProperty(val);
202
203 Short opval = (Short) KEYMAP_NAMES.get(op);
204
205 if (opval != null) {
206 keybindings[code.shortValue()] = opval.shortValue();
207 }
208 } catch (NumberFormatException nfe) {
209 consumeException(nfe);
210 }
211 }
212
213 // hardwired arrow key bindings
214 // keybindings[VK_UP] = PREV_HISTORY;
215 // keybindings[VK_DOWN] = NEXT_HISTORY;
216 // keybindings[VK_LEFT] = PREV_CHAR;
217 // keybindings[VK_RIGHT] = NEXT_CHAR;
218 }
219 }
220
221 public Terminal getTerminal() {
222 return this.terminal;
223 }
224
225 /**
226 * Set the stream for debugging. Development use only.
227 */
228 public void setDebug(final PrintWriter debugger) {
229 ConsoleReader.debugger = debugger;
230 }
231
232 /**
233 * Set the stream to be used for console input.
234 */
235 public void setInput(final InputStream in) {
236 this.in = in;
237 }
238
239 /**
240 * Returns the stream used for console input.
241 */
242 public InputStream getInput() {
243 return this.in;
244 }
245
246 /**
247 * Read the next line and return the contents of the buffer.
248 */
249 public String readLine() throws IOException {
250 return readLine((String) null);
251 }
252
253 /**
254 * Read the next line with the specified character mask. If null, then
255 * characters will be echoed. If 0, then no characters will be echoed.
256 */
257 public String readLine(final Character mask) throws IOException {
258 return readLine(null, mask);
259 }
260
261 /**
262 * @param bellEnabled if true, enable audible keyboard bells if
263 * an alert is required.
264 */
265 public void setBellEnabled(final boolean bellEnabled) {
266 this.bellEnabled = bellEnabled;
267 }
268
269 /**
270 * @return true is audible keyboard bell is enabled.
271 */
272 public boolean getBellEnabled() {
273 return this.bellEnabled;
274 }
275
276 /**
277 * Query the terminal to find the current width;
278 *
279 * @see Terminal#getTerminalWidth
280 * @return the width of the current terminal.
281 */
282 public int getTermwidth() {
283 return Terminal.setupTerminal().getTerminalWidth();
284 }
285
286 /**
287 * Query the terminal to find the current width;
288 *
289 * @see Terminal#getTerminalHeight
290 *
291 * @return the height of the current terminal.
292 */
293 public int getTermheight() {
294 return Terminal.setupTerminal().getTerminalHeight();
295 }
296
297 /**
298 * @param autoprintThreshhold the number of candidates to print
299 * without issuing a warning.
300 */
301 public void setAutoprintThreshhold(final int autoprintThreshhold) {
302 this.autoprintThreshhold = autoprintThreshhold;
303 }
304
305 /**
306 * @return the number of candidates to print without issing a warning.
307 */
308 public int getAutoprintThreshhold() {
309 return this.autoprintThreshhold;
310 }
311
312 int getKeyForAction(short logicalAction) {
313 for (int i = 0; i < keybindings.length; i++) {
314 if (keybindings[i] == logicalAction) {
315 return i;
316 }
317 }
318
319 return -1;
320 }
321
322 /**
323 * Clear the echoed characters for the specified character code.
324 */
325 int clearEcho(int c) throws IOException {
326 // if the terminal is not echoing, then just return...
327 if (!terminal.getEcho()) {
328 return 0;
329 }
330
331 // otherwise, clear
332 int num = countEchoCharacters((char) c);
333 back(num);
334 drawBuffer(num);
335
336 return num;
337 }
338
339 int countEchoCharacters(char c) {
340 // tabs as special: we need to determine the number of spaces
341 // to cancel based on what out current cursor position is
342 if (c == 9) {
343 int tabstop = 8; // will this ever be different?
344 int position = getCursorPosition();
345
346 return tabstop - (position % tabstop);
347 }
348
349 return getPrintableCharacters(c).length();
350 }
351
352 /**
353 * Return the number of characters that will be printed when the
354 * specified character is echoed to the screen. Adapted from
355 * cat by Torbjorn Granlund, as repeated in stty by
356 * David MacKenzie.
357 */
358 StringBuffer getPrintableCharacters(char ch) {
359 StringBuffer sbuff = new StringBuffer();
360
361 if (ch >= 32) {
362 if (ch < 127) {
363 sbuff.append(ch);
364 } else if (ch == 127) {
365 sbuff.append('^');
366 sbuff.append('?');
367 } else {
368 sbuff.append('M');
369 sbuff.append('-');
370
371 if (ch >= (128 + 32)) {
372 if (ch < (128 + 127)) {
373 sbuff.append((char) (ch - 128));
374 } else {
375 sbuff.append('^');
376 sbuff.append('?');
377 }
378 } else {
379 sbuff.append('^');
380 sbuff.append((char) (ch - 128 + 64));
381 }
382 }
383 } else {
384 sbuff.append('^');
385 sbuff.append((char) (ch + 64));
386 }
387
388 return sbuff;
389 }
390
391 int getCursorPosition() {
392 // FIXME: does not handle anything but a line with a prompt
393 // absolute position
394 return ((prompt == null) ? 0 : prompt.length()) + buf.cursor;
395 }
396
397 public String readLine(final String prompt) throws IOException {
398 return readLine(prompt, null);
399 }
400
401 /**
402 * The default prompt that will be issued.
403 */
404 public void setDefaultPrompt(String prompt) {
405 this.prompt = prompt;
406 }
407
408 /**
409 * The default prompt that will be issued.
410 */
411 public String getDefaultPrompt() {
412 return prompt;
413 }
414
415 /**
416 * Read a line from the <i>in</i> {@link InputStream}, and
417 * return the line (without any trailing newlines).
418 *
419 * @param prompt the prompt to issue to the console, may be null.
420 * @return a line that is read from the terminal, or null if there
421 * was null input (e.g., <i>CTRL-D</i> was pressed).
422 */
423 public String readLine(final String prompt, final Character mask)
424 throws IOException {
425 this.mask = mask;
426 if (prompt != null)
427 this.prompt = prompt;
428
429 try {
430 terminal.beforeReadLine(this, prompt, mask);
431
432 if ((prompt != null) && (prompt.length() > 0)) {
433 out.write(prompt);
434 out.flush();
435 }
436
437 // if the terminal is unsupported, just use plain-java reading
438 if (!terminal.isSupported()) {
439 return readLine(in);
440 }
441
442 while (true) {
443 int[] next = readBinding();
444
445 if (next == null) {
446 return null;
447 }
448
449 int c = next[0];
450 int code = next[1];
451
452 if (c == -1) {
453 return null;
454 }
455
456 boolean success = true;
457
458 switch (code) {
459 case EXIT: // ctrl-d
460
461 if (buf.buffer.length() == 0) {
462 return null;
463 }
464
465 case COMPLETE: // tab
466 success = complete();
467 break;
468
469 case MOVE_TO_BEG:
470 success = setCursorPosition(0);
471 break;
472
473 case KILL_LINE: // CTRL-K
474 success = killLine();
475 break;
476
477 case CLEAR_SCREEN: // CTRL-L
478 success = clearScreen();
479 break;
480
481 case KILL_LINE_PREV: // CTRL-U
482 success = resetLine();
483 break;
484
485 case NEWLINE: // enter
486 printNewline(); // output newline
487 return finishBuffer();
488
489 case DELETE_PREV_CHAR: // backspace
490 success = backspace();
491 break;
492
493 case MOVE_TO_END:
494 success = moveToEnd();
495 break;
496
497 case PREV_CHAR:
498 success = moveCursor(-1) != 0;
499 break;
500
501 case NEXT_CHAR:
502 success = moveCursor(1) != 0;
503 break;
504
505 case NEXT_HISTORY:
506 success = moveHistory(true);
507 break;
508
509 case PREV_HISTORY:
510 success = moveHistory(false);
511 break;
512
513 case REDISPLAY:
514 break;
515
516 case PASTE:
517 success = paste();
518 break;
519
520 case DELETE_PREV_WORD:
521 success = deletePreviousWord();
522 break;
523
524 case PREV_WORD:
525 success = previousWord();
526 break;
527
528 case NEXT_WORD:
529 success = nextWord();
530 break;
531
532 case UNKNOWN:default:
533 putChar(c, true);
534 }
535
536 if (!(success)) {
537 beep();
538 }
539
540 flushConsole();
541 }
542 } finally {
543 terminal.afterReadLine(this, prompt, mask);
544 }
545 }
546
547 private String readLine(InputStream in) throws IOException {
548 StringBuffer buf = new StringBuffer();
549
550 while (true) {
551 int i = in.read();
552
553 if ((i == -1) || (i == '\n') || (i == '\r')) {
554 return buf.toString();
555 }
556
557 buf.append((char) i);
558 }
559
560 // return new BufferedReader (new InputStreamReader (in)).readLine ();
561 }
562
563 /**
564 * Reads the console input and returns an array of the form
565 * [raw, key binding].
566 */
567 private int[] readBinding() throws IOException {
568 int c = readVirtualKey();
569
570 if (c == -1) {
571 return null;
572 }
573
574 // extract the appropriate key binding
575 short code = keybindings[c];
576
577 if (debugger != null) {
578 debug(" translated: " + (int) c + ": " + code);
579 }
580
581 return new int[] {
582 c,
583 code
584 };
585 }
586
587 /**
588 * Move up or down the history tree.
589 *
590 * @param direction less than 0 to move up the tree, down otherwise
591 */
592 private final boolean moveHistory(final boolean next) throws IOException {
593 if (next && !history.next()) {
594 return false;
595 } else if (!next && !history.previous()) {
596 return false;
597 }
598
599 setBuffer(history.current());
600
601 return true;
602 }
603
604 /**
605 * Paste the contents of the clipboard into the console buffer
606 *
607 * @return true if clipboard contents pasted
608 */
609 public boolean paste() throws IOException {
610 Clipboard clipboard = Toolkit.
611 getDefaultToolkit().getSystemClipboard();
612
613 if (clipboard == null) {
614 return false;
615 }
616
617 Transferable transferable = clipboard.getContents(null);
618
619 if (transferable == null) {
620 return false;
621 }
622
623 try {
624 Object content = transferable.
625 getTransferData(DataFlavor.plainTextFlavor);
626
627 /*
628 * This fix was suggested in bug #1060649 at
629 * http://sourceforge.net/tracker/index.php?func=detail&aid=1060649&group_id=64033&atid=506056
630 * to get around the deprecated DataFlavor.plainTextFlavor, but
631 * it raises a UnsupportedFlavorException on Mac OS X
632 */
633 if (content == null) {
634 try {
635 content = new DataFlavor().getReaderForText(transferable);
636 } catch (Exception e) {
637 }
638 }
639
640 if (content == null) {
641 return false;
642 }
643
644 String value;
645
646 if (content instanceof Reader) {
647 // TODO: we might want instead connect to the input stream
648 // so we can interpret individual lines
649 value = "";
650
651 String line = null;
652
653 for (BufferedReader read = new BufferedReader((Reader) content);
654 (line = read.readLine()) != null;) {
655 if (value.length() > 0) {
656 value += "\n";
657 }
658
659 value += line;
660 }
661 } else {
662 value = content.toString();
663 }
664
665 if (value == null) {
666 return true;
667 }
668
669 putString(value);
670
671 return true;
672 } catch (UnsupportedFlavorException ufe) {
673 if (debugger != null)
674 debug(ufe + "");
675
676 return false;
677 }
678 }
679
680 /**
681 * Kill the buffer ahead of the current cursor position.
682 *
683 * @return true if successful
684 */
685 public boolean killLine() throws IOException {
686 int cp = buf.cursor;
687 int len = buf.buffer.length();
688
689 if (cp >= len) {
690 return false;
691 }
692
693 int num = buf.buffer.length() - cp;
694 clearAhead(num);
695
696 for (int i = 0; i < num; i++) {
697 buf.buffer.deleteCharAt(len - i - 1);
698 }
699
700 return true;
701 }
702
703 /**
704 * Clear the screen by issuing the ANSI "clear screen" code.
705 */
706 public boolean clearScreen() throws IOException {
707 if (!terminal.isANSISupported()) {
708 return false;
709 }
710
711 // send the ANSI code to clear the screen
712 printString(((char) 27) + "[2J");
713 flushConsole();
714
715 // then send the ANSI code to go to position 1,1
716 printString(((char) 27) + "[1;1H");
717 flushConsole();
718
719 redrawLine();
720
721 return true;
722 }
723
724 /**
725 * Use the completors to modify the buffer with the
726 * appropriate completions.
727 *
728 * @return true if successful
729 */
730 private final boolean complete() throws IOException {
731 // debug ("tab for (" + buf + ")");
732 if (completors.size() == 0) {
733 return false;
734 }
735
736 List candidates = new LinkedList();
737 String bufstr = buf.buffer.toString();
738 int cursor = buf.cursor;
739
740 int position = -1;
741
742 for (Iterator i = completors.iterator(); i.hasNext();) {
743 Completor comp = (Completor) i.next();
744
745 if ((position = comp.complete(bufstr, cursor, candidates)) != -1) {
746 break;
747 }
748 }
749
750 // no candidates? Fail.
751 if (candidates.size() == 0) {
752 return false;
753 }
754
755 return completionHandler.complete(this, candidates, position);
756 }
757
758 public CursorBuffer getCursorBuffer() {
759 return buf;
760 }
761
762 /**
763 * Output the specified {@link Collection} in proper columns.
764 *
765 * @param stuff the stuff to print
766 */
767 public void printColumns(final Collection stuff) throws IOException {
768 if ((stuff == null) || (stuff.size() == 0)) {
769 return;
770 }
771
772 int width = getTermwidth();
773 int maxwidth = 0;
774
775 for (Iterator i = stuff.iterator(); i.hasNext();
776 maxwidth = Math.max(maxwidth, i.next().toString().length())) {
777 ;
778 }
779
780 StringBuffer line = new StringBuffer();
781
782 for (Iterator i = stuff.iterator(); i.hasNext();) {
783 String cur = (String) i.next();
784
785 if ((line.length() + maxwidth) > width) {
786 printString(line.toString().trim());
787 printNewline();
788 line.setLength(0);
789 }
790
791 pad(cur, maxwidth + 3, line);
792 }
793
794 if (line.length() > 0) {
795 printString(line.toString().trim());
796 printNewline();
797 line.setLength(0);
798 }
799 }
800
801 /**
802 * Append <i>toPad</i> to the specified <i>appendTo</i>, as
803 * well as (<i>toPad.length () - len</i>) spaces.
804 *
805 * @param toPad the {@link String} to pad
806 * @param len the target length
807 * @param appendTo the {@link StringBuffer} to which to append the
808 * padded {@link String}.
809 */
810 private final void pad(final String toPad, final int len,
811 final StringBuffer appendTo) {
812 appendTo.append(toPad);
813
814 for (int i = 0; i < (len - toPad.length());
815 i++, appendTo.append(' ')) {
816 ;
817 }
818 }
819
820 /**
821 * Add the specified {@link Completor} to the list of handlers
822 * for tab-completion.
823 *
824 * @param completor the {@link Completor} to add
825 * @return true if it was successfully added
826 */
827 public boolean addCompletor(final Completor completor) {
828 return completors.add(completor);
829 }
830
831 /**
832 * Remove the specified {@link Completor} from the list of handlers
833 * for tab-completion.
834 *
835 * @param completor the {@link Completor} to remove
836 * @return true if it was successfully removed
837 */
838 public boolean removeCompletor(final Completor completor) {
839 return completors.remove(completor);
840 }
841
842 /**
843 * Returns an unmodifiable list of all the completors.
844 */
845 public Collection getCompletors() {
846 return Collections.unmodifiableList(completors);
847 }
848
849 /**
850 * Erase the current line.
851 *
852 * @return false if we failed (e.g., the buffer was empty)
853 */
854 final boolean resetLine() throws IOException {
855 if (buf.cursor == 0) {
856 return false;
857 }
858
859 backspaceAll();
860
861 return true;
862 }
863
864 /**
865 * Move the cursor position to the specified absolute index.
866 */
867 public final boolean setCursorPosition(final int position)
868 throws IOException {
869 return moveCursor(position - buf.cursor) != 0;
870 }
871
872 /**
873 * Set the current buffer's content to the specified
874 * {@link String}. The visual console will be modified
875 * to show the current buffer.
876 *
877 * @param buffer the new contents of the buffer.
878 */
879 private final void setBuffer(final String buffer) throws IOException {
880 // don't bother modifying it if it is unchanged
881 if (buffer.equals(buf.buffer.toString())) {
882 return;
883 }
884
885 // obtain the difference between the current buffer and the new one
886 int sameIndex = 0;
887
888 for (int i = 0, l1 = buffer.length(), l2 = buf.buffer.length();
889 (i < l1) && (i < l2); i++) {
890 if (buffer.charAt(i) == buf.buffer.charAt(i)) {
891 sameIndex++;
892 } else {
893 break;
894 }
895 }
896
897 int diff = buf.buffer.length() - sameIndex;
898
899 backspace(diff); // go back for the differences
900 killLine(); // clear to the end of the line
901 buf.buffer.setLength(sameIndex); // the new length
902 putString(buffer.substring(sameIndex)); // append the differences
903 }
904
905 /**
906 * Clear the line and redraw it.
907 */
908 public final void redrawLine() throws IOException {
909 printCharacter(RESET_LINE);
910 flushConsole();
911 drawLine();
912 }
913
914 /**
915 * Output put the prompt + the current buffer
916 */
917 public final void drawLine() throws IOException {
918 if (prompt != null) {
919 printString(prompt);
920 }
921
922 printString(buf.buffer.toString());
923 }
924
925 /**
926 * Output a platform-dependant newline.
927 */
928 public final void printNewline() throws IOException {
929 printString(CR);
930 flushConsole();
931 }
932
933 /**
934 * Clear the buffer and add its contents to the history.
935 *
936 * @return the former contents of the buffer.
937 */
938 final String finishBuffer() {
939 String str = buf.buffer.toString();
940
941 // we only add it to the history if the buffer is not empty
942 // and if mask is null, since having a mask typically means
943 // the string was a password. We clear the mask after this call
944 if (str.length() > 0) {
945 if (mask == null && useHistory) {
946 history.addToHistory(str);
947 } else {
948 mask = null;
949 }
950 }
951
952 history.moveToEnd();
953
954 buf.buffer.setLength(0);
955 buf.cursor = 0;
956
957 return str;
958 }
959
960 /**
961 * Write out the specified string to the buffer and the
962 * output stream.
963 */
964 public final void putString(final String str) throws IOException {
965 buf.insert(str);
966 printString(str);
967 drawBuffer();
968 }
969
970 /**
971 * Output the specified string to the output stream (but not the
972 * buffer).
973 */
974 public final void printString(final String str) throws IOException {
975 printCharacters(str.toCharArray());
976 }
977
978 /**
979 * Output the specified character, both to the buffer
980 * and the output stream.
981 */
982 private final void putChar(final int c, final boolean print)
983 throws IOException {
984 buf.insert((char) c);
985
986 if (print) {
987 // no masking...
988 if (mask == null) {
989 printCharacter(c);
990 }
991 // null mask: don't print anything...
992 else if (mask.charValue() == 0) {
993 ;
994 }
995 // otherwise print the mask...
996 else {
997 printCharacter(mask.charValue());
998 }
999
1000 drawBuffer();
1001 }
1002 }
1003
1004 /**
1005 * Redraw the rest of the buffer from the cursor onwards. This
1006 * is necessary for inserting text into the buffer.
1007 *
1008 * @param clear the number of characters to clear after the
1009 * end of the buffer
1010 */
1011 private final void drawBuffer(final int clear) throws IOException {
1012 // debug ("drawBuffer: " + clear);
1013 char[] chars = buf.buffer.substring(buf.cursor).toCharArray();
1014 printCharacters(chars);
1015
1016 clearAhead(clear);
1017 back(chars.length);
1018 flushConsole();
1019 }
1020
1021 /**
1022 * Redraw the rest of the buffer from the cursor onwards. This
1023 * is necessary for inserting text into the buffer.
1024 */
1025 private final void drawBuffer() throws IOException {
1026 drawBuffer(0);
1027 }
1028
1029 /**
1030 * Clear ahead the specified number of characters
1031 * without moving the cursor.
1032 */
1033 private final void clearAhead(final int num) throws IOException {
1034 if (num == 0) {
1035 return;
1036 }
1037
1038 // debug ("clearAhead: " + num);
1039
1040 // print blank extra characters
1041 printCharacters(' ', num);
1042
1043 // we need to flush here so a "clever" console
1044 // doesn't just ignore the redundancy of a space followed by
1045 // a backspace.
1046 flushConsole();
1047
1048 // reset the visual cursor
1049 back(num);
1050
1051 flushConsole();
1052 }
1053
1054 /**
1055 * Move the visual cursor backwards without modifying the
1056 * buffer cursor.
1057 */
1058 private final void back(final int num) throws IOException {
1059 printCharacters(BACKSPACE, num);
1060 flushConsole();
1061 }
1062
1063 /**
1064 * Issue an audible keyboard bell, if
1065 * {@link #getBellEnabled} return true.
1066 */
1067 public final void beep() throws IOException {
1068 if (!(getBellEnabled())) {
1069 return;
1070 }
1071
1072 printCharacter(KEYBOARD_BELL);
1073 // need to flush so the console actually beeps
1074 flushConsole();
1075 }
1076
1077 /**
1078 * Output the specified character to the output stream
1079 * without manipulating the current buffer.
1080 */
1081 private final void printCharacter(final int c) throws IOException {
1082 out.write(c);
1083 }
1084
1085 /**
1086 * Output the specified characters to the output stream
1087 * without manipulating the current buffer.
1088 */
1089 private final void printCharacters(final char[] c)
1090 throws IOException {
1091 out.write(c);
1092 }
1093
1094 private final void printCharacters(final char c, final int num)
1095 throws IOException {
1096 if (num == 1) {
1097 printCharacter(c);
1098 } else {
1099 char[] chars = new char[num];
1100 Arrays.fill(chars, c);
1101 printCharacters(chars);
1102 }
1103 }
1104
1105 /**
1106 * Flush the console output stream. This is important for
1107 * printout out single characters (like a backspace or keyboard)
1108 * that we want the console to handle immedately.
1109 */
1110 public final void flushConsole() throws IOException {
1111 out.flush();
1112 }
1113
1114 private final int backspaceAll() throws IOException {
1115 return backspace(Integer.MAX_VALUE);
1116 }
1117
1118 /**
1119 * Issue <em>num</em> backspaces.
1120 *
1121 * @return the number of characters backed up
1122 */
1123 private final int backspace(final int num) throws IOException {
1124 if (buf.cursor == 0) {
1125 return 0;
1126 }
1127
1128 int count = 0;
1129
1130 count = moveCursor(-1 * num) * -1;
1131 // debug ("Deleting from " + buf.cursor + " for " + count);
1132 buf.buffer.delete(buf.cursor, buf.cursor + count);
1133 drawBuffer(count);
1134
1135 return count;
1136 }
1137
1138 /**
1139 * Issue a backspace.
1140 *
1141 * @return true if successful
1142 */
1143 public final boolean backspace() throws IOException {
1144 return backspace(1) == 1;
1145 }
1146
1147 private final boolean moveToEnd() throws IOException {
1148 if (moveCursor(1) == 0) {
1149 return false;
1150 }
1151
1152 while (moveCursor(1) != 0) {
1153 ;
1154 }
1155
1156 return true;
1157 }
1158
1159 /**
1160 * Delete the character at the current position and
1161 * redraw the remainder of the buffer.
1162 */
1163 private final boolean deleteCurrentCharacter() throws IOException {
1164 buf.buffer.deleteCharAt(buf.cursor);
1165 drawBuffer(1);
1166
1167 return true;
1168 }
1169
1170 private final boolean previousWord() throws IOException {
1171 while (isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1172 ;
1173 }
1174
1175 while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1176 ;
1177 }
1178
1179 return true;
1180 }
1181
1182 private final boolean nextWord() throws IOException {
1183 while (isDelimiter(buf.current()) && (moveCursor(1) != 0)) {
1184 ;
1185 }
1186
1187 while (!isDelimiter(buf.current()) && (moveCursor(1) != 0)) {
1188 ;
1189 }
1190
1191 return true;
1192 }
1193
1194 private final boolean deletePreviousWord() throws IOException {
1195 while (isDelimiter(buf.current()) && backspace()) {
1196 ;
1197 }
1198
1199 while (!isDelimiter(buf.current()) && backspace()) {
1200 ;
1201 }
1202
1203 return true;
1204 }
1205
1206 /**
1207 * Move the cursor <i>where</i> characters.
1208 *
1209 * @param where if less than 0, move abs(<i>where</i>) to the left,
1210 * otherwise move <i>where</i> to the right.
1211 *
1212 * @return the number of spaces we moved
1213 */
1214 private final int moveCursor(final int num) throws IOException {
1215 int where = num;
1216
1217 if ((buf.cursor == 0) && (where < 0)) {
1218 return 0;
1219 }
1220
1221 if ((buf.cursor == buf.buffer.length()) && (where > 0)) {
1222 return 0;
1223 }
1224
1225 if ((buf.cursor + where) < 0) {
1226 where = -buf.cursor;
1227 } else if ((buf.cursor + where) > buf.buffer.length()) {
1228 where = buf.buffer.length() - buf.cursor;
1229 }
1230
1231 moveInternal(where);
1232
1233 return where;
1234 }
1235
1236 /**
1237 * debug.
1238 *
1239 * @param str the message to issue.
1240 */
1241 public static void debug(final String str) {
1242 if (debugger != null) {
1243 debugger.println(str);
1244 debugger.flush();
1245 }
1246 }
1247
1248 /**
1249 * Move the cursor <i>where</i> characters, withough checking
1250 * the current buffer.
1251 *
1252 * @see #where
1253 *
1254 * @param where the number of characters to move to the right or left.
1255 */
1256 private final void moveInternal(final int where) throws IOException {
1257 // debug ("move cursor " + where + " ("
1258 // + buf.cursor + " => " + (buf.cursor + where) + ")");
1259 buf.cursor += where;
1260
1261 char c;
1262
1263 if (where < 0) {
1264 c = BACKSPACE;
1265 } else if (buf.cursor == 0) {
1266 return;
1267 } else {
1268 c = buf.buffer.charAt(buf.cursor - 1); // draw replacement
1269 }
1270
1271 // null character mask: don't output anything
1272 if (NULL_MASK.equals(mask)) {
1273 return;
1274 }
1275
1276 printCharacters(c, Math.abs(where));
1277 }
1278
1279 /**
1280 * Read a character from the console.
1281 *
1282 * @return the character, or -1 if an EOF is received.
1283 */
1284 public final int readVirtualKey() throws IOException {
1285 int c = terminal.readVirtualKey(in);
1286
1287 if (debugger != null) {
1288 debug("keystroke: " + c + "");
1289 }
1290
1291 // clear any echo characters
1292 clearEcho(c);
1293
1294 return c;
1295 }
1296
1297 public final int readCharacter(final char[] allowed)
1298 throws IOException {
1299 // if we restrict to a limited set and the current character
1300 // is not in the set, then try again.
1301 char c;
1302
1303 Arrays.sort(allowed); // always need to sort before binarySearch
1304
1305 while (Arrays.binarySearch(allowed, c = (char) readVirtualKey()) == -1);
1306
1307 return c;
1308 }
1309
1310 public void setHistory(final History history) {
1311 this.history = history;
1312 }
1313
1314 public History getHistory() {
1315 return this.history;
1316 }
1317
1318 public void setCompletionHandler
1319 (final CompletionHandler completionHandler) {
1320 this.completionHandler = completionHandler;
1321 }
1322
1323 public CompletionHandler getCompletionHandler() {
1324 return this.completionHandler;
1325 }
1326
1327 /**
1328 * <p>
1329 * Set the echo character. For example, to have "*" entered
1330 * when a password is typed:
1331 * </p>
1332 *
1333 * <pre>
1334 * myConsoleReader.setEchoCharacter (new Character ('*'));
1335 * </pre>
1336 *
1337 * <p>
1338 * Setting the character to <pre>null</pre> will restore normal
1339 * character echoing. Setting the character to
1340 * <pre>new Character (0)</pre> will cause nothing to be echoed.
1341 * </p>
1342 *
1343 * @param echoCharacter the character to echo to the console in
1344 * place of the typed character.
1345 */
1346 public void setEchoCharacter(final Character echoCharacter) {
1347 this.echoCharacter = echoCharacter;
1348 }
1349
1350 /**
1351 * Returns the echo character.
1352 */
1353 public Character getEchoCharacter() {
1354 return this.echoCharacter;
1355 }
1356
1357 /**
1358 * No-op for exceptions we want to silently consume.
1359 */
1360 private void consumeException(final Throwable e) {
1361 }
1362
1363 /**
1364 * Checks to see if the specified character is a delimiter. We
1365 * consider a character a delimiter if it is anything but a letter or
1366 * digit.
1367 *
1368 * @param c the character to test
1369 * @return true if it is a delimiter
1370 */
1371 private boolean isDelimiter(char c) {
1372 return !Character.isLetterOrDigit(c);
1373 }
1374
1375
1376 /**
1377 * Whether or not to add new commands to the history buffer.
1378 */
1379 public void setUseHistory(boolean useHistory) {
1380 this.useHistory = useHistory;
1381 }
1382
1383
1384 /**
1385 * Whether or not to add new commands to the history buffer.
1386 */
1387 public boolean getUseHistory() {
1388 return useHistory;
1389 }
1390 }