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