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