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