001 /* 002 * Copyright (c) 2002-2006, Marc Prud'hommeaux. All rights reserved. 003 * 004 * This software is distributable under the BSD license. See the terms of the 005 * BSD license in the documentation provided with this software. 006 */ 007 package jline; 008 009 import java.awt.*; 010 import java.awt.datatransfer.*; 011 012 import java.io.*; 013 import java.util.*; 014 import java.util.List; 015 016 017 /** 018 * A reader for console applications. It supports custom tab-completion, 019 * saveable command history, and command line editing. On some 020 * platforms, platform-specific commands will need to be 021 * issued before the reader will function properly. See 022 * {@link Terminal#initializeTerminal} for convenience methods for 023 * issuing platform-specific setup commands. 024 * 025 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 026 */ 027 public class ConsoleReader implements ConsoleOperations { 028 String prompt; 029 private boolean useHistory = true; 030 public static final String CR = System.getProperty("line.separator"); 031 032 /** 033 * Map that contains the operation name to keymay operation mapping. 034 */ 035 public static SortedMap KEYMAP_NAMES; 036 037 static { 038 Map names = new TreeMap(); 039 040 names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG)); 041 names.put("MOVE_TO_END", new Short(MOVE_TO_END)); 042 names.put("PREV_CHAR", new Short(PREV_CHAR)); 043 names.put("NEWLINE", new Short(NEWLINE)); 044 names.put("KILL_LINE", new Short(KILL_LINE)); 045 names.put("PASTE", new Short(PASTE)); 046 names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN)); 047 names.put("NEXT_HISTORY", new Short(NEXT_HISTORY)); 048 names.put("PREV_HISTORY", new Short(PREV_HISTORY)); 049 names.put("REDISPLAY", new Short(REDISPLAY)); 050 names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV)); 051 names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD)); 052 names.put("NEXT_CHAR", new Short(NEXT_CHAR)); 053 names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR)); 054 names.put("SEARCH_PREV", new Short(SEARCH_PREV)); 055 names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR)); 056 names.put("SEARCH_NEXT", new Short(SEARCH_NEXT)); 057 names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD)); 058 names.put("TO_END_WORD", new Short(TO_END_WORD)); 059 names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV)); 060 names.put("PASTE_PREV", new Short(PASTE_PREV)); 061 names.put("REPLACE_MODE", new Short(REPLACE_MODE)); 062 names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE)); 063 names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR)); 064 names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD)); 065 names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR)); 066 names.put("ADD", new Short(ADD)); 067 names.put("PREV_WORD", new Short(PREV_WORD)); 068 names.put("CHANGE_META", new Short(CHANGE_META)); 069 names.put("DELETE_META", new Short(DELETE_META)); 070 names.put("END_WORD", new Short(END_WORD)); 071 names.put("NEXT_CHAR", new Short(NEXT_CHAR)); 072 names.put("INSERT", new Short(INSERT)); 073 names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT)); 074 names.put("PASTE_NEXT", new Short(PASTE_NEXT)); 075 names.put("REPLACE_CHAR", new Short(REPLACE_CHAR)); 076 names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR)); 077 names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR)); 078 names.put("UNDO", new Short(UNDO)); 079 names.put("NEXT_WORD", new Short(NEXT_WORD)); 080 names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR)); 081 names.put("CHANGE_CASE", new Short(CHANGE_CASE)); 082 names.put("COMPLETE", new Short(COMPLETE)); 083 names.put("EXIT", new Short(EXIT)); 084 085 KEYMAP_NAMES = new TreeMap(Collections.unmodifiableMap(names)); 086 } 087 088 /** 089 * The map for logical operations. 090 */ 091 private final short[] keybindings; 092 093 /** 094 * If true, issue an audible keyboard bell when appropriate. 095 */ 096 private boolean bellEnabled = true; 097 098 /** 099 * The current character mask. 100 */ 101 private Character mask = null; 102 103 /** 104 * The null mask. 105 */ 106 private static final Character NULL_MASK = new Character((char) 0); 107 108 /** 109 * The number of tab-completion candidates above which a warning 110 * will be prompted before showing all the candidates. 111 */ 112 private int autoprintThreshhold = Integer.getInteger 113 ("jline.completion.threshold", 100).intValue(); // same default as bash 114 115 /** 116 * The Terminal to use. 117 */ 118 private final Terminal terminal; 119 private CompletionHandler completionHandler = 120 new CandidateListCompletionHandler(); 121 InputStream in; 122 final Writer out; 123 final CursorBuffer buf = new CursorBuffer(); 124 static PrintWriter debugger; 125 History history = new History(); 126 final List completors = new LinkedList(); 127 private Character echoCharacter = null; 128 129 /** 130 * Create a new reader using {@link FileDescriptor#in} for input 131 * and {@link System#out} for output. {@link FileDescriptor#in} is 132 * used because it has a better chance of being unbuffered. 133 */ 134 public ConsoleReader() throws IOException { 135 this(new FileInputStream(FileDescriptor.in), 136 new PrintWriter(System.out)); 137 } 138 139 /** 140 * Create a new reader using the specified {@link InputStream} 141 * for input and the specific writer for output, using the 142 * default keybindings resource. 143 */ 144 public ConsoleReader(final InputStream in, final Writer out) 145 throws IOException { 146 this(in, out, null); 147 } 148 149 public ConsoleReader(final InputStream in, final Writer out, 150 final InputStream bindings) throws IOException { 151 this(in, out, bindings, Terminal.getTerminal()); 152 } 153 154 /** 155 * Create a new reader. 156 * 157 * @param in the input 158 * @param out the output 159 * @param bindings the key bindings to use 160 * @param term the terminal to use 161 */ 162 public ConsoleReader(InputStream in, Writer out, InputStream bindings, 163 Terminal term) throws IOException { 164 this.terminal = term; 165 setInput(in); 166 this.out = out; 167 168 if (bindings == null) { 169 String bindingFile = 170 System.getProperty("jline.keybindings", 171 new File(System.getProperty("user.home", 172 ".jlinebindings.properties")).getAbsolutePath()); 173 174 if (!(new File(bindingFile).isFile())) { 175 bindings = ConsoleReader.class. 176 getResourceAsStream("keybindings.properties"); 177 } else { 178 bindings = new FileInputStream(new File(bindingFile)); 179 } 180 } 181 182 this.keybindings = new short[Byte.MAX_VALUE * 2]; 183 184 Arrays.fill(this.keybindings, UNKNOWN); 185 186 /** 187 * Loads the key bindings. Bindings file is in the format: 188 * 189 * keycode: operation name 190 */ 191 if (bindings != null) { 192 Properties p = new Properties(); 193 p.load(bindings); 194 bindings.close(); 195 196 for (Iterator i = p.keySet().iterator(); i.hasNext();) { 197 String val = (String) i.next(); 198 199 try { 200 Short code = new Short(val); 201 String op = (String) p.getProperty(val); 202 203 Short opval = (Short) KEYMAP_NAMES.get(op); 204 205 if (opval != null) { 206 keybindings[code.shortValue()] = opval.shortValue(); 207 } 208 } catch (NumberFormatException nfe) { 209 consumeException(nfe); 210 } 211 } 212 213 // hardwired arrow key bindings 214 // keybindings[VK_UP] = PREV_HISTORY; 215 // keybindings[VK_DOWN] = NEXT_HISTORY; 216 // keybindings[VK_LEFT] = PREV_CHAR; 217 // keybindings[VK_RIGHT] = NEXT_CHAR; 218 } 219 } 220 221 public Terminal getTerminal() { 222 return this.terminal; 223 } 224 225 /** 226 * Set the stream for debugging. Development use only. 227 */ 228 public void setDebug(final PrintWriter debugger) { 229 ConsoleReader.debugger = debugger; 230 } 231 232 /** 233 * Set the stream to be used for console input. 234 */ 235 public void setInput(final InputStream in) { 236 this.in = in; 237 } 238 239 /** 240 * Returns the stream used for console input. 241 */ 242 public InputStream getInput() { 243 return this.in; 244 } 245 246 /** 247 * Read the next line and return the contents of the buffer. 248 */ 249 public String readLine() throws IOException { 250 return readLine((String) null); 251 } 252 253 /** 254 * Read the next line with the specified character mask. If null, then 255 * characters will be echoed. If 0, then no characters will be echoed. 256 */ 257 public String readLine(final Character mask) throws IOException { 258 return readLine(null, mask); 259 } 260 261 /** 262 * @param bellEnabled if true, enable audible keyboard bells if 263 * an alert is required. 264 */ 265 public void setBellEnabled(final boolean bellEnabled) { 266 this.bellEnabled = bellEnabled; 267 } 268 269 /** 270 * @return true is audible keyboard bell is enabled. 271 */ 272 public boolean getBellEnabled() { 273 return this.bellEnabled; 274 } 275 276 /** 277 * Query the terminal to find the current width; 278 * 279 * @see Terminal#getTerminalWidth 280 * @return the width of the current terminal. 281 */ 282 public int getTermwidth() { 283 return Terminal.setupTerminal().getTerminalWidth(); 284 } 285 286 /** 287 * Query the terminal to find the current width; 288 * 289 * @see Terminal#getTerminalHeight 290 * 291 * @return the height of the current terminal. 292 */ 293 public int getTermheight() { 294 return Terminal.setupTerminal().getTerminalHeight(); 295 } 296 297 /** 298 * @param autoprintThreshhold the number of candidates to print 299 * without issuing a warning. 300 */ 301 public void setAutoprintThreshhold(final int autoprintThreshhold) { 302 this.autoprintThreshhold = autoprintThreshhold; 303 } 304 305 /** 306 * @return the number of candidates to print without issing a warning. 307 */ 308 public int getAutoprintThreshhold() { 309 return this.autoprintThreshhold; 310 } 311 312 int getKeyForAction(short logicalAction) { 313 for (int i = 0; i < keybindings.length; i++) { 314 if (keybindings[i] == logicalAction) { 315 return i; 316 } 317 } 318 319 return -1; 320 } 321 322 /** 323 * Clear the echoed characters for the specified character code. 324 */ 325 int clearEcho(int c) throws IOException { 326 // if the terminal is not echoing, then just return... 327 if (!terminal.getEcho()) { 328 return 0; 329 } 330 331 // otherwise, clear 332 int num = countEchoCharacters((char) c); 333 back(num); 334 drawBuffer(num); 335 336 return num; 337 } 338 339 int countEchoCharacters(char c) { 340 // tabs as special: we need to determine the number of spaces 341 // to cancel based on what out current cursor position is 342 if (c == 9) { 343 int tabstop = 8; // will this ever be different? 344 int position = getCursorPosition(); 345 346 return tabstop - (position % tabstop); 347 } 348 349 return getPrintableCharacters(c).length(); 350 } 351 352 /** 353 * Return the number of characters that will be printed when the 354 * specified character is echoed to the screen. Adapted from 355 * cat by Torbjorn Granlund, as repeated in stty by 356 * David MacKenzie. 357 */ 358 StringBuffer getPrintableCharacters(char ch) { 359 StringBuffer sbuff = new StringBuffer(); 360 361 if (ch >= 32) { 362 if (ch < 127) { 363 sbuff.append(ch); 364 } else if (ch == 127) { 365 sbuff.append('^'); 366 sbuff.append('?'); 367 } else { 368 sbuff.append('M'); 369 sbuff.append('-'); 370 371 if (ch >= (128 + 32)) { 372 if (ch < (128 + 127)) { 373 sbuff.append((char) (ch - 128)); 374 } else { 375 sbuff.append('^'); 376 sbuff.append('?'); 377 } 378 } else { 379 sbuff.append('^'); 380 sbuff.append((char) (ch - 128 + 64)); 381 } 382 } 383 } else { 384 sbuff.append('^'); 385 sbuff.append((char) (ch + 64)); 386 } 387 388 return sbuff; 389 } 390 391 int getCursorPosition() { 392 // FIXME: does not handle anything but a line with a prompt 393 // absolute position 394 return ((prompt == null) ? 0 : prompt.length()) + buf.cursor; 395 } 396 397 public String readLine(final String prompt) throws IOException { 398 return readLine(prompt, null); 399 } 400 401 /** 402 * The default prompt that will be issued. 403 */ 404 public void setDefaultPrompt(String prompt) { 405 this.prompt = prompt; 406 } 407 408 /** 409 * The default prompt that will be issued. 410 */ 411 public String getDefaultPrompt() { 412 return prompt; 413 } 414 415 /** 416 * Read a line from the <i>in</i> {@link InputStream}, and 417 * return the line (without any trailing newlines). 418 * 419 * @param prompt the prompt to issue to the console, may be null. 420 * @return a line that is read from the terminal, or null if there 421 * was null input (e.g., <i>CTRL-D</i> was pressed). 422 */ 423 public String readLine(final String prompt, final Character mask) 424 throws IOException { 425 this.mask = mask; 426 if (prompt != null) 427 this.prompt = prompt; 428 429 try { 430 terminal.beforeReadLine(this, prompt, mask); 431 432 if ((prompt != null) && (prompt.length() > 0)) { 433 out.write(prompt); 434 out.flush(); 435 } 436 437 // if the terminal is unsupported, just use plain-java reading 438 if (!terminal.isSupported()) { 439 return readLine(in); 440 } 441 442 while (true) { 443 int[] next = readBinding(); 444 445 if (next == null) { 446 return null; 447 } 448 449 int c = next[0]; 450 int code = next[1]; 451 452 if (c == -1) { 453 return null; 454 } 455 456 boolean success = true; 457 458 switch (code) { 459 case EXIT: // ctrl-d 460 461 if (buf.buffer.length() == 0) { 462 return null; 463 } 464 465 case COMPLETE: // tab 466 success = complete(); 467 break; 468 469 case MOVE_TO_BEG: 470 success = setCursorPosition(0); 471 break; 472 473 case KILL_LINE: // CTRL-K 474 success = killLine(); 475 break; 476 477 case CLEAR_SCREEN: // CTRL-L 478 success = clearScreen(); 479 break; 480 481 case KILL_LINE_PREV: // CTRL-U 482 success = resetLine(); 483 break; 484 485 case NEWLINE: // enter 486 printNewline(); // output newline 487 return finishBuffer(); 488 489 case DELETE_PREV_CHAR: // backspace 490 success = backspace(); 491 break; 492 493 case MOVE_TO_END: 494 success = moveToEnd(); 495 break; 496 497 case PREV_CHAR: 498 success = moveCursor(-1) != 0; 499 break; 500 501 case NEXT_CHAR: 502 success = moveCursor(1) != 0; 503 break; 504 505 case NEXT_HISTORY: 506 success = moveHistory(true); 507 break; 508 509 case PREV_HISTORY: 510 success = moveHistory(false); 511 break; 512 513 case REDISPLAY: 514 break; 515 516 case PASTE: 517 success = paste(); 518 break; 519 520 case DELETE_PREV_WORD: 521 success = deletePreviousWord(); 522 break; 523 524 case PREV_WORD: 525 success = previousWord(); 526 break; 527 528 case NEXT_WORD: 529 success = nextWord(); 530 break; 531 532 case UNKNOWN:default: 533 putChar(c, true); 534 } 535 536 if (!(success)) { 537 beep(); 538 } 539 540 flushConsole(); 541 } 542 } finally { 543 terminal.afterReadLine(this, prompt, mask); 544 } 545 } 546 547 private String readLine(InputStream in) throws IOException { 548 StringBuffer buf = new StringBuffer(); 549 550 while (true) { 551 int i = in.read(); 552 553 if ((i == -1) || (i == '\n') || (i == '\r')) { 554 return buf.toString(); 555 } 556 557 buf.append((char) i); 558 } 559 560 // return new BufferedReader (new InputStreamReader (in)).readLine (); 561 } 562 563 /** 564 * Reads the console input and returns an array of the form 565 * [raw, key binding]. 566 */ 567 private int[] readBinding() throws IOException { 568 int c = readVirtualKey(); 569 570 if (c == -1) { 571 return null; 572 } 573 574 // extract the appropriate key binding 575 short code = keybindings[c]; 576 577 if (debugger != null) { 578 debug(" translated: " + (int) c + ": " + code); 579 } 580 581 return new int[] { 582 c, 583 code 584 }; 585 } 586 587 /** 588 * Move up or down the history tree. 589 * 590 * @param direction less than 0 to move up the tree, down otherwise 591 */ 592 private final boolean moveHistory(final boolean next) throws IOException { 593 if (next && !history.next()) { 594 return false; 595 } else if (!next && !history.previous()) { 596 return false; 597 } 598 599 setBuffer(history.current()); 600 601 return true; 602 } 603 604 /** 605 * Paste the contents of the clipboard into the console buffer 606 * 607 * @return true if clipboard contents pasted 608 */ 609 public boolean paste() throws IOException { 610 Clipboard clipboard = Toolkit. 611 getDefaultToolkit().getSystemClipboard(); 612 613 if (clipboard == null) { 614 return false; 615 } 616 617 Transferable transferable = clipboard.getContents(null); 618 619 if (transferable == null) { 620 return false; 621 } 622 623 try { 624 Object content = transferable. 625 getTransferData(DataFlavor.plainTextFlavor); 626 627 /* 628 * This fix was suggested in bug #1060649 at 629 * http://sourceforge.net/tracker/index.php?func=detail&aid=1060649&group_id=64033&atid=506056 630 * to get around the deprecated DataFlavor.plainTextFlavor, but 631 * it raises a UnsupportedFlavorException on Mac OS X 632 */ 633 if (content == null) { 634 try { 635 content = new DataFlavor().getReaderForText(transferable); 636 } catch (Exception e) { 637 } 638 } 639 640 if (content == null) { 641 return false; 642 } 643 644 String value; 645 646 if (content instanceof Reader) { 647 // TODO: we might want instead connect to the input stream 648 // so we can interpret individual lines 649 value = ""; 650 651 String line = null; 652 653 for (BufferedReader read = new BufferedReader((Reader) content); 654 (line = read.readLine()) != null;) { 655 if (value.length() > 0) { 656 value += "\n"; 657 } 658 659 value += line; 660 } 661 } else { 662 value = content.toString(); 663 } 664 665 if (value == null) { 666 return true; 667 } 668 669 putString(value); 670 671 return true; 672 } catch (UnsupportedFlavorException ufe) { 673 if (debugger != null) 674 debug(ufe + ""); 675 676 return false; 677 } 678 } 679 680 /** 681 * Kill the buffer ahead of the current cursor position. 682 * 683 * @return true if successful 684 */ 685 public boolean killLine() throws IOException { 686 int cp = buf.cursor; 687 int len = buf.buffer.length(); 688 689 if (cp >= len) { 690 return false; 691 } 692 693 int num = buf.buffer.length() - cp; 694 clearAhead(num); 695 696 for (int i = 0; i < num; i++) { 697 buf.buffer.deleteCharAt(len - i - 1); 698 } 699 700 return true; 701 } 702 703 /** 704 * Clear the screen by issuing the ANSI "clear screen" code. 705 */ 706 public boolean clearScreen() throws IOException { 707 if (!terminal.isANSISupported()) { 708 return false; 709 } 710 711 // send the ANSI code to clear the screen 712 printString(((char) 27) + "[2J"); 713 flushConsole(); 714 715 // then send the ANSI code to go to position 1,1 716 printString(((char) 27) + "[1;1H"); 717 flushConsole(); 718 719 redrawLine(); 720 721 return true; 722 } 723 724 /** 725 * Use the completors to modify the buffer with the 726 * appropriate completions. 727 * 728 * @return true if successful 729 */ 730 private final boolean complete() throws IOException { 731 // debug ("tab for (" + buf + ")"); 732 if (completors.size() == 0) { 733 return false; 734 } 735 736 List candidates = new LinkedList(); 737 String bufstr = buf.buffer.toString(); 738 int cursor = buf.cursor; 739 740 int position = -1; 741 742 for (Iterator i = completors.iterator(); i.hasNext();) { 743 Completor comp = (Completor) i.next(); 744 745 if ((position = comp.complete(bufstr, cursor, candidates)) != -1) { 746 break; 747 } 748 } 749 750 // no candidates? Fail. 751 if (candidates.size() == 0) { 752 return false; 753 } 754 755 return completionHandler.complete(this, candidates, position); 756 } 757 758 public CursorBuffer getCursorBuffer() { 759 return buf; 760 } 761 762 /** 763 * Output the specified {@link Collection} in proper columns. 764 * 765 * @param stuff the stuff to print 766 */ 767 public void printColumns(final Collection stuff) throws IOException { 768 if ((stuff == null) || (stuff.size() == 0)) { 769 return; 770 } 771 772 int width = getTermwidth(); 773 int maxwidth = 0; 774 775 for (Iterator i = stuff.iterator(); i.hasNext(); 776 maxwidth = Math.max(maxwidth, i.next().toString().length())) { 777 ; 778 } 779 780 StringBuffer line = new StringBuffer(); 781 782 for (Iterator i = stuff.iterator(); i.hasNext();) { 783 String cur = (String) i.next(); 784 785 if ((line.length() + maxwidth) > width) { 786 printString(line.toString().trim()); 787 printNewline(); 788 line.setLength(0); 789 } 790 791 pad(cur, maxwidth + 3, line); 792 } 793 794 if (line.length() > 0) { 795 printString(line.toString().trim()); 796 printNewline(); 797 line.setLength(0); 798 } 799 } 800 801 /** 802 * Append <i>toPad</i> to the specified <i>appendTo</i>, as 803 * well as (<i>toPad.length () - len</i>) spaces. 804 * 805 * @param toPad the {@link String} to pad 806 * @param len the target length 807 * @param appendTo the {@link StringBuffer} to which to append the 808 * padded {@link String}. 809 */ 810 private final void pad(final String toPad, final int len, 811 final StringBuffer appendTo) { 812 appendTo.append(toPad); 813 814 for (int i = 0; i < (len - toPad.length()); 815 i++, appendTo.append(' ')) { 816 ; 817 } 818 } 819 820 /** 821 * Add the specified {@link Completor} to the list of handlers 822 * for tab-completion. 823 * 824 * @param completor the {@link Completor} to add 825 * @return true if it was successfully added 826 */ 827 public boolean addCompletor(final Completor completor) { 828 return completors.add(completor); 829 } 830 831 /** 832 * Remove the specified {@link Completor} from the list of handlers 833 * for tab-completion. 834 * 835 * @param completor the {@link Completor} to remove 836 * @return true if it was successfully removed 837 */ 838 public boolean removeCompletor(final Completor completor) { 839 return completors.remove(completor); 840 } 841 842 /** 843 * Returns an unmodifiable list of all the completors. 844 */ 845 public Collection getCompletors() { 846 return Collections.unmodifiableList(completors); 847 } 848 849 /** 850 * Erase the current line. 851 * 852 * @return false if we failed (e.g., the buffer was empty) 853 */ 854 final boolean resetLine() throws IOException { 855 if (buf.cursor == 0) { 856 return false; 857 } 858 859 backspaceAll(); 860 861 return true; 862 } 863 864 /** 865 * Move the cursor position to the specified absolute index. 866 */ 867 public final boolean setCursorPosition(final int position) 868 throws IOException { 869 return moveCursor(position - buf.cursor) != 0; 870 } 871 872 /** 873 * Set the current buffer's content to the specified 874 * {@link String}. The visual console will be modified 875 * to show the current buffer. 876 * 877 * @param buffer the new contents of the buffer. 878 */ 879 private final void setBuffer(final String buffer) throws IOException { 880 // don't bother modifying it if it is unchanged 881 if (buffer.equals(buf.buffer.toString())) { 882 return; 883 } 884 885 // obtain the difference between the current buffer and the new one 886 int sameIndex = 0; 887 888 for (int i = 0, l1 = buffer.length(), l2 = buf.buffer.length(); 889 (i < l1) && (i < l2); i++) { 890 if (buffer.charAt(i) == buf.buffer.charAt(i)) { 891 sameIndex++; 892 } else { 893 break; 894 } 895 } 896 897 int diff = buf.buffer.length() - sameIndex; 898 899 backspace(diff); // go back for the differences 900 killLine(); // clear to the end of the line 901 buf.buffer.setLength(sameIndex); // the new length 902 putString(buffer.substring(sameIndex)); // append the differences 903 } 904 905 /** 906 * Clear the line and redraw it. 907 */ 908 public final void redrawLine() throws IOException { 909 printCharacter(RESET_LINE); 910 flushConsole(); 911 drawLine(); 912 } 913 914 /** 915 * Output put the prompt + the current buffer 916 */ 917 public final void drawLine() throws IOException { 918 if (prompt != null) { 919 printString(prompt); 920 } 921 922 printString(buf.buffer.toString()); 923 } 924 925 /** 926 * Output a platform-dependant newline. 927 */ 928 public final void printNewline() throws IOException { 929 printString(CR); 930 flushConsole(); 931 } 932 933 /** 934 * Clear the buffer and add its contents to the history. 935 * 936 * @return the former contents of the buffer. 937 */ 938 final String finishBuffer() { 939 String str = buf.buffer.toString(); 940 941 // we only add it to the history if the buffer is not empty 942 // and if mask is null, since having a mask typically means 943 // the string was a password. We clear the mask after this call 944 if (str.length() > 0) { 945 if (mask == null && useHistory) { 946 history.addToHistory(str); 947 } else { 948 mask = null; 949 } 950 } 951 952 history.moveToEnd(); 953 954 buf.buffer.setLength(0); 955 buf.cursor = 0; 956 957 return str; 958 } 959 960 /** 961 * Write out the specified string to the buffer and the 962 * output stream. 963 */ 964 public final void putString(final String str) throws IOException { 965 buf.insert(str); 966 printString(str); 967 drawBuffer(); 968 } 969 970 /** 971 * Output the specified string to the output stream (but not the 972 * buffer). 973 */ 974 public final void printString(final String str) throws IOException { 975 printCharacters(str.toCharArray()); 976 } 977 978 /** 979 * Output the specified character, both to the buffer 980 * and the output stream. 981 */ 982 private final void putChar(final int c, final boolean print) 983 throws IOException { 984 buf.insert((char) c); 985 986 if (print) { 987 // no masking... 988 if (mask == null) { 989 printCharacter(c); 990 } 991 // null mask: don't print anything... 992 else if (mask.charValue() == 0) { 993 ; 994 } 995 // otherwise print the mask... 996 else { 997 printCharacter(mask.charValue()); 998 } 999 1000 drawBuffer(); 1001 } 1002 } 1003 1004 /** 1005 * Redraw the rest of the buffer from the cursor onwards. This 1006 * is necessary for inserting text into the buffer. 1007 * 1008 * @param clear the number of characters to clear after the 1009 * end of the buffer 1010 */ 1011 private final void drawBuffer(final int clear) throws IOException { 1012 // debug ("drawBuffer: " + clear); 1013 char[] chars = buf.buffer.substring(buf.cursor).toCharArray(); 1014 printCharacters(chars); 1015 1016 clearAhead(clear); 1017 back(chars.length); 1018 flushConsole(); 1019 } 1020 1021 /** 1022 * Redraw the rest of the buffer from the cursor onwards. This 1023 * is necessary for inserting text into the buffer. 1024 */ 1025 private final void drawBuffer() throws IOException { 1026 drawBuffer(0); 1027 } 1028 1029 /** 1030 * Clear ahead the specified number of characters 1031 * without moving the cursor. 1032 */ 1033 private final void clearAhead(final int num) throws IOException { 1034 if (num == 0) { 1035 return; 1036 } 1037 1038 // debug ("clearAhead: " + num); 1039 1040 // print blank extra characters 1041 printCharacters(' ', num); 1042 1043 // we need to flush here so a "clever" console 1044 // doesn't just ignore the redundancy of a space followed by 1045 // a backspace. 1046 flushConsole(); 1047 1048 // reset the visual cursor 1049 back(num); 1050 1051 flushConsole(); 1052 } 1053 1054 /** 1055 * Move the visual cursor backwards without modifying the 1056 * buffer cursor. 1057 */ 1058 private final void back(final int num) throws IOException { 1059 printCharacters(BACKSPACE, num); 1060 flushConsole(); 1061 } 1062 1063 /** 1064 * Issue an audible keyboard bell, if 1065 * {@link #getBellEnabled} return true. 1066 */ 1067 public final void beep() throws IOException { 1068 if (!(getBellEnabled())) { 1069 return; 1070 } 1071 1072 printCharacter(KEYBOARD_BELL); 1073 // need to flush so the console actually beeps 1074 flushConsole(); 1075 } 1076 1077 /** 1078 * Output the specified character to the output stream 1079 * without manipulating the current buffer. 1080 */ 1081 private final void printCharacter(final int c) throws IOException { 1082 out.write(c); 1083 } 1084 1085 /** 1086 * Output the specified characters to the output stream 1087 * without manipulating the current buffer. 1088 */ 1089 private final void printCharacters(final char[] c) 1090 throws IOException { 1091 out.write(c); 1092 } 1093 1094 private final void printCharacters(final char c, final int num) 1095 throws IOException { 1096 if (num == 1) { 1097 printCharacter(c); 1098 } else { 1099 char[] chars = new char[num]; 1100 Arrays.fill(chars, c); 1101 printCharacters(chars); 1102 } 1103 } 1104 1105 /** 1106 * Flush the console output stream. This is important for 1107 * printout out single characters (like a backspace or keyboard) 1108 * that we want the console to handle immedately. 1109 */ 1110 public final void flushConsole() throws IOException { 1111 out.flush(); 1112 } 1113 1114 private final int backspaceAll() throws IOException { 1115 return backspace(Integer.MAX_VALUE); 1116 } 1117 1118 /** 1119 * Issue <em>num</em> backspaces. 1120 * 1121 * @return the number of characters backed up 1122 */ 1123 private final int backspace(final int num) throws IOException { 1124 if (buf.cursor == 0) { 1125 return 0; 1126 } 1127 1128 int count = 0; 1129 1130 count = moveCursor(-1 * num) * -1; 1131 // debug ("Deleting from " + buf.cursor + " for " + count); 1132 buf.buffer.delete(buf.cursor, buf.cursor + count); 1133 drawBuffer(count); 1134 1135 return count; 1136 } 1137 1138 /** 1139 * Issue a backspace. 1140 * 1141 * @return true if successful 1142 */ 1143 public final boolean backspace() throws IOException { 1144 return backspace(1) == 1; 1145 } 1146 1147 private final boolean moveToEnd() throws IOException { 1148 if (moveCursor(1) == 0) { 1149 return false; 1150 } 1151 1152 while (moveCursor(1) != 0) { 1153 ; 1154 } 1155 1156 return true; 1157 } 1158 1159 /** 1160 * Delete the character at the current position and 1161 * redraw the remainder of the buffer. 1162 */ 1163 private final boolean deleteCurrentCharacter() throws IOException { 1164 buf.buffer.deleteCharAt(buf.cursor); 1165 drawBuffer(1); 1166 1167 return true; 1168 } 1169 1170 private final boolean previousWord() throws IOException { 1171 while (isDelimiter(buf.current()) && (moveCursor(-1) != 0)) { 1172 ; 1173 } 1174 1175 while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) { 1176 ; 1177 } 1178 1179 return true; 1180 } 1181 1182 private final boolean nextWord() throws IOException { 1183 while (isDelimiter(buf.current()) && (moveCursor(1) != 0)) { 1184 ; 1185 } 1186 1187 while (!isDelimiter(buf.current()) && (moveCursor(1) != 0)) { 1188 ; 1189 } 1190 1191 return true; 1192 } 1193 1194 private final boolean deletePreviousWord() throws IOException { 1195 while (isDelimiter(buf.current()) && backspace()) { 1196 ; 1197 } 1198 1199 while (!isDelimiter(buf.current()) && backspace()) { 1200 ; 1201 } 1202 1203 return true; 1204 } 1205 1206 /** 1207 * Move the cursor <i>where</i> characters. 1208 * 1209 * @param where if less than 0, move abs(<i>where</i>) to the left, 1210 * otherwise move <i>where</i> to the right. 1211 * 1212 * @return the number of spaces we moved 1213 */ 1214 private final int moveCursor(final int num) throws IOException { 1215 int where = num; 1216 1217 if ((buf.cursor == 0) && (where < 0)) { 1218 return 0; 1219 } 1220 1221 if ((buf.cursor == buf.buffer.length()) && (where > 0)) { 1222 return 0; 1223 } 1224 1225 if ((buf.cursor + where) < 0) { 1226 where = -buf.cursor; 1227 } else if ((buf.cursor + where) > buf.buffer.length()) { 1228 where = buf.buffer.length() - buf.cursor; 1229 } 1230 1231 moveInternal(where); 1232 1233 return where; 1234 } 1235 1236 /** 1237 * debug. 1238 * 1239 * @param str the message to issue. 1240 */ 1241 public static void debug(final String str) { 1242 if (debugger != null) { 1243 debugger.println(str); 1244 debugger.flush(); 1245 } 1246 } 1247 1248 /** 1249 * Move the cursor <i>where</i> characters, withough checking 1250 * the current buffer. 1251 * 1252 * @see #where 1253 * 1254 * @param where the number of characters to move to the right or left. 1255 */ 1256 private final void moveInternal(final int where) throws IOException { 1257 // debug ("move cursor " + where + " (" 1258 // + buf.cursor + " => " + (buf.cursor + where) + ")"); 1259 buf.cursor += where; 1260 1261 char c; 1262 1263 if (where < 0) { 1264 c = BACKSPACE; 1265 } else if (buf.cursor == 0) { 1266 return; 1267 } else { 1268 c = buf.buffer.charAt(buf.cursor - 1); // draw replacement 1269 } 1270 1271 // null character mask: don't output anything 1272 if (NULL_MASK.equals(mask)) { 1273 return; 1274 } 1275 1276 printCharacters(c, Math.abs(where)); 1277 } 1278 1279 /** 1280 * Read a character from the console. 1281 * 1282 * @return the character, or -1 if an EOF is received. 1283 */ 1284 public final int readVirtualKey() throws IOException { 1285 int c = terminal.readVirtualKey(in); 1286 1287 if (debugger != null) { 1288 debug("keystroke: " + c + ""); 1289 } 1290 1291 // clear any echo characters 1292 clearEcho(c); 1293 1294 return c; 1295 } 1296 1297 public final int readCharacter(final char[] allowed) 1298 throws IOException { 1299 // if we restrict to a limited set and the current character 1300 // is not in the set, then try again. 1301 char c; 1302 1303 Arrays.sort(allowed); // always need to sort before binarySearch 1304 1305 while (Arrays.binarySearch(allowed, c = (char) readVirtualKey()) == -1); 1306 1307 return c; 1308 } 1309 1310 public void setHistory(final History history) { 1311 this.history = history; 1312 } 1313 1314 public History getHistory() { 1315 return this.history; 1316 } 1317 1318 public void setCompletionHandler 1319 (final CompletionHandler completionHandler) { 1320 this.completionHandler = completionHandler; 1321 } 1322 1323 public CompletionHandler getCompletionHandler() { 1324 return this.completionHandler; 1325 } 1326 1327 /** 1328 * <p> 1329 * Set the echo character. For example, to have "*" entered 1330 * when a password is typed: 1331 * </p> 1332 * 1333 * <pre> 1334 * myConsoleReader.setEchoCharacter (new Character ('*')); 1335 * </pre> 1336 * 1337 * <p> 1338 * Setting the character to <pre>null</pre> will restore normal 1339 * character echoing. Setting the character to 1340 * <pre>new Character (0)</pre> will cause nothing to be echoed. 1341 * </p> 1342 * 1343 * @param echoCharacter the character to echo to the console in 1344 * place of the typed character. 1345 */ 1346 public void setEchoCharacter(final Character echoCharacter) { 1347 this.echoCharacter = echoCharacter; 1348 } 1349 1350 /** 1351 * Returns the echo character. 1352 */ 1353 public Character getEchoCharacter() { 1354 return this.echoCharacter; 1355 } 1356 1357 /** 1358 * No-op for exceptions we want to silently consume. 1359 */ 1360 private void consumeException(final Throwable e) { 1361 } 1362 1363 /** 1364 * Checks to see if the specified character is a delimiter. We 1365 * consider a character a delimiter if it is anything but a letter or 1366 * digit. 1367 * 1368 * @param c the character to test 1369 * @return true if it is a delimiter 1370 */ 1371 private boolean isDelimiter(char c) { 1372 return !Character.isLetterOrDigit(c); 1373 } 1374 1375 1376 /** 1377 * Whether or not to add new commands to the history buffer. 1378 */ 1379 public void setUseHistory(boolean useHistory) { 1380 this.useHistory = useHistory; 1381 } 1382 1383 1384 /** 1385 * Whether or not to add new commands to the history buffer. 1386 */ 1387 public boolean getUseHistory() { 1388 return useHistory; 1389 } 1390 }