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