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