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