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