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