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