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