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