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