001    /**
002     * jline - Java console input library
003     * Copyright (c) 2002,2003 Marc Prud'hommeaux mwp1@cornell.edu
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:mwp1@cornell.edu">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 ("REPEAT_SEARCH_PREV",        new Short (REPEAT_SEARCH_PREV));
312                    names.put ("PASTE_PREV",                        new Short (PASTE_PREV));
313                    names.put ("REPLACE_MODE",                      new Short (REPLACE_MODE));
314                    names.put ("SUBSTITUTE_LINE",           new Short (SUBSTITUTE_LINE));
315                    names.put ("TO_PREV_CHAR",                      new Short (TO_PREV_CHAR));
316                    names.put ("NEXT_SPACE_WORD",           new Short (NEXT_SPACE_WORD));
317                    names.put ("DELETE_PREV_CHAR",          new Short (DELETE_PREV_CHAR));
318                    names.put ("ADD",                                       new Short (ADD));
319                    names.put ("PREV_WORD",                         new Short (PREV_WORD));
320                    names.put ("CHANGE_META",                       new Short (CHANGE_META));
321                    names.put ("DELETE_META",                       new Short (DELETE_META));
322                    names.put ("END_WORD",                          new Short (END_WORD));
323                    names.put ("NEXT_CHAR",                         new Short (NEXT_CHAR));
324                    names.put ("INSERT",                            new Short (INSERT));
325                    names.put ("REPEAT_SEARCH_NEXT",        new Short (REPEAT_SEARCH_NEXT));
326                    names.put ("PASTE_NEXT",                        new Short (PASTE_NEXT));
327                    names.put ("REPLACE_CHAR",                      new Short (REPLACE_CHAR));
328                    names.put ("SUBSTITUTE_CHAR",           new Short (SUBSTITUTE_CHAR));
329                    names.put ("TO_NEXT_CHAR",                      new Short (TO_NEXT_CHAR));
330                    names.put ("UNDO",                                      new Short (UNDO));
331                    names.put ("NEXT_WORD",                         new Short (NEXT_WORD));
332                    names.put ("DELETE_NEXT_CHAR",          new Short (DELETE_NEXT_CHAR));
333                    names.put ("CHANGE_CASE",                       new Short (CHANGE_CASE));
334                    names.put ("COMPLETE",                          new Short (COMPLETE));
335                    names.put ("EXIT",                                      new Short (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 mask = null;
357    
358    
359            /** 
360             *  The null mask.
361             */
362            private static final Character NULL_MASK = new Character ((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 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 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 val = (String)i.next ();
471                                    try
472                                    {
473                                            Short code = new Short (val);
474                                            String op = (String)p.getProperty (val);
475    
476                                            Short opval = (Short)KEYMAP_NAMES.get (op);
477    
478                                            if (opval != null)
479                                                    keybindings [code.shortValue ()] = opval.shortValue ();
480                                    }
481                                    catch (NumberFormatException 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 readLine ()
533                    throws IOException
534            {
535                    return readLine ((String)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 readLine (Character 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 getPrintableCharacters (char ch)
668            {
669                    StringBuffer sbuff = new StringBuffer ();
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 readLine (String 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 readLine (String prompt, Character 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 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 line = new StringBuffer ();
960    
961                    for (Iterator i = stuff.iterator (); i.hasNext (); )
962                    {
963                            String cur = (String)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 toPad, int len, StringBuffer 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 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 finishBuffer ()
1138            {
1139                    String 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 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 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 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 echoCharacter)
1592            {
1593                    this.echoCharacter = echoCharacter;
1594            }
1595    
1596    
1597            /** 
1598             *  Returns the echo character.
1599             */
1600            public Character getEchoCharacter ()
1601            {
1602                    return this.echoCharacter;
1603            }
1604    }
1605