View Javadoc

1   /*
2    * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
3    *
4    * This software is distributable under the BSD license. See the terms of the
5    * BSD license in the documentation provided with this software.
6    */
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(); // 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 	/***
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                 // swallow exceptions with option debugging
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             // hardwired arrow key bindings
264             // keybindings[VK_UP] = PREV_HISTORY;
265             // keybindings[VK_DOWN] = NEXT_HISTORY;
266             // keybindings[VK_LEFT] = PREV_CHAR;
267             // keybindings[VK_RIGHT] = NEXT_CHAR;
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         // if the terminal is not echoing, then just return...
378         if (!terminal.getEcho()) {
379             return 0;
380         }
381 
382         // otherwise, clear
383         int num = countEchoCharacters((char) c);
384         back(num);
385         drawBuffer(num);
386 
387         return num;
388     }
389 
390     int countEchoCharacters(char c) {
391         // tabs as special: we need to determine the number of spaces
392         // to cancel based on what out current cursor position is
393         if (c == 9) {
394             int tabstop = 8; // will this ever be different?
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         // FIXME: does not handle anything but a line with a prompt
443         // absolute position
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             // if the terminal is unsupported, just use plain-java reading
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: // ctrl-d
511 
512                     if (buf.buffer.length() == 0) {
513                         return null;
514                     }
515 		    break;
516 
517                 case COMPLETE: // tab
518                     success = complete();
519                     break;
520 
521                 case MOVE_TO_BEG:
522                     success = setCursorPosition(0);
523                     break;
524 
525                 case KILL_LINE: // CTRL-K
526                     success = killLine();
527                     break;
528 
529                 case CLEAR_SCREEN: // CTRL-L
530                     success = clearScreen();
531                     break;
532 
533                 case KILL_LINE_PREV: // CTRL-U
534                     success = resetLine();
535                     break;
536 
537                 case NEWLINE: // enter
538                     moveToEnd();
539                     printNewline(); // output newline
540                     return finishBuffer();
541 
542                 case DELETE_PREV_CHAR: // backspace
543                     success = backspace();
544                     break;
545 
546                 case DELETE_NEXT_CHAR: // delete
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) { // ignore null chars
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         // return new BufferedReader (new InputStreamReader (in)).readLine ();
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         // extract the appropriate key binding
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 { // May throw ugly exception on system without X
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              * This fix was suggested in bug #1060649 at
717              * http://sourceforge.net/tracker/index.php?func=detail&aid=1060649&group_id=64033&atid=506056
718              * to get around the deprecated DataFlavor.plainTextFlavor, but it
719              * raises a UnsupportedFlavorException on Mac OS X
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                 // TODO: we might want instead connect to the input stream
736                 // so we can interpret individual lines
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         // send the ANSI code to clear the screen
800         printString(((char) 27) + "[2J");
801         flushConsole();
802 
803         // then send the ANSI code to go to position 1,1
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         // debug ("tab for (" + buf + ")");
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         // no candidates? Fail.
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; // page limit
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) { // Overflow
885                     printString(loc.getString("display-more"));
886                     flushConsole();
887                     int c = readVirtualKey();
888                     if (c == '\r' || c == '\n')
889                         showLines = 1; // one step forward
890                     else if (c != 'q')
891                         showLines = getTermheight() - 1; // page forward
892 
893                     back(loc.getString("display-more").length());
894                     if (c == 'q')
895                         break; // cancel
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         // don't bother modifying it if it is unchanged
993         if (buffer.equals(buf.buffer.toString())) {
994             return;
995         }
996 
997         // obtain the difference between the current buffer and the new one
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); // go back for the differences
1012         killLine(); // clear to the end of the line
1013         buf.buffer.setLength(sameIndex); // the new length
1014         putString(buffer.substring(sameIndex)); // append the differences
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) // not at end of line
1037             back(buf.length() - buf.cursor); // sync
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         // we only add it to the history if the buffer is not empty
1057         // and if mask is null, since having a mask typically means
1058         // the string was a password. We clear the mask after this call
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             // no masking...
1100             if (mask == null) {
1101                 printCharacter(c);
1102             }
1103             // null mask: don't print anything...
1104             else if (mask.charValue() == 0) {
1105                 ;
1106             }
1107             // otherwise print the mask...
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         // debug ("drawBuffer: " + clear);
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         // debug ("clearAhead: " + num);
1153 
1154         // print blank extra characters
1155         printCharacters(' ', num);
1156 
1157         // we need to flush here so a "clever" console
1158         // doesn't just ignore the redundancy of a space followed by
1159         // a backspace.
1160         flushConsole();
1161 
1162         // reset the visual cursor
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         // need to flush so the console actually beeps
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         // debug ("Deleting from " + buf.cursor + " for " + count);
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         // debug ("move cursor " + where + " ("
1411         // + buf.cursor + " => " + (buf.cursor + where) + ")");
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         // null character mask: don't output anything
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         // clear any echo characters
1460         clearEcho(c);
1461 
1462         return c;
1463     }
1464 
1465     public final int readCharacter(final char[] allowed) throws IOException {
1466         // if we restrict to a limited set and the current character
1467         // is not in the set, then try again.
1468         char c;
1469 
1470         Arrays.sort(allowed); // always need to sort before binarySearch
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     	/* Commented out beacuse of DWA-2949:
1488            if (buf.cursor == 0)
1489                        return 0;*/
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 }