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