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