1/*
2 * Copyright (c) 2002-2012, the original author or authors.
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 *
7 * http://www.opensource.org/licenses/bsd-license.php
8 */
9package jdk.internal.jline.console;
10
11//import java.awt.*;
12//import java.awt.datatransfer.Clipboard;
13//import java.awt.datatransfer.DataFlavor;
14//import java.awt.datatransfer.Transferable;
15//import java.awt.datatransfer.UnsupportedFlavorException;
16//import java.awt.event.ActionListener;
17//import java.io.BufferedReader;
18import java.io.ByteArrayInputStream;
19//import java.io.ByteArrayOutputStream;
20import java.io.File;
21import java.io.FileDescriptor;
22import java.io.FileInputStream;
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.OutputStream;
26import java.io.OutputStreamWriter;
27import java.io.Reader;
28import java.io.Writer;
29import java.lang.reflect.InvocationTargetException;
30import java.lang.reflect.Method;
31import java.net.URL;
32import java.util.Arrays;
33import java.util.Collection;
34import java.util.Collections;
35//import java.util.HashMap;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.ListIterator;
39//import java.util.Map;
40import java.util.ResourceBundle;
41import java.util.Stack;
42import java.util.regex.Matcher;
43import java.util.regex.Pattern;
44
45import jdk.internal.jline.Terminal;
46import jdk.internal.jline.TerminalFactory;
47import jdk.internal.jline.UnixTerminal;
48import jdk.internal.jline.console.completer.CandidateListCompletionHandler;
49import jdk.internal.jline.console.completer.Completer;
50import jdk.internal.jline.console.completer.CompletionHandler;
51import jdk.internal.jline.console.history.History;
52import jdk.internal.jline.console.history.MemoryHistory;
53import jdk.internal.jline.internal.Configuration;
54import jdk.internal.jline.internal.InputStreamReader;
55import jdk.internal.jline.internal.Log;
56import jdk.internal.jline.internal.NonBlockingInputStream;
57import jdk.internal.jline.internal.Nullable;
58import jdk.internal.jline.internal.Urls;
59//import org.fusesource.jansi.AnsiOutputStream;
60
61import static jdk.internal.jline.internal.Preconditions.checkNotNull;
62
63/**
64 * A reader for console applications. It supports custom tab-completion,
65 * saveable command history, and command line editing. On some platforms,
66 * platform-specific commands will need to be issued before the reader will
67 * function properly. See {@link jline.Terminal#init} for convenience
68 * methods for issuing platform-specific setup commands.
69 *
70 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
71 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
72 * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
73 */
74public class ConsoleReader
75{
76    public static final String JLINE_NOBELL = "jline.nobell";
77
78    public static final String JLINE_ESC_TIMEOUT = "jline.esc.timeout";
79
80    public static final String JLINE_INPUTRC = "jline.inputrc";
81
82    public static final String INPUT_RC = ".inputrc";
83
84    public static final String DEFAULT_INPUT_RC = "/etc/inputrc";
85
86    public static final char BACKSPACE = '\b';
87
88    public static final char RESET_LINE = '\r';
89
90    public static final char KEYBOARD_BELL = '\07';
91
92    public static final char NULL_MASK = 0;
93
94    public static final int TAB_WIDTH = 4;
95
96    private static final ResourceBundle
97        resources = ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName());
98
99    private final Terminal terminal;
100
101    private final Writer out;
102
103    private final CursorBuffer buf = new CursorBuffer();
104
105    private String prompt;
106    private int    promptLen;
107
108    private boolean expandEvents = true;
109
110    private boolean bellEnabled = !Configuration.getBoolean(JLINE_NOBELL, true);
111
112    private boolean handleUserInterrupt = false;
113
114    private Character mask;
115
116    private Character echoCharacter;
117
118    private StringBuffer searchTerm = null;
119
120    private String previousSearchTerm = "";
121
122    private int searchIndex = -1;
123
124    private int parenBlinkTimeout = 500;
125
126    /*
127     * The reader and the nonBlockingInput go hand-in-hand.  The reader wraps
128     * the nonBlockingInput, but we have to retain a handle to it so that
129     * we can shut down its blocking read thread when we go away.
130     */
131    private NonBlockingInputStream in;
132    private long                   escapeTimeout;
133    private Reader                 reader;
134
135    /*
136     * TODO: Please read the comments about this in setInput(), but this needs
137     * to be done away with.
138     */
139    private boolean                isUnitTestInput;
140
141    /**
142     * Last character searched for with a vi character search
143     */
144    private char  charSearchChar = 0;           // Character to search for
145    private char  charSearchLastInvokeChar = 0; // Most recent invocation key
146    private char  charSearchFirstInvokeChar = 0;// First character that invoked
147
148    /**
149     * The vi yank buffer
150     */
151    private String yankBuffer = "";
152
153    private KillRing killRing = new KillRing();
154
155    private String encoding;
156
157    private boolean recording;
158
159    private String macro = "";
160
161    private String appName;
162
163    private URL inputrcUrl;
164
165    private ConsoleKeys consoleKeys;
166
167    private String commentBegin = null;
168
169    private boolean skipLF = false;
170
171    /**
172     * Set to true if the reader should attempt to detect copy-n-paste. The
173     * effect of this that an attempt is made to detect if tab is quickly
174     * followed by another character, then it is assumed that the tab was
175     * a literal tab as part of a copy-and-paste operation and is inserted as
176     * such.
177     */
178    private boolean copyPasteDetection = false;
179
180    /*
181     * Current internal state of the line reader
182     */
183    private State   state = State.NORMAL;
184
185    /**
186     * Possible states in which the current readline operation may be in.
187     */
188    private static enum State {
189        /**
190         * The user is just typing away
191         */
192        NORMAL,
193        /**
194         * In the middle of a emacs seach
195         */
196        SEARCH,
197        FORWARD_SEARCH,
198        /**
199         * VI "yank-to" operation ("y" during move mode)
200         */
201        VI_YANK_TO,
202        /**
203         * VI "delete-to" operation ("d" during move mode)
204         */
205        VI_DELETE_TO,
206        /**
207         * VI "change-to" operation ("c" during move mode)
208         */
209        VI_CHANGE_TO
210    }
211
212    public ConsoleReader() throws IOException {
213        this(null, new FileInputStream(FileDescriptor.in), System.out, null);
214    }
215
216    public ConsoleReader(final InputStream in, final OutputStream out) throws IOException {
217        this(null, in, out, null);
218    }
219
220    public ConsoleReader(final InputStream in, final OutputStream out, final Terminal term) throws IOException {
221        this(null, in, out, term);
222    }
223
224    public ConsoleReader(final @Nullable String appName, final InputStream in, final OutputStream out, final @Nullable Terminal term) throws IOException {
225        this(appName, in, out, term, null);
226    }
227
228    public ConsoleReader(final @Nullable String appName, final InputStream in, final OutputStream out, final @Nullable Terminal term, final @Nullable String encoding)
229        throws IOException
230    {
231        this.appName = appName != null ? appName : "JLine";
232        this.encoding = encoding != null ? encoding : Configuration.getEncoding();
233        this.terminal = term != null ? term : TerminalFactory.get();
234        String outEncoding = terminal.getOutputEncoding() != null? terminal.getOutputEncoding() : this.encoding;
235        this.out = new OutputStreamWriter(terminal.wrapOutIfNeeded(out), outEncoding);
236        setInput( in );
237
238        this.inputrcUrl = getInputRc();
239
240        consoleKeys = new ConsoleKeys(this.appName, inputrcUrl);
241    }
242
243    private URL getInputRc() throws IOException {
244        String path = Configuration.getString(JLINE_INPUTRC);
245        if (path == null) {
246            File f = new File(Configuration.getUserHome(), INPUT_RC);
247            if (!f.exists()) {
248                f = new File(DEFAULT_INPUT_RC);
249            }
250            return f.toURI().toURL();
251        } else {
252            return Urls.create(path);
253        }
254    }
255
256    public KeyMap getKeys() {
257        return consoleKeys.getKeys();
258    }
259
260    void setInput(final InputStream in) throws IOException {
261        this.escapeTimeout = Configuration.getLong(JLINE_ESC_TIMEOUT, 100);
262        /*
263         * This is gross and here is how to fix it. In getCurrentPosition()
264         * and getCurrentAnsiRow(), the logic is disabled when running unit
265         * tests and the fact that it is a unit test is determined by knowing
266         * if the original input stream was a ByteArrayInputStream. So, this
267         * is our test to do this.  What SHOULD happen is that the unit
268         * tests should pass in a terminal that is appropriately configured
269         * such that whatever behavior they expect to happen (or not happen)
270         * happens (or doesn't).
271         *
272         * So, TODO, get rid of this and fix the unit tests.
273         */
274        this.isUnitTestInput = in instanceof ByteArrayInputStream;
275        boolean nonBlockingEnabled =
276               escapeTimeout > 0L
277            && terminal.isSupported()
278            && in != null;
279
280        /*
281         * If we had a non-blocking thread already going, then shut it down
282         * and start a new one.
283         */
284        if (this.in != null) {
285            this.in.shutdown();
286        }
287
288        final InputStream wrapped = terminal.wrapInIfNeeded( in );
289
290        this.in = new NonBlockingInputStream(wrapped, nonBlockingEnabled);
291        this.reader = new InputStreamReader( this.in, encoding );
292    }
293
294    /**
295     * Shuts the console reader down.  This method should be called when you
296     * have completed using the reader as it shuts down and cleans up resources
297     * that would otherwise be "leaked".
298     */
299    public void shutdown() {
300        if (in != null) {
301            in.shutdown();
302        }
303    }
304
305    /**
306     * Shuts down the ConsoleReader if the JVM attempts to clean it up.
307     */
308    @Override
309    @SuppressWarnings("deprecation")
310    protected void finalize() throws Throwable {
311        try {
312            shutdown();
313        }
314        finally {
315            super.finalize();
316        }
317    }
318
319    public InputStream getInput() {
320        return in;
321    }
322
323    public Writer getOutput() {
324        return out;
325    }
326
327    public Terminal getTerminal() {
328        return terminal;
329    }
330
331    public CursorBuffer getCursorBuffer() {
332        return buf;
333    }
334
335    public void setExpandEvents(final boolean expand) {
336        this.expandEvents = expand;
337    }
338
339    public boolean getExpandEvents() {
340        return expandEvents;
341    }
342
343    /**
344     * Enables or disables copy and paste detection. The effect of enabling this
345     * this setting is that when a tab is received immediately followed by another
346     * character, the tab will not be treated as a completion, but as a tab literal.
347     * @param onoff true if detection is enabled
348     */
349    public void setCopyPasteDetection(final boolean onoff) {
350        copyPasteDetection = onoff;
351    }
352
353    /**
354     * @return true if copy and paste detection is enabled.
355     */
356    public boolean isCopyPasteDetectionEnabled() {
357        return copyPasteDetection;
358    }
359
360    /**
361     * Set whether the console bell is enabled.
362     *
363     * @param enabled true if enabled; false otherwise
364     * @since 2.7
365     */
366    public void setBellEnabled(boolean enabled) {
367        this.bellEnabled = enabled;
368    }
369
370    /**
371     * Get whether the console bell is enabled
372     *
373     * @return true if enabled; false otherwise
374     * @since 2.7
375     */
376    public boolean getBellEnabled() {
377        return bellEnabled;
378    }
379
380    /**
381     * Set whether user interrupts (ctrl-C) are handled by having JLine
382     * throw {@link UserInterruptException} from {@link #readLine}.
383     * Otherwise, the JVM will handle {@code SIGINT} as normal, which
384     * usually causes it to exit. The default is {@code false}.
385     *
386     * @since 2.10
387     */
388    public void setHandleUserInterrupt(boolean enabled)
389    {
390        this.handleUserInterrupt = enabled;
391    }
392
393    /**
394     * Get whether user interrupt handling is enabled
395     *
396     * @return true if enabled; false otherwise
397     * @since 2.10
398     */
399    public boolean getHandleUserInterrupt()
400    {
401        return handleUserInterrupt;
402    }
403
404    /**
405     * Sets the string that will be used to start a comment when the
406     * insert-comment key is struck.
407     * @param commentBegin The begin comment string.
408     * @since 2.7
409     */
410    public void setCommentBegin(String commentBegin) {
411        this.commentBegin = commentBegin;
412    }
413
414    /**
415     * @return the string that will be used to start a comment when the
416     * insert-comment key is struck.
417     * @since 2.7
418     */
419    public String getCommentBegin() {
420        String str = commentBegin;
421
422        if (str == null) {
423            str = consoleKeys.getVariable("comment-begin");
424            if (str == null) {
425                str = "#";
426            }
427        }
428        return str;
429    }
430
431    public void setPrompt(final String prompt) {
432        this.prompt = prompt;
433        this.promptLen = ((prompt == null) ? 0 : stripAnsi(lastLine(prompt)).length());
434    }
435
436    public String getPrompt() {
437        return prompt;
438    }
439
440    /**
441     * Set the echo character. For example, to have "*" entered when a password is typed:
442     * <p/>
443     * <pre>
444     * myConsoleReader.setEchoCharacter(new Character('*'));
445     * </pre>
446     * <p/>
447     * Setting the character to
448     * <p/>
449     * <pre>
450     * null
451     * </pre>
452     * <p/>
453     * will restore normal character echoing. Setting the character to
454     * <p/>
455     * <pre>
456     * new Character(0)
457     * </pre>
458     * <p/>
459     * will cause nothing to be echoed.
460     *
461     * @param c the character to echo to the console in place of the typed character.
462     */
463    public void setEchoCharacter(final Character c) {
464        this.echoCharacter = c;
465    }
466
467    /**
468     * Returns the echo character.
469     */
470    public Character getEchoCharacter() {
471        return echoCharacter;
472    }
473
474    /**
475     * Erase the current line.
476     *
477     * @return false if we failed (e.g., the buffer was empty)
478     */
479    protected final boolean resetLine() throws IOException {
480        if (buf.cursor == 0) {
481            return false;
482        }
483
484        StringBuilder killed = new StringBuilder();
485
486        while (buf.cursor > 0) {
487            char c = buf.current();
488            if (c == 0) {
489                break;
490            }
491
492            killed.append(c);
493            backspace();
494        }
495
496        String copy = killed.reverse().toString();
497        killRing.addBackwards(copy);
498
499        return true;
500    }
501
502    int getCursorPosition() {
503        // FIXME: does not handle anything but a line with a prompt absolute position
504        return promptLen + buf.cursor;
505    }
506
507    /**
508     * Returns the text after the last '\n'.
509     * prompt is returned if no '\n' characters are present.
510     * null is returned if prompt is null.
511     */
512    private String lastLine(String str) {
513        if (str == null) return "";
514        int last = str.lastIndexOf("\n");
515
516        if (last >= 0) {
517            return str.substring(last + 1, str.length());
518        }
519
520        return str;
521    }
522
523    String stripAnsi(String str) {
524        if (str == null) return "";
525        return ANSI_CODE_PATTERN.matcher(str).replaceAll("");
526//        try {
527//            ByteArrayOutputStream baos = new ByteArrayOutputStream();
528//            AnsiOutputStream aos = new AnsiOutputStream(baos);
529//            aos.write(str.getBytes());
530//            aos.flush();
531//            return baos.toString();
532//        } catch (IOException e) {
533//            return str;
534//        }
535    }
536    //where:
537        private static final Pattern ANSI_CODE_PATTERN = Pattern.compile("\033\\[[^@-~]*[@-~]");
538
539    /**
540     * Move the cursor position to the specified absolute index.
541     */
542    public final boolean setCursorPosition(final int position) throws IOException {
543        if (position == buf.cursor) {
544            return true;
545        }
546
547        return moveCursor(position - buf.cursor) != 0;
548    }
549
550    /**
551     * Set the current buffer's content to the specified {@link String}. The
552     * visual console will be modified to show the current buffer.
553     *
554     * @param buffer the new contents of the buffer.
555     */
556    private void setBuffer(final String buffer) throws IOException {
557        // don't bother modifying it if it is unchanged
558        if (buffer.equals(buf.buffer.toString())) {
559            return;
560        }
561
562        // obtain the difference between the current buffer and the new one
563        int sameIndex = 0;
564
565        for (int i = 0, l1 = buffer.length(), l2 = buf.buffer.length(); (i < l1)
566            && (i < l2); i++) {
567            if (buffer.charAt(i) == buf.buffer.charAt(i)) {
568                sameIndex++;
569            }
570            else {
571                break;
572            }
573        }
574
575        int diff = buf.cursor - sameIndex;
576        if (diff < 0) { // we can't backspace here so try from the end of the buffer
577            moveToEnd();
578            diff = buf.buffer.length() - sameIndex;
579        }
580
581        backspace(diff); // go back for the differences
582        killLine(); // clear to the end of the line
583        buf.buffer.setLength(sameIndex); // the new length
584        putString(buffer.substring(sameIndex)); // append the differences
585    }
586
587    private void setBuffer(final CharSequence buffer) throws IOException {
588        setBuffer(String.valueOf(buffer));
589    }
590
591    private void setBufferKeepPos(final String buffer) throws IOException {
592        int pos = buf.cursor;
593        setBuffer(buffer);
594        setCursorPosition(pos);
595    }
596
597    private void setBufferKeepPos(final CharSequence buffer) throws IOException {
598        setBufferKeepPos(String.valueOf(buffer));
599    }
600
601    /**
602     * Output put the prompt + the current buffer
603     */
604    public final void drawLine() throws IOException {
605        String prompt = getPrompt();
606        if (prompt != null) {
607            print(prompt);
608        }
609
610        print(buf.buffer.toString());
611
612        if (buf.length() != buf.cursor) { // not at end of line
613            back(buf.length() - buf.cursor - 1);
614        }
615        // force drawBuffer to check for weird wrap (after clear screen)
616        drawBuffer();
617    }
618
619    /**
620     * Clear the line and redraw it.
621     */
622    public final void redrawLine() throws IOException {
623        print(RESET_LINE);
624//        flush();
625        drawLine();
626    }
627
628    /**
629     * Clear the buffer and add its contents to the history.
630     *
631     * @return the former contents of the buffer.
632     */
633    final String finishBuffer() throws IOException { // FIXME: Package protected because used by tests
634        String str = buf.buffer.toString();
635        String historyLine = str;
636
637        if (expandEvents) {
638            try {
639                str = expandEvents(str);
640                // all post-expansion occurrences of '!' must have been escaped, so re-add escape to each
641                historyLine = str.replace("!", "\\!");
642                // only leading '^' results in expansion, so only re-add escape for that case
643                historyLine = historyLine.replaceAll("^\\^", "\\\\^");
644            } catch(IllegalArgumentException e) {
645                Log.error("Could not expand event", e);
646                beep();
647                buf.clear();
648                str = "";
649            }
650        }
651
652        // we only add it to the history if the buffer is not empty
653        // and if mask is null, since having a mask typically means
654        // the string was a password. We clear the mask after this call
655        if (str.length() > 0) {
656            if (mask == null && isHistoryEnabled()) {
657                history.add(historyLine);
658            }
659            else {
660                mask = null;
661            }
662        }
663
664        history.moveToEnd();
665
666        buf.buffer.setLength(0);
667        buf.cursor = 0;
668
669        return str;
670    }
671
672    /**
673     * Expand event designator such as !!, !#, !3, etc...
674     * See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
675     */
676    @SuppressWarnings("fallthrough")
677    protected String expandEvents(String str) throws IOException {
678        StringBuilder sb = new StringBuilder();
679        for (int i = 0; i < str.length(); i++) {
680            char c = str.charAt(i);
681            switch (c) {
682                case '\\':
683                    // any '\!' should be considered an expansion escape, so skip expansion and strip the escape character
684                    // a leading '\^' should be considered an expansion escape, so skip expansion and strip the escape character
685                    // otherwise, add the escape
686                    if (i + 1 < str.length()) {
687                        char nextChar = str.charAt(i+1);
688                        if (nextChar == '!' || (nextChar == '^' && i == 0)) {
689                            c = nextChar;
690                            i++;
691                        }
692                    }
693                    sb.append(c);
694                    break;
695                case '!':
696                    if (i + 1 < str.length()) {
697                        c = str.charAt(++i);
698                        boolean neg = false;
699                        String rep = null;
700                        int i1, idx;
701                        switch (c) {
702                            case '!':
703                                if (history.size() == 0) {
704                                    throw new IllegalArgumentException("!!: event not found");
705                                }
706                                rep = history.get(history.index() - 1).toString();
707                                break;
708                            case '#':
709                                sb.append(sb.toString());
710                                break;
711                            case '?':
712                                i1 = str.indexOf('?', i + 1);
713                                if (i1 < 0) {
714                                    i1 = str.length();
715                                }
716                                String sc = str.substring(i + 1, i1);
717                                i = i1;
718                                idx = searchBackwards(sc);
719                                if (idx < 0) {
720                                    throw new IllegalArgumentException("!?" + sc + ": event not found");
721                                } else {
722                                    rep = history.get(idx).toString();
723                                }
724                                break;
725                            case '$':
726                                if (history.size() == 0) {
727                                    throw new IllegalArgumentException("!$: event not found");
728                                }
729                                String previous = history.get(history.index() - 1).toString().trim();
730                                int lastSpace = previous.lastIndexOf(' ');
731                                if(lastSpace != -1) {
732                                    rep = previous.substring(lastSpace+1);
733                                } else {
734                                    rep = previous;
735                                }
736                                break;
737                            case ' ':
738                            case '\t':
739                                sb.append('!');
740                                sb.append(c);
741                                break;
742                            case '-':
743                                neg = true;
744                                i++;
745                                // fall through
746                            case '0':
747                            case '1':
748                            case '2':
749                            case '3':
750                            case '4':
751                            case '5':
752                            case '6':
753                            case '7':
754                            case '8':
755                            case '9':
756                                i1 = i;
757                                for (; i < str.length(); i++) {
758                                    c = str.charAt(i);
759                                    if (c < '0' || c > '9') {
760                                        break;
761                                    }
762                                }
763                                idx = 0;
764                                try {
765                                    idx = Integer.parseInt(str.substring(i1, i));
766                                } catch (NumberFormatException e) {
767                                    throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found");
768                                }
769                                if (neg) {
770                                    if (idx > 0 && idx <= history.size()) {
771                                        rep = (history.get(history.index() - idx)).toString();
772                                    } else {
773                                        throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found");
774                                    }
775                                } else {
776                                    if (idx > history.index() - history.size() && idx <= history.index()) {
777                                        rep = (history.get(idx - 1)).toString();
778                                    } else {
779                                        throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found");
780                                    }
781                                }
782                                break;
783                            default:
784                                String ss = str.substring(i);
785                                i = str.length();
786                                idx = searchBackwards(ss, history.index(), true);
787                                if (idx < 0) {
788                                    throw new IllegalArgumentException("!" + ss + ": event not found");
789                                } else {
790                                    rep = history.get(idx).toString();
791                                }
792                                break;
793                        }
794                        if (rep != null) {
795                            sb.append(rep);
796                        }
797                    } else {
798                        sb.append(c);
799                    }
800                    break;
801                case '^':
802                    if (i == 0) {
803                        int i1 = str.indexOf('^', i + 1);
804                        int i2 = str.indexOf('^', i1 + 1);
805                        if (i2 < 0) {
806                            i2 = str.length();
807                        }
808                        if (i1 > 0 && i2 > 0) {
809                            String s1 = str.substring(i + 1, i1);
810                            String s2 = str.substring(i1 + 1, i2);
811                            String s = history.get(history.index() - 1).toString().replace(s1, s2);
812                            sb.append(s);
813                            i = i2 + 1;
814                            break;
815                        }
816                    }
817                    sb.append(c);
818                    break;
819                default:
820                    sb.append(c);
821                    break;
822            }
823        }
824        String result = sb.toString();
825        if (!str.equals(result)) {
826            print(result);
827            println();
828            flush();
829        }
830        return result;
831
832    }
833
834    /**
835     * Write out the specified string to the buffer and the output stream.
836     */
837    public final void putString(final CharSequence str) throws IOException {
838        buf.write(str);
839        if (mask == null) {
840            // no masking
841            print(str);
842        } else if (mask == NULL_MASK) {
843            // don't print anything
844        } else {
845            print(mask, str.length());
846        }
847        drawBuffer();
848    }
849
850    /**
851     * Redraw the rest of the buffer from the cursor onwards. This is necessary
852     * for inserting text into the buffer.
853     *
854     * @param clear the number of characters to clear after the end of the buffer
855     */
856    private void drawBuffer(final int clear) throws IOException {
857        // debug ("drawBuffer: " + clear);
858        if (buf.cursor == buf.length() && clear == 0) {
859        } else {
860            char[] chars = buf.buffer.substring(buf.cursor).toCharArray();
861            if (mask != null) {
862                Arrays.fill(chars, mask);
863            }
864            if (terminal.hasWeirdWrap()) {
865                // need to determine if wrapping will occur:
866                int width = terminal.getWidth();
867                int pos = getCursorPosition();
868                for (int i = 0; i < chars.length; i++) {
869                    print(chars[i]);
870                    if ((pos + i + 1) % width == 0) {
871                        print(32); // move cursor to next line by printing dummy space
872                        print(13); // CR / not newline.
873                    }
874                }
875            } else {
876                print(chars);
877            }
878            clearAhead(clear, chars.length);
879            if (terminal.isAnsiSupported()) {
880                if (chars.length > 0) {
881                    back(chars.length);
882                }
883            } else {
884                back(chars.length);
885            }
886        }
887        if (terminal.hasWeirdWrap()) {
888            int width = terminal.getWidth();
889            // best guess on whether the cursor is in that weird location...
890            // Need to do this without calling ansi cursor location methods
891            // otherwise it breaks paste of wrapped lines in xterm.
892            if (getCursorPosition() > 0 && (getCursorPosition() % width == 0)
893                    && buf.cursor == buf.length() && clear == 0) {
894                // the following workaround is reverse-engineered from looking
895                // at what bash sent to the terminal in the same situation
896                print(32); // move cursor to next line by printing dummy space
897                print(13); // CR / not newline.
898            }
899        }
900    }
901
902    /**
903     * Redraw the rest of the buffer from the cursor onwards. This is necessary
904     * for inserting text into the buffer.
905     */
906    private void drawBuffer() throws IOException {
907        drawBuffer(0);
908    }
909
910    /**
911     * Clear ahead the specified number of characters without moving the cursor.
912     *
913     * @param num the number of characters to clear
914     * @param delta the difference between the internal cursor and the screen
915     * cursor - if > 0, assume some stuff was printed and weird wrap has to be
916     * checked
917     */
918    private void clearAhead(final int num, int delta) throws IOException {
919        if (num == 0) {
920            return;
921        }
922
923        if (terminal.isAnsiSupported()) {
924            int width = terminal.getWidth();
925            int screenCursorCol = getCursorPosition() + delta;
926            // clear current line
927            printAnsiSequence("K");
928            // if cursor+num wraps, then we need to clear the line(s) below too
929            int curCol = screenCursorCol % width;
930            int endCol = (screenCursorCol + num - 1) % width;
931            int lines = num / width;
932            if (endCol < curCol) lines++;
933            for (int i = 0; i < lines; i++) {
934                printAnsiSequence("B");
935                printAnsiSequence("2K");
936            }
937            for (int i = 0; i < lines; i++) {
938                printAnsiSequence("A");
939            }
940            return;
941        }
942
943        // print blank extra characters
944        print(' ', num);
945
946        // we need to flush here so a "clever" console doesn't just ignore the redundancy
947        // of a space followed by a backspace.
948//        flush();
949
950        // reset the visual cursor
951        back(num);
952
953//        flush();
954    }
955
956    /**
957     * Move the visual cursor backwards without modifying the buffer cursor.
958     */
959    protected void back(final int num) throws IOException {
960        if (num == 0) return;
961        if (terminal.isAnsiSupported()) {
962            int width = getTerminal().getWidth();
963            int cursor = getCursorPosition();
964            int realCursor = cursor + num;
965            int realCol  = realCursor % width;
966            int newCol = cursor % width;
967            int moveup = num / width;
968            int delta = realCol - newCol;
969            if (delta < 0) moveup++;
970            if (moveup > 0) {
971                printAnsiSequence(moveup + "A");
972            }
973            printAnsiSequence((1 + newCol) + "G");
974            return;
975        }
976        print(BACKSPACE, num);
977//        flush();
978    }
979
980    /**
981     * Flush the console output stream. This is important for printout out single characters (like a backspace or
982     * keyboard) that we want the console to handle immediately.
983     */
984    public void flush() throws IOException {
985        out.flush();
986    }
987
988    private int backspaceAll() throws IOException {
989        return backspace(Integer.MAX_VALUE);
990    }
991
992    /**
993     * Issue <em>num</em> backspaces.
994     *
995     * @return the number of characters backed up
996     */
997    private int backspace(final int num) throws IOException {
998        if (buf.cursor == 0) {
999            return 0;
1000        }
1001
1002        int count = 0;
1003
1004        int termwidth = getTerminal().getWidth();
1005        int lines = getCursorPosition() / termwidth;
1006        count = moveCursor(-1 * num) * -1;
1007        buf.buffer.delete(buf.cursor, buf.cursor + count);
1008        if (getCursorPosition() / termwidth != lines) {
1009            if (terminal.isAnsiSupported()) {
1010                // debug("doing backspace redraw: " + getCursorPosition() + " on " + termwidth + ": " + lines);
1011                printAnsiSequence("K");
1012                // if cursor+num wraps, then we need to clear the line(s) below too
1013                // last char printed is one pos less than cursor so we subtract
1014                // one
1015/*
1016                // TODO: fixme (does not work - test with reverse search with wrapping line and CTRL-E)
1017                int endCol = (getCursorPosition() + num - 1) % termwidth;
1018                int curCol = getCursorPosition() % termwidth;
1019                if (endCol < curCol) lines++;
1020                for (int i = 1; i < lines; i++) {
1021                    printAnsiSequence("B");
1022                    printAnsiSequence("2K");
1023                }
1024                for (int i = 1; i < lines; i++) {
1025                    printAnsiSequence("A");
1026                }
1027                return count;
1028*/
1029            }
1030        }
1031        drawBuffer(count);
1032
1033        return count;
1034    }
1035
1036    /**
1037     * Issue a backspace.
1038     *
1039     * @return true if successful
1040     */
1041    public boolean backspace() throws IOException {
1042        return backspace(1) == 1;
1043    }
1044
1045    protected boolean moveToEnd() throws IOException {
1046        if (buf.cursor == buf.length()) {
1047            return true;
1048        }
1049        return moveCursor(buf.length() - buf.cursor) > 0;
1050    }
1051
1052    /**
1053     * Delete the character at the current position and redraw the remainder of the buffer.
1054     */
1055    private boolean deleteCurrentCharacter() throws IOException {
1056        if (buf.length() == 0 || buf.cursor == buf.length()) {
1057            return false;
1058        }
1059
1060        buf.buffer.deleteCharAt(buf.cursor);
1061        drawBuffer(1);
1062        return true;
1063    }
1064
1065    /**
1066     * This method is calling while doing a delete-to ("d"), change-to ("c"),
1067     * or yank-to ("y") and it filters out only those movement operations
1068     * that are allowable during those operations. Any operation that isn't
1069     * allow drops you back into movement mode.
1070     *
1071     * @param op The incoming operation to remap
1072     * @return The remaped operation
1073     */
1074    private Operation viDeleteChangeYankToRemap (Operation op) {
1075        switch (op) {
1076            case VI_EOF_MAYBE:
1077            case ABORT:
1078            case BACKWARD_CHAR:
1079            case FORWARD_CHAR:
1080            case END_OF_LINE:
1081            case VI_MATCH:
1082            case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
1083            case VI_ARG_DIGIT:
1084            case VI_PREV_WORD:
1085            case VI_END_WORD:
1086            case VI_CHAR_SEARCH:
1087            case VI_NEXT_WORD:
1088            case VI_FIRST_PRINT:
1089            case VI_GOTO_MARK:
1090            case VI_COLUMN:
1091            case VI_DELETE_TO:
1092            case VI_YANK_TO:
1093            case VI_CHANGE_TO:
1094                return op;
1095
1096            default:
1097                return Operation.VI_MOVEMENT_MODE;
1098        }
1099    }
1100
1101    /**
1102     * Deletes the previous character from the cursor position
1103     * @param count number of times to do it.
1104     * @return true if it was done.
1105     * @throws IOException
1106     */
1107    private boolean viRubout(int count) throws IOException {
1108        boolean ok = true;
1109        for (int i = 0; ok && i < count; i++) {
1110            ok = backspace();
1111        }
1112        return ok;
1113    }
1114
1115    /**
1116     * Deletes the character you are sitting on and sucks the rest of
1117     * the line in from the right.
1118     * @param count Number of times to perform the operation.
1119     * @return true if its works, false if it didn't
1120     * @throws IOException
1121     */
1122    private boolean viDelete(int count) throws IOException {
1123        boolean ok = true;
1124        for (int i = 0; ok && i < count; i++) {
1125            ok = deleteCurrentCharacter();
1126        }
1127        return ok;
1128    }
1129
1130    /**
1131     * Switches the case of the current character from upper to lower
1132     * or lower to upper as necessary and advances the cursor one
1133     * position to the right.
1134     * @param count The number of times to repeat
1135     * @return true if it completed successfully, false if not all
1136     *   case changes could be completed.
1137     * @throws IOException
1138     */
1139    private boolean viChangeCase(int count) throws IOException {
1140        boolean ok = true;
1141        for (int i = 0; ok && i < count; i++) {
1142
1143            ok = buf.cursor < buf.buffer.length ();
1144            if (ok) {
1145                char ch = buf.buffer.charAt(buf.cursor);
1146                if (Character.isUpperCase(ch)) {
1147                    ch = Character.toLowerCase(ch);
1148                }
1149                else if (Character.isLowerCase(ch)) {
1150                    ch = Character.toUpperCase(ch);
1151                }
1152                buf.buffer.setCharAt(buf.cursor, ch);
1153                drawBuffer(1);
1154                moveCursor(1);
1155            }
1156        }
1157        return ok;
1158    }
1159
1160    /**
1161     * Implements the vi change character command (in move-mode "r"
1162     * followed by the character to change to).
1163     * @param count Number of times to perform the action
1164     * @param c The character to change to
1165     * @return Whether or not there were problems encountered
1166     * @throws IOException
1167     */
1168    private boolean viChangeChar(int count, int c) throws IOException {
1169        // EOF, ESC, or CTRL-C aborts.
1170        if (c < 0 || c == '\033' || c == '\003') {
1171            return true;
1172        }
1173
1174        boolean ok = true;
1175        for (int i = 0; ok && i < count; i++) {
1176            ok = buf.cursor < buf.buffer.length ();
1177            if (ok) {
1178                buf.buffer.setCharAt(buf.cursor, (char) c);
1179                drawBuffer(1);
1180                if (i < (count-1)) {
1181                    moveCursor(1);
1182                }
1183            }
1184        }
1185        return ok;
1186    }
1187
1188    /**
1189     * This is a close facsimile of the actual vi previous word logic. In
1190     * actual vi words are determined by boundaries of identity characterse.
1191     * This logic is a bit more simple and simply looks at white space or
1192     * digits or characters.  It should be revised at some point.
1193     *
1194     * @param count number of iterations
1195     * @return true if the move was successful, false otherwise
1196     * @throws IOException
1197     */
1198    private boolean viPreviousWord(int count) throws IOException {
1199        boolean ok = true;
1200        if (buf.cursor == 0) {
1201            return false;
1202        }
1203
1204        int pos = buf.cursor - 1;
1205        for (int i = 0; pos > 0 && i < count; i++) {
1206            // If we are on white space, then move back.
1207            while (pos > 0 && isWhitespace(buf.buffer.charAt(pos))) {
1208                --pos;
1209            }
1210
1211            while (pos > 0 && !isDelimiter(buf.buffer.charAt(pos-1))) {
1212                --pos;
1213            }
1214
1215            if (pos > 0 && i < (count-1)) {
1216                --pos;
1217            }
1218        }
1219        setCursorPosition(pos);
1220        return ok;
1221    }
1222
1223    /**
1224     * Performs the vi "delete-to" action, deleting characters between a given
1225     * span of the input line.
1226     * @param startPos The start position
1227     * @param endPos The end position.
1228     * @param isChange If true, then the delete is part of a change operationg
1229     *    (e.g. "c$" is change-to-end-of line, so we first must delete to end
1230     *    of line to start the change
1231     * @return true if it succeeded, false otherwise
1232     * @throws IOException
1233     */
1234    private boolean viDeleteTo(int startPos, int endPos, boolean isChange) throws IOException {
1235        if (startPos == endPos) {
1236            return true;
1237        }
1238
1239        if (endPos < startPos) {
1240            int tmp = endPos;
1241            endPos = startPos;
1242            startPos = tmp;
1243        }
1244
1245        setCursorPosition(startPos);
1246        buf.cursor = startPos;
1247        buf.buffer.delete(startPos, endPos);
1248        drawBuffer(endPos - startPos);
1249
1250        // If we are doing a delete operation (e.g. "d$") then don't leave the
1251        // cursor dangling off the end. In reality the "isChange" flag is silly
1252        // what is really happening is that if we are in "move-mode" then the
1253        // cursor can't be moved off the end of the line, but in "edit-mode" it
1254        // is ok, but I have no easy way of knowing which mode we are in.
1255        if (! isChange && startPos > 0 && startPos == buf.length()) {
1256            moveCursor(-1);
1257        }
1258        return true;
1259    }
1260
1261    /**
1262     * Implement the "vi" yank-to operation.  This operation allows you
1263     * to yank the contents of the current line based upon a move operation,
1264     * for exaple "yw" yanks the current word, "3yw" yanks 3 words, etc.
1265     *
1266     * @param startPos The starting position from which to yank
1267     * @param endPos The ending position to which to yank
1268     * @return true if the yank succeeded
1269     * @throws IOException
1270     */
1271    private boolean viYankTo(int startPos, int endPos) throws IOException {
1272        int cursorPos = startPos;
1273
1274        if (endPos < startPos) {
1275            int tmp = endPos;
1276            endPos = startPos;
1277            startPos = tmp;
1278        }
1279
1280        if (startPos == endPos) {
1281            yankBuffer = "";
1282            return true;
1283        }
1284
1285        yankBuffer = buf.buffer.substring(startPos, endPos);
1286
1287        /*
1288         * It was a movement command that moved the cursor to find the
1289         * end position, so put the cursor back where it started.
1290         */
1291        setCursorPosition(cursorPos);
1292        return true;
1293    }
1294
1295    /**
1296     * Pasts the yank buffer to the right of the current cursor position
1297     * and moves the cursor to the end of the pasted region.
1298     *
1299     * @param count Number of times to perform the operation.
1300     * @return true if it worked, false otherwise
1301     * @throws IOException
1302     */
1303    private boolean viPut(int count) throws IOException {
1304        if (yankBuffer.length () == 0) {
1305            return true;
1306        }
1307        if (buf.cursor < buf.buffer.length ()) {
1308            moveCursor(1);
1309        }
1310        for (int i = 0; i < count; i++) {
1311            putString(yankBuffer);
1312        }
1313        moveCursor(-1);
1314        return true;
1315    }
1316
1317    /**
1318     * Searches forward of the current position for a character and moves
1319     * the cursor onto it.
1320     * @param count Number of times to repeat the process.
1321     * @param ch The character to search for
1322     * @return true if the char was found, false otherwise
1323     * @throws IOException
1324     */
1325    private boolean viCharSearch(int count, int invokeChar, int ch) throws IOException {
1326        if (ch < 0 || invokeChar < 0) {
1327            return false;
1328        }
1329
1330        char    searchChar = (char)ch;
1331        boolean isForward;
1332        boolean stopBefore;
1333
1334        /*
1335         * The character stuff turns out to be hairy. Here is how it works:
1336         *   f - search forward for ch
1337         *   F - search backward for ch
1338         *   t - search forward for ch, but stop just before the match
1339         *   T - search backward for ch, but stop just after the match
1340         *   ; - After [fFtT;], repeat the last search, after ',' reverse it
1341         *   , - After [fFtT;], reverse the last search, after ',' repeat it
1342         */
1343        if (invokeChar == ';' || invokeChar == ',') {
1344            // No recent search done? Then bail
1345            if (charSearchChar == 0) {
1346                return false;
1347            }
1348
1349            // Reverse direction if switching between ',' and ';'
1350            if (charSearchLastInvokeChar == ';' || charSearchLastInvokeChar == ',') {
1351                if (charSearchLastInvokeChar != invokeChar) {
1352                    charSearchFirstInvokeChar = switchCase(charSearchFirstInvokeChar);
1353                }
1354            }
1355            else {
1356                if (invokeChar == ',') {
1357                    charSearchFirstInvokeChar = switchCase(charSearchFirstInvokeChar);
1358                }
1359            }
1360
1361            searchChar = charSearchChar;
1362        }
1363        else {
1364            charSearchChar            = searchChar;
1365            charSearchFirstInvokeChar = (char) invokeChar;
1366        }
1367
1368        charSearchLastInvokeChar = (char)invokeChar;
1369
1370        isForward = Character.isLowerCase(charSearchFirstInvokeChar);
1371        stopBefore = (Character.toLowerCase(charSearchFirstInvokeChar) == 't');
1372
1373        boolean ok = false;
1374
1375        if (isForward) {
1376            while (count-- > 0) {
1377                int pos = buf.cursor + 1;
1378                while (pos < buf.buffer.length()) {
1379                    if (buf.buffer.charAt(pos) == searchChar) {
1380                        setCursorPosition(pos);
1381                        ok = true;
1382                        break;
1383                    }
1384                    ++pos;
1385                }
1386            }
1387
1388            if (ok) {
1389                if (stopBefore)
1390                    moveCursor(-1);
1391
1392                /*
1393                 * When in yank-to, move-to, del-to state we actually want to
1394                 * go to the character after the one we landed on to make sure
1395                 * that the character we ended up on is included in the
1396                 * operation
1397                 */
1398                if (isInViMoveOperationState()) {
1399                    moveCursor(1);
1400                }
1401            }
1402        }
1403        else {
1404            while (count-- > 0) {
1405                int pos = buf.cursor - 1;
1406                while (pos >= 0) {
1407                    if (buf.buffer.charAt(pos) == searchChar) {
1408                        setCursorPosition(pos);
1409                        ok = true;
1410                        break;
1411                    }
1412                    --pos;
1413                }
1414            }
1415
1416            if (ok && stopBefore)
1417                moveCursor(1);
1418        }
1419
1420        return ok;
1421    }
1422
1423    private char switchCase(char ch) {
1424        if (Character.isUpperCase(ch)) {
1425            return Character.toLowerCase(ch);
1426        }
1427        return Character.toUpperCase(ch);
1428    }
1429
1430    /**
1431     * @return true if line reader is in the middle of doing a change-to
1432     *   delete-to or yank-to.
1433     */
1434    private final boolean isInViMoveOperationState() {
1435        return state == State.VI_CHANGE_TO
1436            || state == State.VI_DELETE_TO
1437            || state == State.VI_YANK_TO;
1438    }
1439
1440    /**
1441     * This is a close facsimile of the actual vi next word logic.
1442     * As with viPreviousWord() this probably needs to be improved
1443     * at some point.
1444     *
1445     * @param count number of iterations
1446     * @return true if the move was successful, false otherwise
1447     * @throws IOException
1448     */
1449    private boolean viNextWord(int count) throws IOException {
1450        int pos = buf.cursor;
1451        int end = buf.buffer.length();
1452
1453        for (int i = 0; pos < end && i < count; i++) {
1454            // Skip over letter/digits
1455            while (pos < end && !isDelimiter(buf.buffer.charAt(pos))) {
1456                ++pos;
1457            }
1458
1459            /*
1460             * Don't you love special cases? During delete-to and yank-to
1461             * operations the word movement is normal. However, during a
1462             * change-to, the trailing spaces behind the last word are
1463             * left in tact.
1464             */
1465            if (i < (count-1) || !(state == State.VI_CHANGE_TO)) {
1466                while (pos < end && isDelimiter(buf.buffer.charAt(pos))) {
1467                    ++pos;
1468                }
1469            }
1470        }
1471
1472        setCursorPosition(pos);
1473        return true;
1474    }
1475
1476    /**
1477     * Implements a close facsimile of the vi end-of-word movement.
1478     * If the character is on white space, it takes you to the end
1479     * of the next word.  If it is on the last character of a word
1480     * it takes you to the next of the next word.  Any other character
1481     * of a word, takes you to the end of the current word.
1482     *
1483     * @param count Number of times to repeat the action
1484     * @return true if it worked.
1485     * @throws IOException
1486     */
1487    private boolean viEndWord(int count) throws IOException {
1488        int pos = buf.cursor;
1489        int end = buf.buffer.length();
1490
1491        for (int i = 0; pos < end && i < count; i++) {
1492            if (pos < (end-1)
1493                    && !isDelimiter(buf.buffer.charAt(pos))
1494                    && isDelimiter(buf.buffer.charAt (pos+1))) {
1495                ++pos;
1496            }
1497
1498            // If we are on white space, then move back.
1499            while (pos < end && isDelimiter(buf.buffer.charAt(pos))) {
1500                ++pos;
1501            }
1502
1503            while (pos < (end-1) && !isDelimiter(buf.buffer.charAt(pos+1))) {
1504                ++pos;
1505            }
1506        }
1507        setCursorPosition(pos);
1508        return true;
1509    }
1510
1511    private boolean previousWord() throws IOException {
1512        while (isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1513            // nothing
1514        }
1515
1516        while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1517            // nothing
1518        }
1519
1520        return true;
1521    }
1522
1523    private boolean nextWord() throws IOException {
1524        while (isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) {
1525            // nothing
1526        }
1527
1528        while (!isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) {
1529            // nothing
1530        }
1531
1532        return true;
1533    }
1534
1535    /**
1536     * Deletes to the beginning of the word that the cursor is sitting on.
1537     * If the cursor is on white-space, it deletes that and to the beginning
1538     * of the word before it.  If the user is not on a word or whitespace
1539     * it deletes up to the end of the previous word.
1540     *
1541     * @param count Number of times to perform the operation
1542     * @return true if it worked, false if you tried to delete too many words
1543     * @throws IOException
1544     */
1545    private boolean unixWordRubout(int count) throws IOException {
1546        boolean success = true;
1547        StringBuilder killed = new StringBuilder();
1548
1549        for (; count > 0; --count) {
1550            if (buf.cursor == 0) {
1551                success = false;
1552                break;
1553            }
1554
1555            while (isWhitespace(buf.current())) {
1556                char c = buf.current();
1557                if (c == 0) {
1558                    break;
1559                }
1560
1561                killed.append(c);
1562                backspace();
1563            }
1564
1565            while (!isWhitespace(buf.current())) {
1566                char c = buf.current();
1567                if (c == 0) {
1568                    break;
1569                }
1570
1571                killed.append(c);
1572                backspace();
1573            }
1574        }
1575
1576        String copy = killed.reverse().toString();
1577        killRing.addBackwards(copy);
1578
1579        return success;
1580    }
1581
1582    private String insertComment(boolean isViMode) throws IOException {
1583        String comment = this.getCommentBegin ();
1584        setCursorPosition(0);
1585        putString(comment);
1586        if (isViMode) {
1587            consoleKeys.setKeyMap(KeyMap.VI_INSERT);
1588        }
1589        return accept();
1590    }
1591
1592    /**
1593     * Similar to putString() but allows the string to be repeated a specific
1594     * number of times, allowing easy support of vi digit arguments to a given
1595     * command. The string is placed as the current cursor position.
1596     *
1597     * @param count The count of times to insert the string.
1598     * @param str The string to insert
1599     * @return true if the operation is a success, false otherwise
1600     * @throws IOException
1601     */
1602    private boolean insert(int count, final CharSequence str) throws IOException {
1603        for (int i = 0; i < count; i++) {
1604            buf.write(str);
1605            if (mask == null) {
1606                // no masking
1607                print(str);
1608            } else if (mask == NULL_MASK) {
1609                // don't print anything
1610            } else {
1611                print(mask, str.length());
1612            }
1613        }
1614        drawBuffer();
1615        return true;
1616    }
1617
1618    /**
1619     * Implements vi search ("/" or "?").
1620     * @throws IOException
1621     */
1622    @SuppressWarnings("fallthrough")
1623    private int viSearch(char searchChar) throws IOException {
1624        boolean isForward = (searchChar == '/');
1625
1626        /*
1627         * This is a little gross, I'm sure there is a more appropriate way
1628         * of saving and restoring state.
1629         */
1630        CursorBuffer origBuffer = buf.copy();
1631
1632        // Clear the contents of the current line and
1633        setCursorPosition (0);
1634        killLine();
1635
1636        // Our new "prompt" is the character that got us into search mode.
1637        putString(Character.toString(searchChar));
1638        flush();
1639
1640        boolean isAborted = false;
1641        boolean isComplete = false;
1642
1643        /*
1644         * Readline doesn't seem to do any special character map handling
1645         * here, so I think we are safe.
1646         */
1647        int ch = -1;
1648        while (!isAborted && !isComplete && (ch = readCharacter()) != -1) {
1649            switch (ch) {
1650                case '\033':  // ESC
1651                    /*
1652                     * The ESC behavior doesn't appear to be readline behavior,
1653                     * but it is a little tweak of my own. I like it.
1654                     */
1655                    isAborted = true;
1656                    break;
1657                case '\010':  // Backspace
1658                case '\177':  // Delete
1659                    backspace();
1660                    /*
1661                     * Backspacing through the "prompt" aborts the search.
1662                     */
1663                    if (buf.cursor == 0) {
1664                        isAborted = true;
1665                    }
1666                    break;
1667                case '\012': // NL
1668                case '\015': // CR
1669                    isComplete = true;
1670                    break;
1671                default:
1672                    putString(Character.toString((char) ch));
1673            }
1674
1675            flush();
1676        }
1677
1678        // If we aborted, then put ourself at the end of the original buffer.
1679        if (ch == -1 || isAborted) {
1680            setCursorPosition(0);
1681            killLine();
1682            putString(origBuffer.buffer);
1683            setCursorPosition(origBuffer.cursor);
1684            return -1;
1685        }
1686
1687        /*
1688         * The first character of the buffer was the search character itself
1689         * so we discard it.
1690         */
1691        String searchTerm = buf.buffer.substring(1);
1692        int idx = -1;
1693
1694        /*
1695         * The semantics of the history thing is gross when you want to
1696         * explicitly iterate over entries (without an iterator) as size()
1697         * returns the actual number of entries in the list but get()
1698         * doesn't work the way you think.
1699         */
1700        int end   = history.index();
1701        int start = (end <= history.size()) ? 0 : end - history.size();
1702
1703        if (isForward) {
1704            for (int i = start; i < end; i++) {
1705                if (history.get(i).toString().contains(searchTerm)) {
1706                    idx = i;
1707                    break;
1708                }
1709            }
1710        }
1711        else {
1712            for (int i = end-1; i >= start; i--) {
1713                if (history.get(i).toString().contains(searchTerm)) {
1714                    idx = i;
1715                    break;
1716                }
1717            }
1718        }
1719
1720        /*
1721         * No match? Then restore what we were working on, but make sure
1722         * the cursor is at the beginning of the line.
1723         */
1724        if (idx == -1) {
1725            setCursorPosition(0);
1726            killLine();
1727            putString(origBuffer.buffer);
1728            setCursorPosition(0);
1729            return -1;
1730        }
1731
1732        /*
1733         * Show the match.
1734         */
1735        setCursorPosition(0);
1736        killLine();
1737        putString(history.get(idx));
1738        setCursorPosition(0);
1739        flush();
1740
1741        /*
1742         * While searching really only the "n" and "N" keys are interpreted
1743         * as movement, any other key is treated as if you are editing the
1744         * line with it, so we return it back up to the caller for interpretation.
1745         */
1746        isComplete = false;
1747        while (!isComplete && (ch = readCharacter()) != -1) {
1748            boolean forward = isForward;
1749            switch (ch) {
1750                case 'p': case 'P':
1751                    forward = !isForward;
1752                    // Fallthru
1753                case 'n': case 'N':
1754                    boolean isMatch = false;
1755                    if (forward) {
1756                        for (int i = idx+1; !isMatch && i < end; i++) {
1757                            if (history.get(i).toString().contains(searchTerm)) {
1758                                idx = i;
1759                                isMatch = true;
1760                            }
1761                        }
1762                    }
1763                    else {
1764                        for (int i = idx - 1; !isMatch && i >= start; i--) {
1765                            if (history.get(i).toString().contains(searchTerm)) {
1766                                idx = i;
1767                                isMatch = true;
1768                            }
1769                        }
1770                    }
1771                    if (isMatch) {
1772                        setCursorPosition(0);
1773                        killLine();
1774                        putString(history.get(idx));
1775                        setCursorPosition(0);
1776                    }
1777                    break;
1778                default:
1779                    isComplete = true;
1780            }
1781            flush();
1782        }
1783
1784        /*
1785         * Complete?
1786         */
1787        return ch;
1788    }
1789
1790    public void setParenBlinkTimeout(int timeout) {
1791        parenBlinkTimeout = timeout;
1792    }
1793
1794    private void insertClose(String s) throws IOException {
1795         putString(s);
1796         int closePosition = buf.cursor;
1797
1798         moveCursor(-1);
1799         viMatch();
1800
1801
1802         if (in.isNonBlockingEnabled()) {
1803            in.peek(parenBlinkTimeout);
1804         }
1805
1806         setCursorPosition(closePosition);
1807    }
1808
1809    /**
1810     * Implements vi style bracket matching ("%" command). The matching
1811     * bracket for the current bracket type that you are sitting on is matched.
1812     * The logic works like so:
1813     * @return true if it worked, false if the cursor was not on a bracket
1814     *   character or if there was no matching bracket.
1815     * @throws IOException
1816     */
1817    private boolean viMatch() throws IOException {
1818        int pos        = buf.cursor;
1819
1820        if (pos == buf.length()) {
1821            return false;
1822        }
1823
1824        int type       = getBracketType(buf.buffer.charAt (pos));
1825        int move       = (type < 0) ? -1 : 1;
1826        int count      = 1;
1827
1828        if (type == 0)
1829            return false;
1830
1831        while (count > 0) {
1832            pos += move;
1833
1834            // Fell off the start or end.
1835            if (pos < 0 || pos >= buf.buffer.length ()) {
1836                return false;
1837            }
1838
1839            int curType = getBracketType(buf.buffer.charAt (pos));
1840            if (curType == type) {
1841                ++count;
1842            }
1843            else if (curType == -type) {
1844                --count;
1845            }
1846        }
1847
1848        /*
1849         * Slight adjustment for delete-to, yank-to, change-to to ensure
1850         * that the matching paren is consumed
1851         */
1852        if (move > 0 && isInViMoveOperationState())
1853            ++pos;
1854
1855        setCursorPosition(pos);
1856        return true;
1857    }
1858
1859    /**
1860     * Given a character determines what type of bracket it is (paren,
1861     * square, curly, or none).
1862     * @param ch The character to check
1863     * @return 1 is square, 2 curly, 3 parent, or zero for none.  The value
1864     *   will be negated if it is the closing form of the bracket.
1865     */
1866    private int getBracketType (char ch) {
1867        switch (ch) {
1868            case '[': return  1;
1869            case ']': return -1;
1870            case '{': return  2;
1871            case '}': return -2;
1872            case '(': return  3;
1873            case ')': return -3;
1874            default:
1875                return 0;
1876        }
1877    }
1878
1879    private boolean deletePreviousWord() throws IOException {
1880        StringBuilder killed = new StringBuilder();
1881        char c;
1882
1883        while (isDelimiter((c = buf.current()))) {
1884            if (c == 0) {
1885                break;
1886            }
1887
1888            killed.append(c);
1889            backspace();
1890        }
1891
1892        while (!isDelimiter((c = buf.current()))) {
1893            if (c == 0) {
1894                break;
1895            }
1896
1897            killed.append(c);
1898            backspace();
1899        }
1900
1901        String copy = killed.reverse().toString();
1902        killRing.addBackwards(copy);
1903        return true;
1904    }
1905
1906    private boolean deleteNextWord() throws IOException {
1907        StringBuilder killed = new StringBuilder();
1908        char c;
1909
1910        while (isDelimiter((c = buf.nextChar()))) {
1911            if (c == 0) {
1912                break;
1913            }
1914            killed.append(c);
1915            delete();
1916        }
1917
1918        while (!isDelimiter((c = buf.nextChar()))) {
1919            if (c == 0) {
1920                break;
1921            }
1922            killed.append(c);
1923            delete();
1924        }
1925
1926        String copy = killed.toString();
1927        killRing.add(copy);
1928
1929        return true;
1930    }
1931
1932    private boolean capitalizeWord() throws IOException {
1933        boolean first = true;
1934        int i = 1;
1935        char c;
1936        while (buf.cursor + i  - 1< buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) {
1937            buf.buffer.setCharAt(buf.cursor + i - 1, first ? Character.toUpperCase(c) : Character.toLowerCase(c));
1938            first = false;
1939            i++;
1940        }
1941        drawBuffer();
1942        moveCursor(i - 1);
1943        return true;
1944    }
1945
1946    private boolean upCaseWord() throws IOException {
1947        int i = 1;
1948        char c;
1949        while (buf.cursor + i - 1 < buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) {
1950            buf.buffer.setCharAt(buf.cursor + i - 1, Character.toUpperCase(c));
1951            i++;
1952        }
1953        drawBuffer();
1954        moveCursor(i - 1);
1955        return true;
1956    }
1957
1958    private boolean downCaseWord() throws IOException {
1959        int i = 1;
1960        char c;
1961        while (buf.cursor + i - 1 < buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) {
1962            buf.buffer.setCharAt(buf.cursor + i - 1, Character.toLowerCase(c));
1963            i++;
1964        }
1965        drawBuffer();
1966        moveCursor(i - 1);
1967        return true;
1968    }
1969
1970    /**
1971     * Performs character transpose. The character prior to the cursor and the
1972     * character under the cursor are swapped and the cursor is advanced one
1973     * character unless you are already at the end of the line.
1974     *
1975     * @param count The number of times to perform the transpose
1976     * @return true if the operation succeeded, false otherwise (e.g. transpose
1977     *   cannot happen at the beginning of the line).
1978     * @throws IOException
1979     */
1980    private boolean transposeChars(int count) throws IOException {
1981        for (; count > 0; --count) {
1982            if (buf.cursor == 0 || buf.cursor == buf.buffer.length()) {
1983                return false;
1984            }
1985
1986            int first  = buf.cursor-1;
1987            int second = buf.cursor;
1988
1989            char tmp = buf.buffer.charAt (first);
1990            buf.buffer.setCharAt(first, buf.buffer.charAt(second));
1991            buf.buffer.setCharAt(second, tmp);
1992
1993            // This could be done more efficiently by only re-drawing at the end.
1994            moveInternal(-1);
1995            drawBuffer();
1996            moveInternal(2);
1997        }
1998
1999        return true;
2000    }
2001
2002    public boolean isKeyMap(String name) {
2003        // Current keymap.
2004        KeyMap map = consoleKeys.getKeys();
2005        KeyMap mapByName = consoleKeys.getKeyMaps().get(name);
2006
2007        if (mapByName == null)
2008            return false;
2009
2010        /*
2011         * This may not be safe to do, but there doesn't appear to be a
2012         * clean way to find this information out.
2013         */
2014        return map == mapByName;
2015    }
2016
2017
2018    /**
2019     * The equivalent of hitting &lt;RET&gt;.  The line is considered
2020     * complete and is returned.
2021     *
2022     * @return The completed line of text.
2023     * @throws IOException
2024     */
2025    public String accept() throws IOException {
2026        moveToEnd();
2027        println(); // output newline
2028        flush();
2029        return finishBuffer();
2030    }
2031
2032    private void abort() throws IOException {
2033        beep();
2034        buf.clear();
2035        println();
2036        redrawLine();
2037    }
2038
2039    /**
2040     * Move the cursor <i>where</i> characters.
2041     *
2042     * @param num   If less than 0, move abs(<i>where</i>) to the left, otherwise move <i>where</i> to the right.
2043     * @return      The number of spaces we moved
2044     */
2045    public int moveCursor(final int num) throws IOException {
2046        int where = num;
2047
2048        if ((buf.cursor == 0) && (where <= 0)) {
2049            return 0;
2050        }
2051
2052        if ((buf.cursor == buf.buffer.length()) && (where >= 0)) {
2053            return 0;
2054        }
2055
2056        if ((buf.cursor + where) < 0) {
2057            where = -buf.cursor;
2058        }
2059        else if ((buf.cursor + where) > buf.buffer.length()) {
2060            where = buf.buffer.length() - buf.cursor;
2061        }
2062
2063        moveInternal(where);
2064
2065        return where;
2066    }
2067
2068    /**
2069     * Move the cursor <i>where</i> characters, without checking the current buffer.
2070     *
2071     * @param where the number of characters to move to the right or left.
2072     */
2073    private void moveInternal(final int where) throws IOException {
2074        // debug ("move cursor " + where + " ("
2075        // + buf.cursor + " => " + (buf.cursor + where) + ")");
2076        buf.cursor += where;
2077
2078        if (terminal.isAnsiSupported()) {
2079            if (where < 0) {
2080                back(Math.abs(where));
2081            } else {
2082                int width = getTerminal().getWidth();
2083                int cursor = getCursorPosition();
2084                int oldLine = (cursor - where) / width;
2085                int newLine = cursor / width;
2086                if (newLine > oldLine) {
2087                    printAnsiSequence((newLine - oldLine) + "B");
2088                }
2089                printAnsiSequence(1 +(cursor % width) + "G");
2090            }
2091//            flush();
2092            return;
2093        }
2094
2095        char c;
2096
2097        if (where < 0) {
2098            int len = 0;
2099            for (int i = buf.cursor; i < buf.cursor - where; i++) {
2100                if (buf.buffer.charAt(i) == '\t') {
2101                    len += TAB_WIDTH;
2102                }
2103                else {
2104                    len++;
2105                }
2106            }
2107
2108            char chars[] = new char[len];
2109            Arrays.fill(chars, BACKSPACE);
2110            out.write(chars);
2111
2112            return;
2113        }
2114        else if (buf.cursor == 0) {
2115            return;
2116        }
2117        else if (mask != null) {
2118            c = mask;
2119        }
2120        else {
2121            print(buf.buffer.substring(buf.cursor - where, buf.cursor).toCharArray());
2122            return;
2123        }
2124
2125        // null character mask: don't output anything
2126        if (mask == NULL_MASK) {
2127            return;
2128        }
2129
2130        print(c, Math.abs(where));
2131    }
2132
2133    // FIXME: replace() is not used
2134
2135    public final boolean replace(final int num, final String replacement) {
2136        buf.buffer.replace(buf.cursor - num, buf.cursor, replacement);
2137        try {
2138            moveCursor(-num);
2139            drawBuffer(Math.max(0, num - replacement.length()));
2140            moveCursor(replacement.length());
2141        }
2142        catch (IOException e) {
2143            e.printStackTrace();
2144            return false;
2145        }
2146        return true;
2147    }
2148
2149    /**
2150     * Read a character from the console.
2151     *
2152     * @return the character, or -1 if an EOF is received.
2153     */
2154    public final int readCharacter() throws IOException {
2155        int c = reader.read();
2156        if (c >= 0) {
2157            Log.trace("Keystroke: ", c);
2158            // clear any echo characters
2159            if (terminal.isSupported()) {
2160                clearEcho(c);
2161            }
2162        }
2163        return c;
2164    }
2165
2166    /**
2167     * Clear the echoed characters for the specified character code.
2168     */
2169    private int clearEcho(final int c) throws IOException {
2170        // if the terminal is not echoing, then ignore
2171        if (!terminal.isEchoEnabled()) {
2172            return 0;
2173        }
2174
2175        // otherwise, clear
2176        int num = countEchoCharacters(c);
2177        back(num);
2178        drawBuffer(num);
2179
2180        return num;
2181    }
2182
2183    private int countEchoCharacters(final int c) {
2184        // tabs as special: we need to determine the number of spaces
2185        // to cancel based on what out current cursor position is
2186        if (c == 9) {
2187            int tabStop = 8; // will this ever be different?
2188            int position = getCursorPosition();
2189
2190            return tabStop - (position % tabStop);
2191        }
2192
2193        return getPrintableCharacters(c).length();
2194    }
2195
2196    /**
2197     * Return the number of characters that will be printed when the specified
2198     * character is echoed to the screen
2199     *
2200     * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
2201     */
2202    private StringBuilder getPrintableCharacters(final int ch) {
2203        StringBuilder sbuff = new StringBuilder();
2204
2205        if (ch >= 32) {
2206            if (ch < 127) {
2207                sbuff.append(ch);
2208            }
2209            else if (ch == 127) {
2210                sbuff.append('^');
2211                sbuff.append('?');
2212            }
2213            else {
2214                sbuff.append('M');
2215                sbuff.append('-');
2216
2217                if (ch >= (128 + 32)) {
2218                    if (ch < (128 + 127)) {
2219                        sbuff.append((char) (ch - 128));
2220                    }
2221                    else {
2222                        sbuff.append('^');
2223                        sbuff.append('?');
2224                    }
2225                }
2226                else {
2227                    sbuff.append('^');
2228                    sbuff.append((char) (ch - 128 + 64));
2229                }
2230            }
2231        }
2232        else {
2233            sbuff.append('^');
2234            sbuff.append((char) (ch + 64));
2235        }
2236
2237        return sbuff;
2238    }
2239
2240    public final int readCharacter(final char... allowed) throws IOException {
2241        // if we restrict to a limited set and the current character is not in the set, then try again.
2242        char c;
2243
2244        Arrays.sort(allowed); // always need to sort before binarySearch
2245
2246        while (Arrays.binarySearch(allowed, c = (char) readCharacter()) < 0) {
2247            // nothing
2248        }
2249
2250        return c;
2251    }
2252
2253    //
2254    // Key Bindings
2255    //
2256
2257    public static final String JLINE_COMPLETION_THRESHOLD = "jline.completion.threshold";
2258
2259    //
2260    // Line Reading
2261    //
2262
2263    /**
2264     * Read the next line and return the contents of the buffer.
2265     */
2266    public String readLine() throws IOException {
2267        return readLine((String) null);
2268    }
2269
2270    /**
2271     * Read the next line with the specified character mask. If null, then
2272     * characters will be echoed. If 0, then no characters will be echoed.
2273     */
2274    public String readLine(final Character mask) throws IOException {
2275        return readLine(null, mask);
2276    }
2277
2278    public String readLine(final String prompt) throws IOException {
2279        return readLine(prompt, null);
2280    }
2281
2282    /**
2283     * Sets the current keymap by name. Supported keymaps are "emacs",
2284     * "vi-insert", "vi-move".
2285     * @param name The name of the keymap to switch to
2286     * @return true if the keymap was set, or false if the keymap is
2287     *    not recognized.
2288     */
2289    public boolean setKeyMap(String name) {
2290        return consoleKeys.setKeyMap(name);
2291    }
2292
2293    /**
2294     * Returns the name of the current key mapping.
2295     * @return the name of the key mapping. This will be the canonical name
2296     *   of the current mode of the key map and may not reflect the name that
2297     *   was used with {@link #setKeyMap(String)}.
2298     */
2299    public String getKeyMap() {
2300        return consoleKeys.getKeys().getName();
2301    }
2302
2303    /**
2304     * Read a line from the <i>in</i> {@link InputStream}, and return the line
2305     * (without any trailing newlines).
2306     *
2307     * @param prompt    The prompt to issue to the console, may be null.
2308     * @return          A line that is read from the terminal, or null if there was null input (e.g., <i>CTRL-D</i>
2309     *                  was pressed).
2310     */
2311    public String readLine(String prompt, final Character mask) throws IOException {
2312        // prompt may be null
2313        // mask may be null
2314
2315        /*
2316         * This is the accumulator for VI-mode repeat count. That is, while in
2317         * move mode, if you type 30x it will delete 30 characters. This is
2318         * where the "30" is accumulated until the command is struck.
2319         */
2320        int repeatCount = 0;
2321
2322        // FIXME: This blows, each call to readLine will reset the console's state which doesn't seem very nice.
2323        this.mask = mask;
2324        if (prompt != null) {
2325            setPrompt(prompt);
2326        }
2327        else {
2328            prompt = getPrompt();
2329        }
2330
2331        try {
2332            if (!terminal.isSupported()) {
2333                beforeReadLine(prompt, mask);
2334            }
2335
2336            if (prompt != null && prompt.length() > 0) {
2337                out.write(prompt);
2338                out.flush();
2339            }
2340
2341            Stack<Character> pushBackChar = new Stack<Character>();
2342
2343            if (terminal.isAnsiSupported() && System.console() != null) {
2344                //detect the prompt length by reading the cursor position from the terminal
2345                //the real prompt length could differ from the simple prompt length due to
2346                //use of escape sequences:
2347                out.write("\033[6n");
2348                out.flush();
2349                StringBuilder input = new StringBuilder();
2350                while (true) {
2351                    int read;
2352                    while ((read = in.read()) != 'R') {
2353                        input.appendCodePoint(read);
2354                    }
2355                    input.appendCodePoint(read);
2356                    Matcher m = CURSOR_COLUMN_PATTERN.matcher(input);
2357                    if (m.matches()) {
2358                        promptLen = Integer.parseInt(m.group("column")) - 1;
2359                        String prefix = m.group("prefix");
2360                        for (int i = prefix.length() - 1; i >= 0; i--) {
2361                            pushBackChar.push(prefix.charAt(i));
2362                        }
2363                        break;
2364                    }
2365                }
2366            }
2367
2368            // if the terminal is unsupported, just use plain-java reading
2369            if (!terminal.isSupported()) {
2370                return readLineSimple();
2371            }
2372
2373            if (handleUserInterrupt && (terminal instanceof UnixTerminal)) {
2374                ((UnixTerminal) terminal).disableInterruptCharacter();
2375            }
2376
2377            String originalPrompt = this.prompt;
2378
2379            state = State.NORMAL;
2380
2381            boolean success = true;
2382
2383            StringBuilder sb = new StringBuilder();
2384            while (true) {
2385                int c = pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop ();
2386                if (c == -1) {
2387                    return null;
2388                }
2389                sb.appendCodePoint(c);
2390
2391                if (recording) {
2392                    macro += new String(new int[]{c}, 0, 1);
2393                }
2394
2395                Object o = getKeys().getBound( sb );
2396                /*
2397                 * The kill ring keeps record of whether or not the
2398                 * previous command was a yank or a kill. We reset
2399                 * that state here if needed.
2400                 */
2401                if (!recording && !(o instanceof KeyMap)) {
2402                    if (o != Operation.YANK_POP && o != Operation.YANK) {
2403                        killRing.resetLastYank();
2404                    }
2405                    if (o != Operation.KILL_LINE && o != Operation.KILL_WHOLE_LINE
2406                        && o != Operation.BACKWARD_KILL_WORD && o != Operation.KILL_WORD
2407                        && o != Operation.UNIX_LINE_DISCARD && o != Operation.UNIX_WORD_RUBOUT) {
2408                        killRing.resetLastKill();
2409                    }
2410                }
2411
2412                if (o == Operation.DO_LOWERCASE_VERSION) {
2413                    sb.setLength( sb.length() - 1);
2414                    sb.append( Character.toLowerCase( (char) c ));
2415                    o = getKeys().getBound( sb );
2416                }
2417
2418                /*
2419                 * A KeyMap indicates that the key that was struck has a
2420                 * number of keys that can follow it as indicated in the
2421                 * map. This is used primarily for Emacs style ESC-META-x
2422                 * lookups. Since more keys must follow, go back to waiting
2423                 * for the next key.
2424                 */
2425                if ( o instanceof KeyMap ) {
2426                    /*
2427                     * The ESC key (#27) is special in that it is ambiguous until
2428                     * you know what is coming next.  The ESC could be a literal
2429                     * escape, like the user entering vi-move mode, or it could
2430                     * be part of a terminal control sequence.  The following
2431                     * logic attempts to disambiguate things in the same
2432                     * fashion as regular vi or readline.
2433                     *
2434                     * When ESC is encountered and there is no other pending
2435                     * character in the pushback queue, then attempt to peek
2436                     * into the input stream (if the feature is enabled) for
2437                     * 150ms. If nothing else is coming, then assume it is
2438                     * not a terminal control sequence, but a raw escape.
2439                     */
2440                    if (c == 27
2441                            && pushBackChar.isEmpty()
2442                            && in.isNonBlockingEnabled()
2443                            && in.peek(escapeTimeout) == -2) {
2444                        o = ((KeyMap) o).getAnotherKey();
2445                        if (o == null || o instanceof KeyMap) {
2446                            continue;
2447                        }
2448                        sb.setLength(0);
2449                    }
2450                    else {
2451                        continue;
2452                    }
2453                }
2454
2455                /*
2456                 * If we didn't find a binding for the key and there is
2457                 * more than one character accumulated then start checking
2458                 * the largest span of characters from the beginning to
2459                 * see if there is a binding for them.
2460                 *
2461                 * For example if our buffer has ESC,CTRL-M,C the getBound()
2462                 * called previously indicated that there is no binding for
2463                 * this sequence, so this then checks ESC,CTRL-M, and failing
2464                 * that, just ESC. Each keystroke that is pealed off the end
2465                 * during these tests is stuffed onto the pushback buffer so
2466                 * they won't be lost.
2467                 *
2468                 * If there is no binding found, then we go back to waiting for
2469                 * input.
2470                 */
2471                while ( o == null && sb.length() > 0 ) {
2472                    c = sb.charAt( sb.length() - 1 );
2473                    sb.setLength( sb.length() - 1 );
2474                    Object o2 = getKeys().getBound( sb );
2475                    if ( o2 instanceof KeyMap ) {
2476                        o = ((KeyMap) o2).getAnotherKey();
2477                        if ( o == null ) {
2478                            continue;
2479                        } else {
2480                            pushBackChar.push( (char) c );
2481                        }
2482                    }
2483                }
2484
2485                if ( o == null ) {
2486                    continue;
2487                }
2488                Log.trace("Binding: ", o);
2489
2490
2491                // Handle macros
2492                if (o instanceof String) {
2493                    String macro = (String) o;
2494                    for (int i = 0; i < macro.length(); i++) {
2495                        pushBackChar.push(macro.charAt(macro.length() - 1 - i));
2496                    }
2497                    sb.setLength( 0 );
2498                    continue;
2499                }
2500
2501                // Handle custom callbacks
2502                //original code:
2503//                if (o instanceof ActionListener) {
2504//                    ((ActionListener) o).actionPerformed(null);
2505//                    sb.setLength( 0 );
2506//                    continue;
2507//                }
2508                //using reflection to avoid dependency on java.desktop:
2509                try {
2510                    Class<?> actionListener =
2511                            Class.forName("java.awt.event.ActionListener", false, ClassLoader.getSystemClassLoader());
2512                    Class<?> actionEvent =
2513                            Class.forName("java.awt.event.ActionEvent", false, ClassLoader.getSystemClassLoader());
2514                    if (actionListener.isAssignableFrom(o.getClass())) {
2515                        Method actionPerformed =
2516                                actionListener.getMethod("actionPerformed", actionEvent);
2517                        try {
2518                            actionPerformed.invoke(o, (Object) null);
2519                        } catch (InvocationTargetException ex ) {
2520                            Log.error("Exception while running registered action", ex);
2521                        }
2522                        sb.setLength( 0 );
2523                        continue;
2524                    }
2525                } catch (ReflectiveOperationException ex) {
2526                    //ignore
2527                }
2528
2529                if (o instanceof Runnable) {
2530                    ((Runnable) o).run();
2531                    sb.setLength(0);
2532                    continue;
2533                }
2534
2535                // Search mode.
2536                //
2537                // Note that we have to do this first, because if there is a command
2538                // not linked to a search command, we leave the search mode and fall
2539                // through to the normal state.
2540                if (state == State.SEARCH || state == State.FORWARD_SEARCH) {
2541                    int cursorDest = -1;
2542                    switch ( ((Operation) o )) {
2543                        case ABORT:
2544                            state = State.NORMAL;
2545                            buf.clear();
2546                            buf.buffer.append(searchTerm);
2547                            break;
2548
2549                        case REVERSE_SEARCH_HISTORY:
2550                            state = State.SEARCH;
2551                            if (searchTerm.length() == 0) {
2552                                searchTerm.append(previousSearchTerm);
2553                            }
2554
2555                            if (searchIndex > 0) {
2556                                searchIndex = searchBackwards(searchTerm.toString(), searchIndex);
2557                            }
2558                            break;
2559
2560                        case FORWARD_SEARCH_HISTORY:
2561                            state = State.FORWARD_SEARCH;
2562                            if (searchTerm.length() == 0) {
2563                                searchTerm.append(previousSearchTerm);
2564                            }
2565
2566                            if (searchIndex > -1 && searchIndex < history.size() - 1) {
2567                                searchIndex = searchForwards(searchTerm.toString(), searchIndex);
2568                            }
2569                            break;
2570
2571                        case BACKWARD_DELETE_CHAR:
2572                            if (searchTerm.length() > 0) {
2573                                searchTerm.deleteCharAt(searchTerm.length() - 1);
2574                                if (state == State.SEARCH) {
2575                                    searchIndex = searchBackwards(searchTerm.toString());
2576                                } else {
2577                                    searchIndex = searchForwards(searchTerm.toString());
2578                                }
2579                            }
2580                            break;
2581
2582                        case SELF_INSERT:
2583                            searchTerm.appendCodePoint(c);
2584                            if (state == State.SEARCH) {
2585                                searchIndex = searchBackwards(searchTerm.toString());
2586                            } else {
2587                                searchIndex = searchForwards(searchTerm.toString());
2588                            }
2589                            break;
2590
2591                        default:
2592                            // Set buffer and cursor position to the found string.
2593                            if (searchIndex != -1) {
2594                                history.moveTo(searchIndex);
2595                                // set cursor position to the found string
2596                                cursorDest = history.current().toString().indexOf(searchTerm.toString());
2597                            }
2598                            state = State.NORMAL;
2599                            break;
2600                    }
2601
2602                    // if we're still in search mode, print the search status
2603                    if (state == State.SEARCH || state == State.FORWARD_SEARCH) {
2604                        if (searchTerm.length() == 0) {
2605                            if (state == State.SEARCH) {
2606                                printSearchStatus("", "");
2607                            } else {
2608                                printForwardSearchStatus("", "");
2609                            }
2610                            searchIndex = -1;
2611                        } else {
2612                            if (searchIndex == -1) {
2613                                beep();
2614                                printSearchStatus(searchTerm.toString(), "");
2615                            } else if (state == State.SEARCH) {
2616                                printSearchStatus(searchTerm.toString(), history.get(searchIndex).toString());
2617                            } else {
2618                                printForwardSearchStatus(searchTerm.toString(), history.get(searchIndex).toString());
2619                            }
2620                        }
2621                    }
2622                    // otherwise, restore the line
2623                    else {
2624                        restoreLine(originalPrompt, cursorDest);
2625                    }
2626                }
2627                if (state != State.SEARCH && state != State.FORWARD_SEARCH) {
2628                    /*
2629                     * If this is still false at the end of the switch, then
2630                     * we reset our repeatCount to 0.
2631                     */
2632                    boolean isArgDigit = false;
2633
2634                    /*
2635                     * Every command that can be repeated a specified number
2636                     * of times, needs to know how many times to repeat, so
2637                     * we figure that out here.
2638                     */
2639                    int count = (repeatCount == 0) ? 1 : repeatCount;
2640
2641                    /*
2642                     * Default success to true. You only need to explicitly
2643                     * set it if something goes wrong.
2644                     */
2645                    success = true;
2646
2647                    if (o instanceof Operation) {
2648                        Operation op = (Operation)o;
2649                        /*
2650                         * Current location of the cursor (prior to the operation).
2651                         * These are used by vi *-to operation (e.g. delete-to)
2652                         * so we know where we came from.
2653                         */
2654                        int     cursorStart = buf.cursor;
2655                        State   origState   = state;
2656
2657                        /*
2658                         * If we are on a "vi" movement based operation, then we
2659                         * need to restrict the sets of inputs pretty heavily.
2660                         */
2661                        if (state == State.VI_CHANGE_TO
2662                            || state == State.VI_YANK_TO
2663                            || state == State.VI_DELETE_TO) {
2664
2665                            op = viDeleteChangeYankToRemap(op);
2666                        }
2667
2668                        switch ( op ) {
2669                            case COMPLETE: // tab
2670                                // There is an annoyance with tab completion in that
2671                                // sometimes the user is actually pasting input in that
2672                                // has physical tabs in it.  This attempts to look at how
2673                                // quickly a character follows the tab, if the character
2674                                // follows *immediately*, we assume it is a tab literal.
2675                                boolean isTabLiteral = false;
2676                                if (copyPasteDetection
2677                                    && c == 9
2678                                    && (!pushBackChar.isEmpty()
2679                                        || (in.isNonBlockingEnabled() && in.peek(escapeTimeout) != -2))) {
2680                                    isTabLiteral = true;
2681                                }
2682
2683                                if (! isTabLiteral) {
2684                                    success = complete();
2685                                }
2686                                else {
2687                                    putString(sb);
2688                                }
2689                                break;
2690
2691                            case POSSIBLE_COMPLETIONS:
2692                                printCompletionCandidates();
2693                                break;
2694
2695                            case BEGINNING_OF_LINE:
2696                                success = setCursorPosition(0);
2697                                break;
2698
2699                            case YANK:
2700                                success = yank();
2701                                break;
2702
2703                            case YANK_POP:
2704                                success = yankPop();
2705                                break;
2706
2707                            case KILL_LINE: // CTRL-K
2708                                success = killLine();
2709                                break;
2710
2711                            case KILL_WHOLE_LINE:
2712                                success = setCursorPosition(0) && killLine();
2713                                break;
2714
2715                            case CLEAR_SCREEN: // CTRL-L
2716                                success = clearScreen();
2717                                redrawLine();
2718                                break;
2719
2720                            case OVERWRITE_MODE:
2721                                buf.setOverTyping(!buf.isOverTyping());
2722                                break;
2723
2724                            case SELF_INSERT:
2725                                putString(sb);
2726                                break;
2727
2728                            case ACCEPT_LINE:
2729                                return accept();
2730
2731                            case ABORT:
2732                                if (searchTerm == null) {
2733                                    abort();
2734                                }
2735                                break;
2736
2737                            case INTERRUPT:
2738                                if (handleUserInterrupt) {
2739                                    println();
2740                                    flush();
2741                                    String partialLine = buf.buffer.toString();
2742                                    buf.clear();
2743                                    history.moveToEnd();
2744                                    throw new UserInterruptException(partialLine);
2745                                }
2746                                break;
2747
2748                            /*
2749                             * VI_MOVE_ACCEPT_LINE is the result of an ENTER
2750                             * while in move mode. This is the same as a normal
2751                             * ACCEPT_LINE, except that we need to enter
2752                             * insert mode as well.
2753                             */
2754                            case VI_MOVE_ACCEPT_LINE:
2755                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2756                                return accept();
2757
2758                            case BACKWARD_WORD:
2759                                success = previousWord();
2760                                break;
2761
2762                            case FORWARD_WORD:
2763                                success = nextWord();
2764                                break;
2765
2766                            case PREVIOUS_HISTORY:
2767                                success = moveHistory(false);
2768                                break;
2769
2770                            /*
2771                             * According to bash/readline move through history
2772                             * in "vi" mode will move the cursor to the
2773                             * start of the line. If there is no previous
2774                             * history, then the cursor doesn't move.
2775                             */
2776                            case VI_PREVIOUS_HISTORY:
2777                                success = moveHistory(false, count)
2778                                    && setCursorPosition(0);
2779                                break;
2780
2781                            case NEXT_HISTORY:
2782                                success = moveHistory(true);
2783                                break;
2784
2785                            /*
2786                             * According to bash/readline move through history
2787                             * in "vi" mode will move the cursor to the
2788                             * start of the line. If there is no next history,
2789                             * then the cursor doesn't move.
2790                             */
2791                            case VI_NEXT_HISTORY:
2792                                success = moveHistory(true, count)
2793                                    && setCursorPosition(0);
2794                                break;
2795
2796                            case BACKWARD_DELETE_CHAR: // backspace
2797                                success = backspace();
2798                                break;
2799
2800                            case EXIT_OR_DELETE_CHAR:
2801                                if (buf.buffer.length() == 0) {
2802                                    return null;
2803                                }
2804                                success = deleteCurrentCharacter();
2805                                break;
2806
2807                            case DELETE_CHAR: // delete
2808                                success = deleteCurrentCharacter();
2809                                break;
2810
2811                            case BACKWARD_CHAR:
2812                                success = moveCursor(-(count)) != 0;
2813                                break;
2814
2815                            case FORWARD_CHAR:
2816                                success = moveCursor(count) != 0;
2817                                break;
2818
2819                            case UNIX_LINE_DISCARD:
2820                                success = resetLine();
2821                                break;
2822
2823                            case UNIX_WORD_RUBOUT:
2824                                success = unixWordRubout(count);
2825                                break;
2826
2827                            case BACKWARD_KILL_WORD:
2828                                success = deletePreviousWord();
2829                                break;
2830
2831                            case KILL_WORD:
2832                                success = deleteNextWord();
2833                                break;
2834
2835                            case BEGINNING_OF_HISTORY:
2836                                success = history.moveToFirst();
2837                                if (success) {
2838                                    setBuffer(history.current());
2839                                }
2840                                break;
2841
2842                            case END_OF_HISTORY:
2843                                success = history.moveToLast();
2844                                if (success) {
2845                                    setBuffer(history.current());
2846                                }
2847                                break;
2848
2849                            case HISTORY_SEARCH_BACKWARD:
2850                                searchTerm = new StringBuffer(buf.upToCursor());
2851                                searchIndex = searchBackwards(searchTerm.toString(), history.index(), true);
2852
2853                                if (searchIndex == -1) {
2854                                    beep();
2855                                } else {
2856                                    // Maintain cursor position while searching.
2857                                    success = history.moveTo(searchIndex);
2858                                    if (success) {
2859                                        setBufferKeepPos(history.current());
2860                                    }
2861                                }
2862                                break;
2863
2864                            case HISTORY_SEARCH_FORWARD:
2865                                searchTerm = new StringBuffer(buf.upToCursor());
2866                                int index = history.index() + 1;
2867
2868                                if (index == history.size()) {
2869                                    history.moveToEnd();
2870                                    setBufferKeepPos(searchTerm.toString());
2871                                } else if (index < history.size()) {
2872                                    searchIndex = searchForwards(searchTerm.toString(), index, true);
2873                                    if (searchIndex == -1) {
2874                                        beep();
2875                                    } else {
2876                                        // Maintain cursor position while searching.
2877                                        success = history.moveTo(searchIndex);
2878                                        if (success) {
2879                                            setBufferKeepPos(history.current());
2880                                        }
2881                                    }
2882                                }
2883                                break;
2884
2885                            case REVERSE_SEARCH_HISTORY:
2886                                if (searchTerm != null) {
2887                                    previousSearchTerm = searchTerm.toString();
2888                                }
2889                                searchTerm = new StringBuffer(buf.buffer);
2890                                state = State.SEARCH;
2891                                if (searchTerm.length() > 0) {
2892                                    searchIndex = searchBackwards(searchTerm.toString());
2893                                    if (searchIndex == -1) {
2894                                        beep();
2895                                    }
2896                                    printSearchStatus(searchTerm.toString(),
2897                                            searchIndex > -1 ? history.get(searchIndex).toString() : "");
2898                                } else {
2899                                    searchIndex = -1;
2900                                    printSearchStatus("", "");
2901                                }
2902                                break;
2903
2904                            case FORWARD_SEARCH_HISTORY:
2905                                if (searchTerm != null) {
2906                                    previousSearchTerm = searchTerm.toString();
2907                                }
2908                                searchTerm = new StringBuffer(buf.buffer);
2909                                state = State.FORWARD_SEARCH;
2910                                if (searchTerm.length() > 0) {
2911                                    searchIndex = searchForwards(searchTerm.toString());
2912                                    if (searchIndex == -1) {
2913                                        beep();
2914                                    }
2915                                    printForwardSearchStatus(searchTerm.toString(),
2916                                            searchIndex > -1 ? history.get(searchIndex).toString() : "");
2917                                } else {
2918                                    searchIndex = -1;
2919                                    printForwardSearchStatus("", "");
2920                                }
2921                                break;
2922
2923                            case CAPITALIZE_WORD:
2924                                success = capitalizeWord();
2925                                break;
2926
2927                            case UPCASE_WORD:
2928                                success = upCaseWord();
2929                                break;
2930
2931                            case DOWNCASE_WORD:
2932                                success = downCaseWord();
2933                                break;
2934
2935                            case END_OF_LINE:
2936                                success = moveToEnd();
2937                                break;
2938
2939                            case TAB_INSERT:
2940                                putString( "\t" );
2941                                break;
2942
2943                            case RE_READ_INIT_FILE:
2944                                consoleKeys.loadKeys(appName, inputrcUrl);
2945                                break;
2946
2947                            case START_KBD_MACRO:
2948                                recording = true;
2949                                break;
2950
2951                            case END_KBD_MACRO:
2952                                recording = false;
2953                                macro = macro.substring(0, macro.length() - sb.length());
2954                                break;
2955
2956                            case CALL_LAST_KBD_MACRO:
2957                                for (int i = 0; i < macro.length(); i++) {
2958                                    pushBackChar.push(macro.charAt(macro.length() - 1 - i));
2959                                }
2960                                sb.setLength( 0 );
2961                                break;
2962
2963                            case VI_EDITING_MODE:
2964                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2965                                break;
2966
2967                            case VI_MOVEMENT_MODE:
2968                                /*
2969                                 * If we are re-entering move mode from an
2970                                 * aborted yank-to, delete-to, change-to then
2971                                 * don't move the cursor back. The cursor is
2972                                 * only move on an expclit entry to movement
2973                                 * mode.
2974                                 */
2975                                if (state == State.NORMAL) {
2976                                    moveCursor(-1);
2977                                }
2978                                consoleKeys.setKeyMap(KeyMap.VI_MOVE);
2979                                break;
2980
2981                            case VI_INSERTION_MODE:
2982                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2983                                break;
2984
2985                            case VI_APPEND_MODE:
2986                                moveCursor(1);
2987                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2988                                break;
2989
2990                            case VI_APPEND_EOL:
2991                                success = moveToEnd();
2992                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2993                                break;
2994
2995                            /*
2996                             * Handler for CTRL-D. Attempts to follow readline
2997                             * behavior. If the line is empty, then it is an EOF
2998                             * otherwise it is as if the user hit enter.
2999                             */
3000                            case VI_EOF_MAYBE:
3001                                if (buf.buffer.length() == 0) {
3002                                    return null;
3003                                }
3004                                return accept();
3005
3006                            case TRANSPOSE_CHARS:
3007                                success = transposeChars(count);
3008                                break;
3009
3010                            case INSERT_COMMENT:
3011                                return insertComment (false);
3012
3013                            case INSERT_CLOSE_CURLY:
3014                                insertClose("}");
3015                                break;
3016
3017                            case INSERT_CLOSE_PAREN:
3018                                insertClose(")");
3019                                break;
3020
3021                            case INSERT_CLOSE_SQUARE:
3022                                insertClose("]");
3023                                break;
3024
3025                            case VI_INSERT_COMMENT:
3026                                return insertComment (true);
3027
3028                            case VI_MATCH:
3029                                success = viMatch ();
3030                                break;
3031
3032                            case VI_SEARCH:
3033                                int lastChar = viSearch(sb.charAt (0));
3034                                if (lastChar != -1) {
3035                                    pushBackChar.push((char)lastChar);
3036                                }
3037                                break;
3038
3039                            case VI_ARG_DIGIT:
3040                                repeatCount = (repeatCount * 10) + sb.charAt(0) - '0';
3041                                isArgDigit = true;
3042                                break;
3043
3044                            case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
3045                                if (repeatCount > 0) {
3046                                    repeatCount = (repeatCount * 10) + sb.charAt(0) - '0';
3047                                    isArgDigit = true;
3048                                }
3049                                else {
3050                                    success = setCursorPosition(0);
3051                                }
3052                                break;
3053
3054                            case VI_FIRST_PRINT:
3055                                success = setCursorPosition(0) && viNextWord(1);
3056                                break;
3057
3058                            case VI_PREV_WORD:
3059                                success = viPreviousWord(count);
3060                                break;
3061
3062                            case VI_NEXT_WORD:
3063                                success = viNextWord(count);
3064                                break;
3065
3066                            case VI_END_WORD:
3067                                success = viEndWord(count);
3068                                break;
3069
3070                            case VI_INSERT_BEG:
3071                                success = setCursorPosition(0);
3072                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3073                                break;
3074
3075                            case VI_RUBOUT:
3076                                success = viRubout(count);
3077                                break;
3078
3079                            case VI_DELETE:
3080                                success = viDelete(count);
3081                                break;
3082
3083                            case VI_DELETE_TO:
3084                                /*
3085                                 * This is a weird special case. In vi
3086                                 * "dd" deletes the current line. So if we
3087                                 * get a delete-to, followed by a delete-to,
3088                                 * we delete the line.
3089                                 */
3090                                if (state == State.VI_DELETE_TO) {
3091                                    success = setCursorPosition(0) && killLine();
3092                                    state = origState = State.NORMAL;
3093                                }
3094                                else {
3095                                    state = State.VI_DELETE_TO;
3096                                }
3097                                break;
3098
3099                            case VI_YANK_TO:
3100                                // Similar to delete-to, a "yy" yanks the whole line.
3101                                if (state == State.VI_YANK_TO) {
3102                                    yankBuffer = buf.buffer.toString();
3103                                    state = origState = State.NORMAL;
3104                                }
3105                                else {
3106                                    state = State.VI_YANK_TO;
3107                                }
3108                                break;
3109
3110                            case VI_CHANGE_TO:
3111                                if (state == State.VI_CHANGE_TO) {
3112                                    success = setCursorPosition(0) && killLine();
3113                                    state = origState = State.NORMAL;
3114                                    consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3115                                }
3116                                else {
3117                                    state = State.VI_CHANGE_TO;
3118                                }
3119                                break;
3120
3121                            case VI_KILL_WHOLE_LINE:
3122                                success = setCursorPosition(0) && killLine();
3123                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3124                                break;
3125
3126                            case VI_PUT:
3127                                success = viPut(count);
3128                                break;
3129
3130                            case VI_CHAR_SEARCH: {
3131                                 // ';' and ',' don't need another character. They indicate repeat next or repeat prev.
3132                                int searchChar = (c != ';' && c != ',')
3133                                    ? (pushBackChar.isEmpty()
3134                                        ? readCharacter()
3135                                        : pushBackChar.pop ())
3136                                    : 0;
3137
3138                                    success = viCharSearch(count, c, searchChar);
3139                                }
3140                                break;
3141
3142                            case VI_CHANGE_CASE:
3143                                success = viChangeCase(count);
3144                                break;
3145
3146                            case VI_CHANGE_CHAR:
3147                                success = viChangeChar(count,
3148                                    pushBackChar.isEmpty()
3149                                        ? readCharacter()
3150                                        : pushBackChar.pop());
3151                                break;
3152
3153                            case VI_DELETE_TO_EOL:
3154                                success = viDeleteTo(buf.cursor, buf.buffer.length(), false);
3155                                break;
3156
3157                            case VI_CHANGE_TO_EOL:
3158                                success = viDeleteTo(buf.cursor, buf.buffer.length(), true);
3159                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3160                                break;
3161
3162                            case EMACS_EDITING_MODE:
3163                                consoleKeys.setKeyMap(KeyMap.EMACS);
3164                                break;
3165
3166                            default:
3167                                break;
3168                        }
3169
3170                        /*
3171                         * If we were in a yank-to, delete-to, move-to
3172                         * when this operation started, then fall back to
3173                         */
3174                        if (origState != State.NORMAL) {
3175                            if (origState == State.VI_DELETE_TO) {
3176                                success = viDeleteTo(cursorStart, buf.cursor, false);
3177                            }
3178                            else if (origState == State.VI_CHANGE_TO) {
3179                                success = viDeleteTo(cursorStart, buf.cursor, true);
3180                                consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3181                            }
3182                            else if (origState == State.VI_YANK_TO) {
3183                                success = viYankTo(cursorStart, buf.cursor);
3184                            }
3185                            state = State.NORMAL;
3186                        }
3187
3188                        /*
3189                         * Another subtly. The check for the NORMAL state is
3190                         * to ensure that we do not clear out the repeat
3191                         * count when in delete-to, yank-to, or move-to modes.
3192                         */
3193                        if (state == State.NORMAL && !isArgDigit) {
3194                            /*
3195                             * If the operation performed wasn't a vi argument
3196                             * digit, then clear out the current repeatCount;
3197                             */
3198                            repeatCount = 0;
3199                        }
3200
3201                        if (state != State.SEARCH && state != State.FORWARD_SEARCH) {
3202                            previousSearchTerm = "";
3203                            searchTerm = null;
3204                            searchIndex = -1;
3205                        }
3206                    }
3207                }
3208                if (!success) {
3209                    beep();
3210                }
3211                sb.setLength( 0 );
3212                flush();
3213            }
3214        }
3215        finally {
3216            if (!terminal.isSupported()) {
3217                afterReadLine();
3218            }
3219            if (handleUserInterrupt && (terminal instanceof UnixTerminal)) {
3220                ((UnixTerminal) terminal).enableInterruptCharacter();
3221            }
3222        }
3223    }
3224    //where:
3225        private Pattern CURSOR_COLUMN_PATTERN =
3226                Pattern.compile("(?<prefix>.*)\033\\[[0-9]+;(?<column>[0-9]+)R", Pattern.DOTALL);
3227
3228    /**
3229     * Read a line for unsupported terminals.
3230     */
3231    private String readLineSimple() throws IOException {
3232        StringBuilder buff = new StringBuilder();
3233
3234        if (skipLF) {
3235            skipLF = false;
3236
3237            int i = readCharacter();
3238
3239            if (i == -1 || i == '\r') {
3240                return buff.toString();
3241            } else if (i == '\n') {
3242                // ignore
3243            } else {
3244                buff.append((char) i);
3245            }
3246        }
3247
3248        while (true) {
3249            int i = readCharacter();
3250
3251            if (i == -1 && buff.length() == 0) {
3252              return null;
3253            }
3254
3255            if (i == -1 || i == '\n') {
3256                return buff.toString();
3257            } else if (i == '\r') {
3258                skipLF = true;
3259                return buff.toString();
3260            } else {
3261                buff.append((char) i);
3262            }
3263        }
3264    }
3265
3266    //
3267    // Completion
3268    //
3269
3270    private final List<Completer> completers = new LinkedList<Completer>();
3271
3272    private CompletionHandler completionHandler = new CandidateListCompletionHandler();
3273
3274    /**
3275     * Add the specified {@link jline.console.completer.Completer} to the list of handlers for tab-completion.
3276     *
3277     * @param completer the {@link jline.console.completer.Completer} to add
3278     * @return true if it was successfully added
3279     */
3280    public boolean addCompleter(final Completer completer) {
3281        return completers.add(completer);
3282    }
3283
3284    /**
3285     * Remove the specified {@link jline.console.completer.Completer} from the list of handlers for tab-completion.
3286     *
3287     * @param completer     The {@link Completer} to remove
3288     * @return              True if it was successfully removed
3289     */
3290    public boolean removeCompleter(final Completer completer) {
3291        return completers.remove(completer);
3292    }
3293
3294    /**
3295     * Returns an unmodifiable list of all the completers.
3296     */
3297    public Collection<Completer> getCompleters() {
3298        return Collections.unmodifiableList(completers);
3299    }
3300
3301    public void setCompletionHandler(final CompletionHandler handler) {
3302        this.completionHandler = checkNotNull(handler);
3303    }
3304
3305    public CompletionHandler getCompletionHandler() {
3306        return this.completionHandler;
3307    }
3308
3309    /**
3310     * Use the completers to modify the buffer with the appropriate completions.
3311     *
3312     * @return true if successful
3313     */
3314    protected boolean complete() throws IOException {
3315        // debug ("tab for (" + buf + ")");
3316        if (completers.size() == 0) {
3317            return false;
3318        }
3319
3320        List<CharSequence> candidates = new LinkedList<CharSequence>();
3321        String bufstr = buf.buffer.toString();
3322        int cursor = buf.cursor;
3323
3324        int position = -1;
3325
3326        for (Completer comp : completers) {
3327            if ((position = comp.complete(bufstr, cursor, candidates)) != -1) {
3328                break;
3329            }
3330        }
3331
3332        return candidates.size() != 0 && getCompletionHandler().complete(this, candidates, position);
3333    }
3334
3335    protected void printCompletionCandidates() throws IOException {
3336        // debug ("tab for (" + buf + ")");
3337        if (completers.size() == 0) {
3338            return;
3339        }
3340
3341        List<CharSequence> candidates = new LinkedList<CharSequence>();
3342        String bufstr = buf.buffer.toString();
3343        int cursor = buf.cursor;
3344
3345        for (Completer comp : completers) {
3346            if (comp.complete(bufstr, cursor, candidates) != -1) {
3347                break;
3348            }
3349        }
3350        CandidateListCompletionHandler.printCandidates(this, candidates);
3351        drawLine();
3352    }
3353
3354    /**
3355     * The number of tab-completion candidates above which a warning will be
3356     * prompted before showing all the candidates.
3357     */
3358    private int autoprintThreshold = Configuration.getInteger(JLINE_COMPLETION_THRESHOLD, 100); // same default as bash
3359
3360    /**
3361     * @param threshold the number of candidates to print without issuing a warning.
3362     */
3363    public void setAutoprintThreshold(final int threshold) {
3364        this.autoprintThreshold = threshold;
3365    }
3366
3367    /**
3368     * @return the number of candidates to print without issuing a warning.
3369     */
3370    public int getAutoprintThreshold() {
3371        return autoprintThreshold;
3372    }
3373
3374    private boolean paginationEnabled;
3375
3376    /**
3377     * Whether to use pagination when the number of rows of candidates exceeds the height of the terminal.
3378     */
3379    public void setPaginationEnabled(final boolean enabled) {
3380        this.paginationEnabled = enabled;
3381    }
3382
3383    /**
3384     * Whether to use pagination when the number of rows of candidates exceeds the height of the terminal.
3385     */
3386    public boolean isPaginationEnabled() {
3387        return paginationEnabled;
3388    }
3389
3390    //
3391    // History
3392    //
3393
3394    private History history = new MemoryHistory();
3395
3396    public void setHistory(final History history) {
3397        this.history = history;
3398    }
3399
3400    public History getHistory() {
3401        return history;
3402    }
3403
3404    private boolean historyEnabled = true;
3405
3406    /**
3407     * Whether or not to add new commands to the history buffer.
3408     */
3409    public void setHistoryEnabled(final boolean enabled) {
3410        this.historyEnabled = enabled;
3411    }
3412
3413    /**
3414     * Whether or not to add new commands to the history buffer.
3415     */
3416    public boolean isHistoryEnabled() {
3417        return historyEnabled;
3418    }
3419
3420    /**
3421     * Used in "vi" mode for argumented history move, to move a specific
3422     * number of history entries forward or back.
3423     *
3424     * @param next If true, move forward
3425     * @param count The number of entries to move
3426     * @return true if the move was successful
3427     * @throws IOException
3428     */
3429    private boolean moveHistory(final boolean next, int count) throws IOException {
3430        boolean ok = true;
3431        for (int i = 0; i < count && (ok = moveHistory(next)); i++) {
3432            /* empty */
3433        }
3434        return ok;
3435    }
3436
3437    /**
3438     * Move up or down the history tree.
3439     */
3440    private boolean moveHistory(final boolean next) throws IOException {
3441        if (next && !history.next()) {
3442            return false;
3443        }
3444        else if (!next && !history.previous()) {
3445            return false;
3446        }
3447
3448        setBuffer(history.current());
3449
3450        return true;
3451    }
3452
3453    //
3454    // Printing
3455    //
3456
3457    public static final String CR = Configuration.getLineSeparator();
3458
3459    /**
3460     * Output the specified character to the output stream without manipulating the current buffer.
3461     */
3462    private void print(final int c) throws IOException {
3463        if (c == '\t') {
3464            char chars[] = new char[TAB_WIDTH];
3465            Arrays.fill(chars, ' ');
3466            out.write(chars);
3467            return;
3468        }
3469
3470        out.write(c);
3471    }
3472
3473    /**
3474     * Output the specified characters to the output stream without manipulating the current buffer.
3475     */
3476    private void print(final char... buff) throws IOException {
3477        int len = 0;
3478        for (char c : buff) {
3479            if (c == '\t') {
3480                len += TAB_WIDTH;
3481            }
3482            else {
3483                len++;
3484            }
3485        }
3486
3487        char chars[];
3488        if (len == buff.length) {
3489            chars = buff;
3490        }
3491        else {
3492            chars = new char[len];
3493            int pos = 0;
3494            for (char c : buff) {
3495                if (c == '\t') {
3496                    Arrays.fill(chars, pos, pos + TAB_WIDTH, ' ');
3497                    pos += TAB_WIDTH;
3498                }
3499                else {
3500                    chars[pos] = c;
3501                    pos++;
3502                }
3503            }
3504        }
3505
3506        out.write(chars);
3507    }
3508
3509    private void print(final char c, final int num) throws IOException {
3510        if (num == 1) {
3511            print(c);
3512        }
3513        else {
3514            char[] chars = new char[num];
3515            Arrays.fill(chars, c);
3516            print(chars);
3517        }
3518    }
3519
3520    /**
3521     * Output the specified string to the output stream (but not the buffer).
3522     */
3523    public final void print(final CharSequence s) throws IOException {
3524        print(checkNotNull(s).toString().toCharArray());
3525    }
3526
3527    public final void println(final CharSequence s) throws IOException {
3528        print(checkNotNull(s).toString().toCharArray());
3529        println();
3530    }
3531
3532    /**
3533     * Output a platform-dependant newline.
3534     */
3535    public final void println() throws IOException {
3536        print(CR);
3537//        flush();
3538    }
3539
3540    //
3541    // Actions
3542    //
3543
3544    /**
3545     * Issue a delete.
3546     *
3547     * @return true if successful
3548     */
3549    public final boolean delete() throws IOException {
3550        if (buf.cursor == buf.buffer.length()) {
3551          return false;
3552        }
3553
3554        buf.buffer.delete(buf.cursor, buf.cursor + 1);
3555        drawBuffer(1);
3556
3557        return true;
3558    }
3559
3560    /**
3561     * Kill the buffer ahead of the current cursor position.
3562     *
3563     * @return true if successful
3564     */
3565    public boolean killLine() throws IOException {
3566        int cp = buf.cursor;
3567        int len = buf.buffer.length();
3568
3569        if (cp >= len) {
3570            return false;
3571        }
3572
3573        int num = len - cp;
3574        clearAhead(num, 0);
3575
3576        char[] killed = new char[num];
3577        buf.buffer.getChars(cp, (cp + num), killed, 0);
3578        buf.buffer.delete(cp, (cp + num));
3579
3580        String copy = new String(killed);
3581        killRing.add(copy);
3582
3583        return true;
3584    }
3585
3586    public boolean yank() throws IOException {
3587        String yanked = killRing.yank();
3588
3589        if (yanked == null) {
3590            return false;
3591        }
3592        putString(yanked);
3593        return true;
3594    }
3595
3596    public boolean yankPop() throws IOException {
3597        if (!killRing.lastYank()) {
3598            return false;
3599        }
3600        String current = killRing.yank();
3601        if (current == null) {
3602            // This shouldn't happen.
3603            return false;
3604        }
3605        backspace(current.length());
3606        String yanked = killRing.yankPop();
3607        if (yanked == null) {
3608            // This shouldn't happen.
3609            return false;
3610        }
3611
3612        putString(yanked);
3613        return true;
3614    }
3615
3616    /**
3617     * Clear the screen by issuing the ANSI "clear screen" code.
3618     */
3619    public boolean clearScreen() throws IOException {
3620        if (!terminal.isAnsiSupported()) {
3621            return false;
3622        }
3623
3624        // send the ANSI code to clear the screen
3625        printAnsiSequence("2J");
3626
3627        // then send the ANSI code to go to position 1,1
3628        printAnsiSequence("1;1H");
3629
3630        return true;
3631    }
3632
3633    /**
3634     * Issue an audible keyboard bell.
3635     */
3636    public void beep() throws IOException {
3637        if (bellEnabled) {
3638            print(KEYBOARD_BELL);
3639            // need to flush so the console actually beeps
3640            flush();
3641        }
3642    }
3643
3644    //disabled to avoid dependency on java.desktop:
3645//    /**
3646//     * Paste the contents of the clipboard into the console buffer
3647//     *
3648//     * @return true if clipboard contents pasted
3649//     */
3650//    public boolean paste() throws IOException {
3651//        Clipboard clipboard;
3652//        try { // May throw ugly exception on system without X
3653//            clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
3654//        }
3655//        catch (Exception e) {
3656//            return false;
3657//        }
3658//
3659//        if (clipboard == null) {
3660//            return false;
3661//        }
3662//
3663//        Transferable transferable = clipboard.getContents(null);
3664//
3665//        if (transferable == null) {
3666//            return false;
3667//        }
3668//
3669//        try {
3670//            @SuppressWarnings("deprecation")
3671//            Object content = transferable.getTransferData(DataFlavor.plainTextFlavor);
3672//
3673//            // This fix was suggested in bug #1060649 at
3674//            // http://sourceforge.net/tracker/index.php?func=detail&aid=1060649&group_id=64033&atid=506056
3675//            // to get around the deprecated DataFlavor.plainTextFlavor, but it
3676//            // raises a UnsupportedFlavorException on Mac OS X
3677//
3678//            if (content == null) {
3679//                try {
3680//                    content = new DataFlavor().getReaderForText(transferable);
3681//                }
3682//                catch (Exception e) {
3683//                    // ignore
3684//                }
3685//            }
3686//
3687//            if (content == null) {
3688//                return false;
3689//            }
3690//
3691//            String value;
3692//
3693//            if (content instanceof Reader) {
3694//                // TODO: we might want instead connect to the input stream
3695//                // so we can interpret individual lines
3696//                value = "";
3697//                String line;
3698//
3699//                BufferedReader read = new BufferedReader((Reader) content);
3700//                while ((line = read.readLine()) != null) {
3701//                    if (value.length() > 0) {
3702//                        value += "\n";
3703//                    }
3704//
3705//                    value += line;
3706//                }
3707//            }
3708//            else {
3709//                value = content.toString();
3710//            }
3711//
3712//            if (value == null) {
3713//                return true;
3714//            }
3715//
3716//            putString(value);
3717//
3718//            return true;
3719//        }
3720//        catch (UnsupportedFlavorException e) {
3721//            Log.error("Paste failed: ", e);
3722//
3723//            return false;
3724//        }
3725//    }
3726
3727    //disabled to avoid dependency on java.desktop:
3728//    //
3729//    // Triggered Actions
3730//    //
3731//
3732//    private final Map<Character, ActionListener> triggeredActions = new HashMap<Character, ActionListener>();
3733//
3734//    /**
3735//     * Adding a triggered Action allows to give another curse of action if a character passed the pre-processing.
3736//     * <p/>
3737//     * Say you want to close the application if the user enter q.
3738//     * addTriggerAction('q', new ActionListener(){ System.exit(0); }); would do the trick.
3739//     */
3740//    public void addTriggeredAction(final char c, final ActionListener listener) {
3741//        triggeredActions.put(c, listener);
3742//    }
3743
3744    //
3745    // Formatted Output
3746    //
3747
3748    /**
3749     * Output the specified {@link Collection} in proper columns.
3750     */
3751    public void printColumns(final Collection<? extends CharSequence> items) throws IOException {
3752        if (items == null || items.isEmpty()) {
3753            return;
3754        }
3755
3756        int width = getTerminal().getWidth();
3757        int height = getTerminal().getHeight();
3758
3759        int maxWidth = 0;
3760        for (CharSequence item : items) {
3761            maxWidth = Math.max(maxWidth, item.length());
3762        }
3763        maxWidth = maxWidth + 3;
3764        Log.debug("Max width: ", maxWidth);
3765
3766        int showLines;
3767        if (isPaginationEnabled()) {
3768            showLines = height - 1; // page limit
3769        }
3770        else {
3771            showLines = Integer.MAX_VALUE;
3772        }
3773
3774        StringBuilder buff = new StringBuilder();
3775        for (CharSequence item : items) {
3776            if ((buff.length() + maxWidth) > width) {
3777                println(buff);
3778                buff.setLength(0);
3779
3780                if (--showLines == 0) {
3781                    // Overflow
3782                    print(resources.getString("DISPLAY_MORE"));
3783                    flush();
3784                    int c = readCharacter();
3785                    if (c == '\r' || c == '\n') {
3786                        // one step forward
3787                        showLines = 1;
3788                    }
3789                    else if (c != 'q') {
3790                        // page forward
3791                        showLines = height - 1;
3792                    }
3793
3794                    back(resources.getString("DISPLAY_MORE").length());
3795                    if (c == 'q') {
3796                        // cancel
3797                        break;
3798                    }
3799                }
3800            }
3801
3802            // NOTE: toString() is important here due to AnsiString being retarded
3803            buff.append(item.toString());
3804            for (int i = 0; i < (maxWidth - item.length()); i++) {
3805                buff.append(' ');
3806            }
3807        }
3808
3809        if (buff.length() > 0) {
3810            println(buff);
3811        }
3812    }
3813
3814    //
3815    // Non-supported Terminal Support
3816    //
3817
3818    private Thread maskThread;
3819
3820    private void beforeReadLine(final String prompt, final Character mask) {
3821        if (mask != null && maskThread == null) {
3822            final String fullPrompt = "\r" + prompt
3823                + "                 "
3824                + "                 "
3825                + "                 "
3826                + "\r" + prompt;
3827
3828            maskThread = new Thread()
3829            {
3830                public void run() {
3831                    while (!interrupted()) {
3832                        try {
3833                            Writer out = getOutput();
3834                            out.write(fullPrompt);
3835                            out.flush();
3836                            sleep(3);
3837                        }
3838                        catch (IOException e) {
3839                            return;
3840                        }
3841                        catch (InterruptedException e) {
3842                            return;
3843                        }
3844                    }
3845                }
3846            };
3847
3848            maskThread.setPriority(Thread.MAX_PRIORITY);
3849            maskThread.setDaemon(true);
3850            maskThread.start();
3851        }
3852    }
3853
3854    private void afterReadLine() {
3855        if (maskThread != null && maskThread.isAlive()) {
3856            maskThread.interrupt();
3857        }
3858
3859        maskThread = null;
3860    }
3861
3862    /**
3863     * Erases the current line with the existing prompt, then redraws the line
3864     * with the provided prompt and buffer
3865     * @param prompt
3866     *            the new prompt
3867     * @param buffer
3868     *            the buffer to be drawn
3869     * @param cursorDest
3870     *            where you want the cursor set when the line has been drawn.
3871     *            -1 for end of line.
3872     * */
3873    public void resetPromptLine(String prompt, String buffer, int cursorDest) throws IOException {
3874        // move cursor to end of line
3875        moveToEnd();
3876
3877        // backspace all text, including prompt
3878        buf.buffer.append(this.prompt);
3879        int promptLength = 0;
3880        if (this.prompt != null) {
3881            promptLength = this.prompt.length();
3882        }
3883
3884        buf.cursor += promptLength;
3885        setPrompt("");
3886        backspaceAll();
3887
3888        setPrompt(prompt);
3889        redrawLine();
3890        setBuffer(buffer);
3891
3892        // move cursor to destination (-1 will move to end of line)
3893        if (cursorDest < 0) cursorDest = buffer.length();
3894        setCursorPosition(cursorDest);
3895
3896        flush();
3897    }
3898
3899    public void printSearchStatus(String searchTerm, String match) throws IOException {
3900        printSearchStatus(searchTerm, match, "(reverse-i-search)`");
3901    }
3902
3903    public void printForwardSearchStatus(String searchTerm, String match) throws IOException {
3904        printSearchStatus(searchTerm, match, "(i-search)`");
3905    }
3906
3907    private void printSearchStatus(String searchTerm, String match, String searchLabel) throws IOException {
3908        String prompt = searchLabel + searchTerm + "': ";
3909        int cursorDest = match.indexOf(searchTerm);
3910        resetPromptLine(prompt, match, cursorDest);
3911    }
3912
3913    public void restoreLine(String originalPrompt, int cursorDest) throws IOException {
3914        // TODO move cursor to matched string
3915        String prompt = lastLine(originalPrompt);
3916        String buffer = buf.buffer.toString();
3917        resetPromptLine(prompt, buffer, cursorDest);
3918    }
3919
3920    //
3921    // History search
3922    //
3923    /**
3924     * Search backward in history from a given position.
3925     *
3926     * @param searchTerm substring to search for.
3927     * @param startIndex the index from which on to search
3928     * @return index where this substring has been found, or -1 else.
3929     */
3930    public int searchBackwards(String searchTerm, int startIndex) {
3931        return searchBackwards(searchTerm, startIndex, false);
3932    }
3933
3934    /**
3935     * Search backwards in history from the current position.
3936     *
3937     * @param searchTerm substring to search for.
3938     * @return index where the substring has been found, or -1 else.
3939     */
3940    public int searchBackwards(String searchTerm) {
3941        return searchBackwards(searchTerm, history.index());
3942    }
3943
3944
3945    public int searchBackwards(String searchTerm, int startIndex, boolean startsWith) {
3946        ListIterator<History.Entry> it = history.entries(startIndex);
3947        while (it.hasPrevious()) {
3948            History.Entry e = it.previous();
3949            if (startsWith) {
3950                if (e.value().toString().startsWith(searchTerm)) {
3951                    return e.index();
3952                }
3953            } else {
3954                if (e.value().toString().contains(searchTerm)) {
3955                    return e.index();
3956                }
3957            }
3958        }
3959        return -1;
3960    }
3961
3962    /**
3963     * Search forward in history from a given position.
3964     *
3965     * @param searchTerm substring to search for.
3966     * @param startIndex the index from which on to search
3967     * @return index where this substring has been found, or -1 else.
3968     */
3969    public int searchForwards(String searchTerm, int startIndex) {
3970        return searchForwards(searchTerm, startIndex, false);
3971    }
3972    /**
3973     * Search forwards in history from the current position.
3974     *
3975     * @param searchTerm substring to search for.
3976     * @return index where the substring has been found, or -1 else.
3977     */
3978    public int searchForwards(String searchTerm) {
3979        return searchForwards(searchTerm, history.index());
3980    }
3981
3982    public int searchForwards(String searchTerm, int startIndex, boolean startsWith) {
3983        if (startIndex >= history.size()) {
3984            startIndex = history.size() - 1;
3985        }
3986
3987        ListIterator<History.Entry> it = history.entries(startIndex);
3988
3989        if (searchIndex != -1 && it.hasNext()) {
3990            it.next();
3991        }
3992
3993        while (it.hasNext()) {
3994            History.Entry e = it.next();
3995            if (startsWith) {
3996                if (e.value().toString().startsWith(searchTerm)) {
3997                    return e.index();
3998                }
3999            } else {
4000                if (e.value().toString().contains(searchTerm)) {
4001                    return e.index();
4002                }
4003            }
4004        }
4005        return -1;
4006    }
4007
4008    //
4009    // Helpers
4010    //
4011
4012    /**
4013     * Checks to see if the specified character is a delimiter. We consider a
4014     * character a delimiter if it is anything but a letter or digit.
4015     *
4016     * @param c     The character to test
4017     * @return      True if it is a delimiter
4018     */
4019    private boolean isDelimiter(final char c) {
4020        return !Character.isLetterOrDigit(c);
4021    }
4022
4023    /**
4024     * Checks to see if a character is a whitespace character. Currently
4025     * this delegates to {@link Character#isWhitespace(char)}, however
4026     * eventually it should be hooked up so that the definition of whitespace
4027     * can be configured, as readline does.
4028     *
4029     * @param c The character to check
4030     * @return true if the character is a whitespace
4031     */
4032    private boolean isWhitespace(final char c) {
4033        return Character.isWhitespace (c);
4034    }
4035
4036    private void printAnsiSequence(String sequence) throws IOException {
4037        print(27);
4038        print('[');
4039        print(sequence);
4040        flush(); // helps with step debugging
4041    }
4042
4043}
4044