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