1/*
2 * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
3 *
4 * This software is distributable under the BSD license. See the terms of the
5 * BSD license in the documentation provided with this software.
6 */
7package jline;
8
9import java.awt.*;
10import java.awt.datatransfer.*;
11import java.awt.event.ActionListener;
12
13import java.io.*;
14import java.util.*;
15import java.util.List;
16
17/**
18 * A reader for console applications. It supports custom tab-completion,
19 * saveable command history, and command line editing. On some platforms,
20 * platform-specific commands will need to be issued before the reader will
21 * function properly. See {@link Terminal#initializeTerminal} for convenience
22 * methods for issuing platform-specific setup commands.
23 *
24 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
25 */
26public class ConsoleReader implements ConsoleOperations {
27
28    final static int TAB_WIDTH = 4;
29    String prompt;
30    private boolean useHistory = true;
31    private boolean usePagination = false;
32    public static final String CR = System.getProperty("line.separator");
33    private static ResourceBundle loc = ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName());
34    /**
35     * Map that contains the operation name to keymay operation mapping.
36     */
37    public static SortedMap KEYMAP_NAMES;
38
39
40    static {
41        Map names = new TreeMap();
42
43        names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG));
44        names.put("MOVE_TO_END", new Short(MOVE_TO_END));
45        names.put("PREV_CHAR", new Short(PREV_CHAR));
46        names.put("NEWLINE", new Short(NEWLINE));
47        names.put("KILL_LINE", new Short(KILL_LINE));
48        names.put("PASTE", new Short(PASTE));
49        names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN));
50        names.put("NEXT_HISTORY", new Short(NEXT_HISTORY));
51        names.put("PREV_HISTORY", new Short(PREV_HISTORY));
52        names.put("START_OF_HISTORY", new Short(START_OF_HISTORY));
53        names.put("END_OF_HISTORY", new Short(END_OF_HISTORY));
54        names.put("REDISPLAY", new Short(REDISPLAY));
55        names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV));
56        names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD));
57        names.put("NEXT_CHAR", new Short(NEXT_CHAR));
58        names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR));
59        names.put("SEARCH_PREV", new Short(SEARCH_PREV));
60        names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR));
61        names.put("SEARCH_NEXT", new Short(SEARCH_NEXT));
62        names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD));
63        names.put("TO_END_WORD", new Short(TO_END_WORD));
64        names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV));
65        names.put("PASTE_PREV", new Short(PASTE_PREV));
66        names.put("REPLACE_MODE", new Short(REPLACE_MODE));
67        names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE));
68        names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR));
69        names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD));
70        names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR));
71        names.put("ADD", new Short(ADD));
72        names.put("PREV_WORD", new Short(PREV_WORD));
73        names.put("CHANGE_META", new Short(CHANGE_META));
74        names.put("DELETE_META", new Short(DELETE_META));
75        names.put("END_WORD", new Short(END_WORD));
76        names.put("NEXT_CHAR", new Short(NEXT_CHAR));
77        names.put("INSERT", new Short(INSERT));
78        names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT));
79        names.put("PASTE_NEXT", new Short(PASTE_NEXT));
80        names.put("REPLACE_CHAR", new Short(REPLACE_CHAR));
81        names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR));
82        names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR));
83        names.put("UNDO", new Short(UNDO));
84        names.put("NEXT_WORD", new Short(NEXT_WORD));
85        names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR));
86        names.put("CHANGE_CASE", new Short(CHANGE_CASE));
87        names.put("COMPLETE", new Short(COMPLETE));
88        names.put("EXIT", new Short(EXIT));
89        names.put("CLEAR_LINE", new Short(CLEAR_LINE));
90        names.put("ABORT", new Short(ABORT));
91
92        KEYMAP_NAMES = new TreeMap(Collections.unmodifiableMap(names));
93    }
94    /**
95     * The map for logical operations.
96     */
97    private final short[] keybindings;
98    /**
99     * 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}
1824