View Javadoc

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