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