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