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