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