KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jline > ConsoleReader


1 /**
2  * jline - Java console input library
3  * Copyright (c) 2002,2003 Marc Prud'hommeaux mwp1@cornell.edu
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19 package jline;
20
21 import java.io.*;
22 import java.text.*;
23 import java.util.*;
24
25 /**
26  * A reader for console applications. It supports custom tab-completion,
27  * saveable command history, and command line editing. On some
28  * platforms, platform-specific commands will need to be
29  * issued before the reader will function properly. See
30  * {@link Terminal#initializeTerminal} for convenience methods for
31  * issuing platform-specific setup commands.
32  * <p>
33  * <strong>TODO:</strong>
34  * <ul>
35  * <li>i18n</li>
36  * <li>minimize redundant redrawing of similar buffers</li>
37  * <li>obtain terminal width and height for columnation</li>
38  * <li>Enable some sort of pages for large candidate results</li>
39  * <li>Add ANSI color support for completors (e.g., directories in green)</li>
40  * <li>Implement mid-line tab-completion</li>
41  * </ul>
42  *
43  * @author <a HREF="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
44  */

45 public class ConsoleReader
46 {
47     String JavaDoc prompt;
48
49     public static final String JavaDoc CR = System.getProperty ("line.separator");
50
51     public static final char BACKSPACE = '\b';
52     public static final char RESET_LINE = '\r';
53     public static final char KEYBOARD_BELL = '\07';
54
55
56     public static final short ARROW_START = 27;
57     public static final short ARROW_PREFIX = 91;
58     public static final short ARROW_LEFT = 68;
59     public static final short ARROW_RIGHT = 67;
60     public static final short ARROW_UP = 65;
61     public static final short ARROW_DOWN = 66;
62
63
64     /**
65      * Logical constants for key operations.
66      */

67
68     /**
69      * Unknown operation.
70      */

71     public static final short UNKNOWN = -99;
72
73     /**
74      * Operation that moves to the beginning of the buffer.
75      */

76     public static final short MOVE_TO_BEG = -1;
77
78     /**
79      * Operation that moves to the end of the buffer.
80      */

81     public static final short MOVE_TO_END = -3;
82
83     /**
84      * Operation that moved to the previous character in the buffer.
85      */

86     public static final short PREV_CHAR = -4;
87
88     /**
89      * Operation that issues a newline.
90      */

91     public static final short NEWLINE = -6;
92
93     /**
94      * Operation that deletes the buffer from the current character to the end.
95      */

96     public static final short KILL_LINE = -7;
97
98     /**
99      * 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 JavaDoc (MOVE_TO_BEG));
294         names.put ("MOVE_TO_END", new Short JavaDoc (MOVE_TO_END));
295         names.put ("PREV_CHAR", new Short JavaDoc (PREV_CHAR));
296         names.put ("NEWLINE", new Short JavaDoc (NEWLINE));
297         names.put ("KILL_LINE", new Short JavaDoc (KILL_LINE));
298         names.put ("CLEAR_SCREEN", new Short JavaDoc (CLEAR_SCREEN));
299         names.put ("NEXT_HISTORY", new Short JavaDoc (NEXT_HISTORY));
300         names.put ("PREV_HISTORY", new Short JavaDoc (PREV_HISTORY));
301         names.put ("REDISPLAY", new Short JavaDoc (REDISPLAY));
302         names.put ("KILL_LINE_PREV", new Short JavaDoc (KILL_LINE_PREV));
303         names.put ("DELETE_PREV_WORD", new Short JavaDoc (DELETE_PREV_WORD));
304         names.put ("NEXT_CHAR", new Short JavaDoc (NEXT_CHAR));
305         names.put ("REPEAT_PREV_CHAR", new Short JavaDoc (REPEAT_PREV_CHAR));
306         names.put ("SEARCH_PREV", new Short JavaDoc (SEARCH_PREV));
307         names.put ("REPEAT_NEXT_CHAR", new Short JavaDoc (REPEAT_NEXT_CHAR));
308         names.put ("SEARCH_NEXT", new Short JavaDoc (SEARCH_NEXT));
309         names.put ("PREV_SPACE_WORD", new Short JavaDoc (PREV_SPACE_WORD));
310         names.put ("TO_END_WORD", new Short JavaDoc (TO_END_WORD));
311         names.put ("REPEAT_SEARCH_PREV", new Short JavaDoc (REPEAT_SEARCH_PREV));
312         names.put ("PASTE_PREV", new Short JavaDoc (PASTE_PREV));
313         names.put ("REPLACE_MODE", new Short JavaDoc (REPLACE_MODE));
314         names.put ("SUBSTITUTE_LINE", new Short JavaDoc (SUBSTITUTE_LINE));
315         names.put ("TO_PREV_CHAR", new Short JavaDoc (TO_PREV_CHAR));
316         names.put ("NEXT_SPACE_WORD", new Short JavaDoc (NEXT_SPACE_WORD));
317         names.put ("DELETE_PREV_CHAR", new Short JavaDoc (DELETE_PREV_CHAR));
318         names.put ("ADD", new Short JavaDoc (ADD));
319         names.put ("PREV_WORD", new Short JavaDoc (PREV_WORD));
320         names.put ("CHANGE_META", new Short JavaDoc (CHANGE_META));
321         names.put ("DELETE_META", new Short JavaDoc (DELETE_META));
322         names.put ("END_WORD", new Short JavaDoc (END_WORD));
323         names.put ("NEXT_CHAR", new Short JavaDoc (NEXT_CHAR));
324         names.put ("INSERT", new Short JavaDoc (INSERT));
325         names.put ("REPEAT_SEARCH_NEXT", new Short JavaDoc (REPEAT_SEARCH_NEXT));
326         names.put ("PASTE_NEXT", new Short JavaDoc (PASTE_NEXT));
327         names.put ("REPLACE_CHAR", new Short JavaDoc (REPLACE_CHAR));
328         names.put ("SUBSTITUTE_CHAR", new Short JavaDoc (SUBSTITUTE_CHAR));
329         names.put ("TO_NEXT_CHAR", new Short JavaDoc (TO_NEXT_CHAR));
330         names.put ("UNDO", new Short JavaDoc (UNDO));
331         names.put ("NEXT_WORD", new Short JavaDoc (NEXT_WORD));
332         names.put ("DELETE_NEXT_CHAR", new Short JavaDoc (DELETE_NEXT_CHAR));
333         names.put ("CHANGE_CASE", new Short JavaDoc (CHANGE_CASE));
334         names.put ("COMPLETE", new Short JavaDoc (COMPLETE));
335         names.put ("EXIT", new Short JavaDoc (EXIT));
336         
337         KEYMAP_NAMES = new TreeMap (Collections.unmodifiableMap (names));
338     }
339
340
341     /**
342      * The map for logical operations.
343      */

344     private final short [] keybindings;
345
346
347     /**
348      * If true, issue an audible keyboard bell when appropriate.
349      */

350     private boolean bellEnabled = true;
351
352
353     /**
354      * The current character mask.
355      */

356     private Character JavaDoc mask = null;
357
358
359     /**
360      * The null mask.
361      */

362     private static final Character JavaDoc NULL_MASK = new Character JavaDoc ((char)0);
363
364
365     /**
366      * The number of tab-completion candidates above which a warning
367      * will be prompted before showing all the candidates.
368      */

369     private int autoprintThreshhold = Integer.getInteger (
370         "jline.completion.threshold", 100).intValue (); // same default as bash
371

372
373     /**
374      * The Terminal to use.
375      */

376     private final Terminal terminal;
377
378
379     private CompletionHandler completionHandler
380         = new CandidateListCompletionHandler ();
381
382
383     InputStream in;
384     final Writer out;
385     final CursorBuffer buf = new CursorBuffer ();
386     static PrintWriter debugger;
387     History history = new History ();
388     final List completors = new LinkedList ();
389
390     private Character JavaDoc echoCharacter = null;
391
392
393     /**
394      * Create a new reader using {@link FileDescriptor#in} for input
395      * and {@link System#out} for output. {@link FileDescriptor#in} is
396      * used because it has a better chance of being unbuffered.
397      */

398     public ConsoleReader ()
399         throws IOException
400     {
401         this (new FileInputStream (FileDescriptor.in),
402             new PrintWriter (System.out));
403     }
404
405
406     /**
407      * Create a new reader using the specified {@link InputStream}
408      * for input and the specific writer for output, using the
409      * default keybindings resource.
410      */

411     public ConsoleReader (InputStream in, Writer out)
412         throws IOException
413     {
414         this (in, out, null);
415     }
416
417
418     public ConsoleReader (InputStream in, Writer out, InputStream bindings)
419         throws IOException
420     {
421         this (in, out, bindings, Terminal.getTerminal ());
422     }
423
424
425     /**
426      * Create a new reader.
427      *
428      * @param in the input
429      * @param out the output
430      * @param bindings the key bindings to use
431      * @param term the terminal to use
432      */

433     public ConsoleReader (InputStream in, Writer out, InputStream bindings,
434         Terminal term)
435         throws IOException
436     {
437         this.terminal = term;
438         setInput (in);
439         this.out = out;
440         if (bindings == null)
441         {
442             String JavaDoc bindingFile = System.getProperty ("jline.keybindings",
443                 new File (System.getProperty ("user.home",
444                     ".jlinebindings.properties")).getAbsolutePath ());
445
446             if (!(new File (bindingFile).isFile ()))
447                 bindings = ConsoleReader.class.getResourceAsStream (
448                     "keybindings.properties");
449             else
450                 bindings = new FileInputStream (new File (bindingFile));
451         }
452
453         this.keybindings = new short [Byte.MAX_VALUE * 2];
454
455         Arrays.fill (this.keybindings, UNKNOWN);
456
457         /**
458          * Loads the key bindings. Bindings file is in the format:
459          *
460          * keycode: operation name
461          */

462         if (bindings != null)
463         {
464             Properties p = new Properties ();
465             p.load (bindings);
466             bindings.close ();
467
468             for (Iterator i = p.keySet ().iterator (); i.hasNext (); )
469             {
470                 String JavaDoc val = (String JavaDoc)i.next ();
471                 try
472                 {
473                     Short JavaDoc code = new Short JavaDoc (val);
474                     String JavaDoc op = (String JavaDoc)p.getProperty (val);
475
476                     Short JavaDoc opval = (Short JavaDoc)KEYMAP_NAMES.get (op);
477
478                     if (opval != null)
479                         keybindings [code.shortValue ()] = opval.shortValue ();
480                 }
481                 catch (NumberFormatException JavaDoc nfe)
482                 {
483                 }
484             }
485         }
486
487
488         /**
489          * Perform unmodifiable bindings.
490          */

491         keybindings [ARROW_START] = ARROW_START;
492     }
493
494
495     public Terminal getTerminal ()
496     {
497         return this.terminal;
498     }
499
500
501
502     /**
503      * Set the stream for debugging. Development use only.
504      */

505     public void setDebug (PrintWriter debugger)
506     {
507         this.debugger = debugger;
508     }
509
510
511     /**
512      * Set the stream to be used for console input.
513      */

514     public void setInput (InputStream in)
515     {
516         this.in = in;
517     }
518
519
520     /**
521      * Returns the stream used for console input.
522      */

523     public InputStream getInput ()
524     {
525         return this.in;
526     }
527
528
529     /**
530      * Read the next line and return the contents of the buffer.
531      */

532     public String JavaDoc readLine ()
533         throws IOException
534     {
535         return readLine ((String JavaDoc)null);
536     }
537
538
539     /**
540      * Read the next line with the specified character mask. If null, then
541      * characters will be echoed. If 0, then no characters will be echoed.
542      */

543     public String JavaDoc readLine (Character JavaDoc mask)
544         throws IOException
545     {
546         return readLine (null, mask);
547     }
548
549
550     /**
551      * @param bellEnabled if true, enable audible keyboard bells if
552      * an alert is required.
553      */

554     public void setBellEnabled (boolean bellEnabled)
555     {
556         this.bellEnabled = bellEnabled;
557     }
558
559
560     /**
561      * @return true is audible keyboard bell is enabled.
562      */

563     public boolean getBellEnabled ()
564     {
565         return this.bellEnabled;
566     }
567
568
569     /**
570      * Query the terminal to find the current width;
571      *
572      * @see Terminal#getTerminalWidth
573      * @return the width of the current terminal.
574      */

575     public int getTermwidth ()
576     {
577         return Terminal.setupTerminal ().getTerminalWidth ();
578     }
579
580
581     /**
582      * Query the terminal to find the current width;
583      *
584      * @see Terminal#getTerminalheight
585      *
586      * @return the height of the current terminal.
587      */

588     public int getTermheight ()
589     {
590         return Terminal.setupTerminal ().getTerminalHeight ();
591     }
592
593
594     /**
595      * @param autoprintThreshhold the number of candidates to print
596      * without issuing a warning.
597      */

598     public void setAutoprintThreshhold (int autoprintThreshhold)
599     {
600         this.autoprintThreshhold = autoprintThreshhold;
601     }
602
603
604     /**
605      * @return the number of candidates to print without issing a warning.
606      */

607     public int getAutoprintThreshhold ()
608     {
609         return this.autoprintThreshhold;
610     }
611
612
613     int getKeyForAction (short logicalAction)
614     {
615         for (int i = 0; i < keybindings.length; i++)
616         {
617             if (keybindings [i] == logicalAction)
618             {
619                 return i;
620             }
621         }
622
623         return -1;
624     }
625
626
627     /**
628      * Clear the echoed characters for the specified character code.
629      */

630     int clearEcho (int c)
631         throws IOException
632     {
633         // if the terminal is not echoing, then just return...
634
if (!terminal.getEcho ())
635             return 0;
636
637         // otherwise, clear
638
int num = countEchoCharacters ((char)c);
639         back (num);
640         drawBuffer (num);
641
642         return num;
643     }
644
645
646     int countEchoCharacters (char c)
647     {
648         // tabs as special: we need to determine the number of spaces
649
// to cancel based on what out current cursor position is
650
if (c == 9)
651         {
652             int tabstop = 8; // will this ever be different?
653
int position = getCursorPosition ();
654             return tabstop - (position % tabstop);
655         }
656
657         return getPrintableCharacters (c).length ();
658     }
659
660
661     /**
662      * Return the number of characters that will be printed when the
663      * specified character is echoed to the screen. Adapted from
664      * cat by Torbjorn Granlund, as repeated in stty by
665      * David MacKenzie.
666      */

667     StringBuffer JavaDoc getPrintableCharacters (char ch)
668     {
669         StringBuffer JavaDoc sbuff = new StringBuffer JavaDoc ();
670         if (ch >= 32)
671         {
672             if (ch < 127)
673             {
674                 sbuff.append (ch);
675             }
676             else if (ch == 127)
677             {
678                 sbuff.append ('^');
679                 sbuff.append ('?');
680             }
681             else
682             {
683                 sbuff.append ('M');
684                 sbuff.append ('-');
685                 if (ch >= 128 + 32)
686                 {
687                     if (ch < 128 + 127)
688                     {
689                         sbuff.append ((char)(ch - 128));
690                     }
691                     else
692                     {
693                         sbuff.append ('^');
694                         sbuff.append ('?');
695                     }
696                 }
697                 else
698                 {
699                     sbuff.append ('^');
700                     sbuff.append ((char)(ch - 128 + 64));
701                 }
702             }
703         }
704         else
705         {
706             sbuff.append ('^');
707             sbuff.append ((char)(ch + 64));
708         }
709
710         return sbuff;
711     }
712
713
714     int getCursorPosition ()
715     {
716         // FIXME: does not handle anything but a line with a prompt
717
return (prompt == null ? 0 : prompt.length ())
718             + buf.cursor; // absolute position
719
}
720
721
722     public String JavaDoc readLine (String JavaDoc prompt)
723         throws IOException
724     {
725         return readLine (prompt, null);
726     }
727
728
729     /**
730      * Read a line from the <i>in</i> {@link InputStream}, and
731      * return the line (without any trailing newlines).
732      *
733      * @param prompt the prompt to issue to the console, may be null.
734      * @return a line that is read from the terminal, or null if there
735      * was null input (e.g., <i>CTRL-D</i> was pressed).
736      */

737     public String JavaDoc readLine (String JavaDoc prompt, Character JavaDoc mask)
738         throws IOException
739     {
740         this.mask = mask;
741         this.prompt = prompt;
742
743         if (prompt != null && prompt.length () > 0)
744         {
745             out.write (prompt);
746             out.flush ();
747         }
748
749         // if the terminal is unsupported, just use plain-java reading
750
if (!terminal.isSupported ())
751             return new BufferedReader (new InputStreamReader (in)).readLine ();
752
753         int c;
754
755         while (true)
756         {
757             if ((c = readCharacter ()) == -1)
758                 return null;
759
760             boolean success = true;
761
762             // extract the appropriate key binding
763
short code = keybindings [c];
764
765             if (debugger != null)
766                 debug (" translated: " + (int)c + ": " + code);
767
768             switch (code)
769             {
770                 case EXIT: // ctrl-d
771
if (buf.buffer.length () == 0)
772                         return null;
773                 case COMPLETE: // tab
774
success = complete ();
775                     break;
776                 case MOVE_TO_BEG:
777                     success = setCursorPosition (0);
778                     break;
779                 case KILL_LINE: // CTRL-K
780
success = killLine ();
781                     break;
782                 case KILL_LINE_PREV: // CTRL-U
783
success = resetLine ();
784                     break;
785                 case ARROW_START:
786                     // debug ("ARROW_START");
787

788                     switch (c = readCharacter ())
789                     {
790                         case ARROW_PREFIX:
791                             // debug ("ARROW_PREFIX");
792

793                             switch (c = readCharacter ())
794                             {
795                                 case ARROW_LEFT: // left arrow
796
// debug ("LEFT");
797
success = moveCursor (-1) != 0;
798                                     break;
799                                 case ARROW_RIGHT: // right arrow
800
// debug ("RIGHT");
801
success = moveCursor (1) != 0;
802                                     break;
803                                 case ARROW_UP: // up arrow
804
// debug ("UP");
805
success = moveHistory (false);
806                                     break;
807                                 case ARROW_DOWN: // down arrow
808
// debug ("DOWN");
809
success = moveHistory (true);
810                                     break;
811                                 default:
812                                     break;
813
814                             }
815                             break;
816                         default:
817                             break;
818                     }
819                     break;
820                 case NEWLINE: // enter
821
printNewline (); // output newline
822
return finishBuffer ();
823                 case DELETE_PREV_CHAR: // backspace
824
success = backspace ();
825                     break;
826                 case MOVE_TO_END:
827                     success = moveToEnd ();
828                     break;
829                 case PREV_CHAR:
830                     success = moveCursor (-1) != 0;
831                     break;
832                 case NEXT_CHAR:
833                     success = moveCursor (1) != 0;
834                     break;
835                 case NEXT_HISTORY:
836                     success = moveHistory (true);
837                     break;
838                 case PREV_HISTORY:
839                     success = moveHistory (false);
840                     break;
841                 case REDISPLAY:
842                     break;
843                 case DELETE_PREV_WORD:
844                     success = deletePreviousWord ();
845                     break;
846                 case PREV_WORD:
847                     success = previousWord ();
848                     break;
849
850                 case UNKNOWN:
851                 default:
852                     putChar (c, true);
853             }
854
855             if (!(success))
856                 beep ();
857
858             flushConsole ();
859         }
860     }
861
862
863     /**
864      * Move up or down the history tree.
865      *
866      * @param direction less than 0 to move up the tree, down otherwise
867      */

868     private final boolean moveHistory (boolean next)
869         throws IOException
870     {
871         if (next && !history.next ())
872            return false;
873         else if (!next && !history.previous ())
874             return false;
875
876         setBuffer (history.current ());
877         return true;
878     }
879
880
881     /**
882      * Kill the buffer ahead of the current cursor position.
883      *
884      * @return true if successful
885      */

886     public boolean killLine ()
887         throws IOException
888     {
889         int cp = buf.cursor;
890         int len = buf.buffer.length ();
891         if (cp >= len)
892             return false;
893
894         int num = buf.buffer.length () - cp;
895         clearAhead (num);
896         for (int i = 0; i < num; i++)
897             buf.buffer.deleteCharAt (len - i - 1);
898         return true;
899     }
900
901
902     /**
903      * Use the completors to modify the buffer with the
904      * appropriate completions.
905      *
906      * @return true if successful
907      */

908     private final boolean complete ()
909         throws IOException
910     {
911         // debug ("tab for (" + buf + ")");
912

913         if (completors.size () == 0)
914             return false;
915
916         List candidates = new LinkedList ();
917         String JavaDoc bufstr = buf.buffer.toString ();
918         int cursor = buf.cursor;
919
920         int position = -1;
921
922         for (Iterator i = completors.iterator (); i.hasNext (); )
923         {
924             Completor comp = (Completor)i.next ();
925             if ((position = comp.complete (bufstr, cursor, candidates)) != -1)
926                 break;
927         }
928
929         // no candidates? Fail.
930
if (candidates.size () == 0)
931             return false;
932
933         return completionHandler.complete (this, candidates, position);
934     }
935
936
937     public CursorBuffer getCursorBuffer ()
938     {
939         return buf;
940     }
941
942
943     /**
944      * Output the specified {@link Collection} in proper columns.
945      *
946      * @param stuff the stuff to print
947      */

948     public void printColumns (Collection stuff)
949         throws IOException
950     {
951         if (stuff == null || stuff.size () == 0)
952             return;
953
954         int width = getTermwidth ();
955         int maxwidth = 0;
956         for (Iterator i = stuff.iterator (); i.hasNext ();
957             maxwidth = Math.max (maxwidth, i.next ().toString ().length ()));
958
959         StringBuffer JavaDoc line = new StringBuffer JavaDoc ();
960
961         for (Iterator i = stuff.iterator (); i.hasNext (); )
962         {
963             String JavaDoc cur = (String JavaDoc)i.next ();
964
965             if (line.length () + maxwidth > width)
966             {
967                 printString (line.toString ().trim ());
968                 printNewline ();
969                 line.setLength (0);
970             }
971
972             pad (cur, maxwidth + 3, line);
973         }
974
975         if (line.length () > 0)
976         {
977             printString (line.toString ().trim ());
978             printNewline ();
979             line.setLength (0);
980         }
981     }
982
983
984     /**
985      * Append <i>toPad</i> to the specified <i>appendTo</i>, as
986      * well as (<i>toPad.length () - len</i>) spaces.
987      *
988      * @param toPad the {@link String} to pad
989      * @param len the target length
990      * @param appendTo the {@link StringBuffer} to which to append the
991      * padded {@link String}.
992      */

993     private final void pad (String JavaDoc toPad, int len, StringBuffer JavaDoc appendTo)
994     {
995         appendTo.append (toPad);
996         for (int i = 0; i < (len - toPad.length ());
997             i++, appendTo.append (' '));
998     }
999
1000
1001    /**
1002     * Add the specified {@link Completor} to the list of handlers
1003     * for tab-completion.
1004     *
1005     * @param completor the {@link Completor} to add
1006     * @return true if it was successfully added
1007     */

1008    public boolean addCompletor (Completor completor)
1009    {
1010        return completors.add (completor);
1011    }
1012
1013
1014    /**
1015     * Remove the specified {@link Completor} from the list of handlers
1016     * for tab-completion.
1017     *
1018     * @param completor the {@link Completor} to remove
1019     * @return true if it was successfully removed
1020     */

1021    public boolean removeCompletor (Completor completor)
1022    {
1023        return completors.remove (completor);
1024    }
1025
1026
1027    /**
1028     * Returns an unmodifiable list of all the completors.
1029     */

1030    public Collection getCompletors ()
1031    {
1032        return Collections.unmodifiableList (completors);
1033    }
1034
1035
1036    /**
1037     * Erase the current line.
1038     *
1039     * @return false if we failed (e.g., the buffer was empty)
1040     */

1041    final boolean resetLine ()
1042        throws IOException
1043    {
1044        if (buf.cursor == 0)
1045            return false;
1046
1047        backspaceAll ();
1048
1049        return true;
1050    }
1051
1052
1053    /**
1054     * Move the cursor position to the specified absolute index.
1055     */

1056    public final boolean setCursorPosition (int position)
1057        throws IOException
1058    {
1059        return moveCursor (position - buf.cursor) != 0;
1060    }
1061
1062
1063    /**
1064     * Set the current buffer's content to the specified
1065     * {@link String}. The visual console will be modified
1066     * to show the current buffer.
1067     *
1068     * @param buffer the new contents of the buffer.
1069     */

1070    private final void setBuffer (String JavaDoc buffer)
1071        throws IOException
1072    {
1073        // don't bother modifying it if it is unchanged
1074
if (buffer.equals (buf.buffer.toString ()))
1075            return;
1076
1077        // obtain the difference between the current buffer and the new one
1078
int sameIndex = 0;
1079        for (int i = 0, l1 = buffer.length (), l2 = buf.buffer.length ();
1080            i < l1 && i < l2; i++)
1081        {
1082            if (buffer.charAt (i) == buf.buffer.charAt (i))
1083                sameIndex++;
1084            else
1085                break;
1086        }
1087
1088        int diff = buf.buffer.length () - sameIndex;
1089
1090        backspace (diff); // go back for the differences
1091
killLine (); // clear to the end of the line
1092
buf.buffer.setLength (sameIndex); // the new length
1093
putString (buffer.substring (sameIndex)); // append the differences
1094
}
1095
1096
1097    /**
1098     * Clear the line and redraw it.
1099     */

1100    public final void redrawLine ()
1101        throws IOException
1102    {
1103        printCharacter (RESET_LINE);
1104        flushConsole ();
1105        drawLine ();
1106    }
1107
1108
1109    /**
1110     * Output put the prompt + the current buffer
1111     */

1112    public final void drawLine ()
1113        throws IOException
1114    {
1115        if (prompt != null)
1116            printString (prompt);
1117        printString (buf.buffer.toString ());
1118    }
1119
1120
1121    /**
1122     * Output a platform-dependant newline.
1123     */

1124    public final void printNewline ()
1125        throws IOException
1126    {
1127        printString (CR);
1128        flushConsole ();
1129    }
1130
1131
1132    /**
1133     * Clear the buffer and add its contents to the history.
1134     *
1135     * @return the former contents of the buffer.
1136     */

1137    final String JavaDoc finishBuffer ()
1138    {
1139        String JavaDoc str = buf.buffer.toString ();
1140
1141        // we only add it to the history if the buffer is not empty
1142
if (str.length () > 0)
1143            history.addToHistory (str);
1144
1145        history.moveToEnd ();
1146
1147        buf.buffer.setLength (0);
1148        buf.cursor = 0;
1149        return str;
1150    }
1151
1152
1153    /**
1154     * Write out the specified string to the buffer and the
1155     * output stream.
1156     */

1157    public final void putString (String JavaDoc str)
1158        throws IOException
1159    {
1160        buf.insert (str);
1161        printString (str);
1162        drawBuffer ();
1163    }
1164
1165
1166    /**
1167     * Output the specified string to the output stream (but not the
1168     * buffer).
1169     */

1170    public final void printString (String JavaDoc str)
1171        throws IOException
1172    {
1173        printCharacters (str.toCharArray ());
1174    }
1175
1176
1177    /**
1178     * Output the specified character, both to the buffer
1179     * and the output stream.
1180     */

1181    private final void putChar (int c, boolean print)
1182        throws IOException
1183    {
1184        buf.insert ((char)c);
1185
1186        if (print)
1187        {
1188            // no masking...
1189
if (mask == null)
1190            {
1191                printCharacter (c);
1192            }
1193            // null mask: don't print anything...
1194
else if (mask.charValue () == 0)
1195            {
1196            }
1197            // otherwise print the mask...
1198
else
1199            {
1200                printCharacter (mask.charValue ());
1201            }
1202            drawBuffer ();
1203        }
1204    }
1205
1206
1207    /**
1208     * Redraw the rest of the buffer from the cursor onwards. This
1209     * is necessary for inserting text into the buffer.
1210     *
1211     * @param clear the number of characters to clear after the
1212     * end of the buffer
1213     */

1214    private final void drawBuffer (int clear)
1215        throws IOException
1216    {
1217        // debug ("drawBuffer: " + clear);
1218

1219        char [] chars = buf.buffer.substring (buf.cursor).toCharArray ();
1220        printCharacters (chars);
1221
1222        clearAhead (clear);
1223        back (chars.length);
1224        flushConsole ();
1225    }
1226
1227
1228    /**
1229     * Redraw the rest of the buffer from the cursor onwards. This
1230     * is necessary for inserting text into the buffer.
1231     */

1232    private final void drawBuffer ()
1233        throws IOException
1234    {
1235        drawBuffer (0);
1236    }
1237
1238
1239    /**
1240     * Clear ahead the specified number of characters
1241     * without moving the cursor.
1242     */

1243    private final void clearAhead (int num)
1244        throws IOException
1245    {
1246        if (num == 0)
1247            return;
1248
1249        // debug ("clearAhead: " + num);
1250

1251        // print blank extra characters
1252
printCharacters (' ', num);
1253
1254        // we need to flush here so a "clever" console
1255
// doesn't just ignore the redundancy of a space followed by
1256
// a backspace.
1257
flushConsole ();
1258
1259        // reset the visual cursor
1260
back (num);
1261
1262        flushConsole ();
1263    }
1264
1265
1266    /**
1267     * Move the visual cursor backwards without modifying the
1268     * buffer cursor.
1269     */

1270    private final void back (int num)
1271        throws IOException
1272    {
1273        printCharacters (BACKSPACE, num);
1274        flushConsole ();
1275    }
1276
1277
1278    /**
1279     * Issue an audible keyboard bell, if
1280     * {@link #getBellEnabled} return true.
1281     */

1282    public final void beep ()
1283        throws IOException
1284    {
1285        if (!(getBellEnabled ()))
1286            return;
1287
1288        printCharacter (KEYBOARD_BELL);
1289        // need to flush so the console actually beeps
1290
flushConsole ();
1291    }
1292
1293
1294    /**
1295     * Output the specified character to the output stream
1296     * without manipulating the current buffer.
1297     */

1298    private final void printCharacter (int c)
1299        throws IOException
1300    {
1301        out.write (c);
1302    }
1303
1304
1305    /**
1306     * Output the specified characters to the output stream
1307     * without manipulating the current buffer.
1308     */

1309    private final void printCharacters (char [] c)
1310        throws IOException
1311    {
1312        out.write (c);
1313    }
1314
1315
1316    private final void printCharacters (char c, int num)
1317        throws IOException
1318    {
1319        if (num == 1)
1320        {
1321            printCharacter (c);
1322        }
1323        else
1324        {
1325            char [] chars = new char [num];
1326            Arrays.fill (chars, c);
1327            printCharacters (chars);
1328        }
1329    }
1330
1331
1332    /**
1333     * Flush the console output stream. This is important for
1334     * printout out single characters (like a backspace or keyboard)
1335     * that we want the console to handle immedately.
1336     */

1337    public final void flushConsole ()
1338        throws IOException
1339    {
1340        out.flush ();
1341    }
1342
1343
1344    private final int backspaceAll ()
1345        throws IOException
1346    {
1347        return backspace (Integer.MAX_VALUE);
1348    }
1349
1350
1351    /**
1352     * Issue <code>num</code> backspaces.
1353     *
1354     * @return the number of characters backed up
1355     */

1356    private final int backspace (int num)
1357        throws IOException
1358    {
1359        if (buf.cursor == 0)
1360            return 0;
1361
1362        int count = 0;
1363
1364        count = moveCursor (-1 * num) * -1;
1365        // debug ("Deleting from " + buf.cursor + " for " + count);
1366

1367        buf.buffer.delete (buf.cursor, buf.cursor + count);
1368        drawBuffer (count);
1369
1370        return count;
1371    }
1372
1373
1374    /**
1375     * Issue a backspace.
1376     *
1377     * @return true if successful
1378     */

1379    public final boolean backspace ()
1380        throws IOException
1381    {
1382        return backspace (1) == 1;
1383    }
1384
1385
1386    private final boolean moveToEnd ()
1387        throws IOException
1388    {
1389        if (moveCursor (1) == 0)
1390            return false;
1391
1392        while (moveCursor (1) != 0);
1393
1394        return true;
1395    }
1396
1397
1398    /**
1399     * Delete the character at the current position and
1400     * redraw the remainder of the buffer.
1401     */

1402    private final boolean deleteCurrentCharacter ()
1403        throws IOException
1404    {
1405        buf.buffer.deleteCharAt (buf.cursor);
1406        drawBuffer (1);
1407        return true;
1408    }
1409
1410
1411    private final boolean previousWord ()
1412        throws IOException
1413    {
1414        while (Character.isWhitespace (buf.current ()) && moveCursor (-1)!= 0);
1415        while (!Character.isWhitespace (buf.current ()) && moveCursor (-1)!= 0);
1416
1417        return true;
1418    }
1419
1420
1421    private final boolean deletePreviousWord ()
1422        throws IOException
1423    {
1424        while (Character.isWhitespace (buf.current ()) && backspace ());
1425        while (!Character.isWhitespace (buf.current ()) && backspace ());
1426
1427        return true;
1428    }
1429
1430
1431    /**
1432     * Move the cursor <i>where</i> characters.
1433     *
1434     * @param where if less than 0, move abs(<i>where</i>) to the left,
1435     * otherwise move <i>where</i> to the right.
1436     *
1437     * @return the number of spaces we moved
1438     */

1439    private final int moveCursor (int where)
1440        throws IOException
1441    {
1442        if (buf.cursor == 0 && where < 0)
1443            return 0;
1444
1445        if (buf.cursor == buf.buffer.length () && where > 0)
1446            return 0;
1447
1448        if (buf.cursor + where < 0)
1449            where = -buf.cursor;
1450        else if (buf.cursor + where > buf.buffer.length ())
1451            where = buf.buffer.length () - buf.cursor;
1452
1453        moveInternal (where);
1454        return where;
1455    }
1456
1457
1458    /**
1459     * debug.
1460     *
1461     * @param str the message to issue.
1462     */

1463    public static void debug (String JavaDoc str)
1464    {
1465        if (debugger != null)
1466        {
1467            debugger.println (str);
1468            debugger.flush ();
1469        }
1470    }
1471
1472
1473    /**
1474     * Move the cursor <i>where</i> characters, withough checking
1475     * the current buffer.
1476     *
1477     * @see #where
1478     *
1479     * @param where the number of characters to move to the right or left.
1480     */

1481    private final void moveInternal (int where)
1482        throws IOException
1483    {
1484        // debug ("move cursor " + where + " ("
1485
// + buf.cursor + " => " + (buf.cursor + where) + ")");
1486

1487        buf.cursor += where;
1488
1489        char c;
1490
1491        if (where < 0)
1492        {
1493            c = BACKSPACE;
1494        }
1495        else if (buf.cursor == 0)
1496        {
1497            return;
1498        }
1499        else
1500        {
1501            c = buf.buffer.charAt (buf.cursor - 1); // draw replacement
1502
}
1503
1504        // null character mask: don't output anything
1505
if (NULL_MASK.equals (mask))
1506            return;
1507
1508        printCharacters (c, Math.abs (where));
1509    }
1510
1511
1512    /**
1513     * Read a character from the console.
1514     *
1515     * @return the character, or -1 if an EOF is received.
1516     */

1517    public final int readCharacter ()
1518        throws IOException
1519    {
1520        int c = in.read ();
1521
1522        if (debugger != null)
1523            debug ("keystroke: " + c + "");
1524
1525        // clear any echo characters
1526
clearEcho (c);
1527
1528        return c;
1529    }
1530
1531
1532    public final int readCharacter (char[] allowed)
1533        throws IOException
1534    {
1535
1536        // if we restrict to a limited set and the current character
1537
// is not in the set, then try again.
1538
char c;
1539
1540        Arrays.sort (allowed); // always need to sort before binarySearch
1541
while (Arrays.binarySearch (allowed, c = (char)readCharacter ()) == -1);
1542
1543        return c;
1544    }
1545
1546
1547    public void setHistory (History history)
1548    {
1549        this.history = history;
1550    }
1551
1552
1553    public History getHistory ()
1554    {
1555        return this.history;
1556    }
1557
1558
1559    public void setCompletionHandler (CompletionHandler completionHandler)
1560    {
1561        this.completionHandler = completionHandler;
1562    }
1563
1564
1565    public CompletionHandler getCompletionHandler ()
1566    {
1567        return this.completionHandler;
1568    }
1569
1570
1571
1572    /**
1573     * <p>
1574     * Set the echo character. For example, to have "*" entered
1575     * when a password is typed:
1576     * </p>
1577     *
1578     * <pre>
1579     * myConsoleReader.setEchoCharacter (new Character ('*'));
1580     * </pre>
1581     *
1582     * <p>
1583     * Setting the character to <pre>null</pre> will restore normal
1584     * character echoing. Setting the character to
1585     * <pre>new Character (0)</pre> will cause nothing to be echoed.
1586     * </p>
1587     *
1588     * @param echoCharacter the character to echo to the console in
1589     * place of the typed character.
1590     */

1591    public void setEchoCharacter (Character JavaDoc echoCharacter)
1592    {
1593        this.echoCharacter = echoCharacter;
1594    }
1595
1596
1597    /**
1598     * Returns the echo character.
1599     */

1600    public Character JavaDoc getEchoCharacter ()
1601    {
1602        return this.echoCharacter;
1603    }
1604}
1605
1606
Popular Tags