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