    001    /**
    002     *      jline - Java console input library
    003     *      Copyright (c) 2002, 2003, 2004, 2005, 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