001    /*
002     * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
003     *
004     * This software is distributable under the BSD license. See the terms of the
005     * BSD license in the documentation provided with this software.
006     */
007    package jline;
008    
009    import java.awt.*;
010    import java.awt.datatransfer.*;
011    import java.awt.event.ActionListener;
012    
013    import java.io.*;
014    import java.util.*;
015    import java.util.List;
016    
017    /**
018     * A reader for console applications. It supports custom tab-completion,
019     * saveable command history, and command line editing. On some platforms,
020     * platform-specific commands will need to be issued before the reader will
021     * function properly. See {@link Terminal#initializeTerminal} for convenience
022     * methods for issuing platform-specific setup commands.
023     *
024     * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
025     */
026    public class ConsoleReader implements ConsoleOperations {
027    
028        final static int TAB_WIDTH = 4;
029    
030        String prompt;
031    
032        private boolean useHistory = true;
033    
034        private boolean usePagination = false;
035    
036        public static final String CR = System.getProperty("line.separator");
037    
038        private static ResourceBundle loc = ResourceBundle
039                .getBundle(CandidateListCompletionHandler.class.getName());
040    
041        /**
042         * Map that contains the operation name to keymay operation mapping.
043         */
044        public static SortedMap KEYMAP_NAMES;
045    
046        static {
047            Map names = new TreeMap();
048    
049            names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG));
050            names.put("MOVE_TO_END", new Short(MOVE_TO_END));
051            names.put("PREV_CHAR", new Short(PREV_CHAR));
052            names.put("NEWLINE", new Short(NEWLINE));
053            names.put("KILL_LINE", new Short(KILL_LINE));
054            names.put("PASTE", new Short(PASTE));
055            names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN));
056            names.put("NEXT_HISTORY", new Short(NEXT_HISTORY));
057            names.put("PREV_HISTORY", new Short(PREV_HISTORY));
058            names.put("START_OF_HISTORY", new Short(START_OF_HISTORY));
059            names.put("END_OF_HISTORY", new Short(END_OF_HISTORY));
060            names.put("REDISPLAY", new Short(REDISPLAY));
061            names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV));
062            names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD));
063            names.put("NEXT_CHAR", new Short(NEXT_CHAR));
064            names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR));
065            names.put("SEARCH_PREV", new Short(SEARCH_PREV));
066            names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR));
067            names.put("SEARCH_NEXT", new Short(SEARCH_NEXT));
068            names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD));
069            names.put("TO_END_WORD", new Short(TO_END_WORD));
070            names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV));
071            names.put("PASTE_PREV", new Short(PASTE_PREV));
072            names.put("REPLACE_MODE", new Short(REPLACE_MODE));
073            names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE));
074            names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR));
075            names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD));
076            names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR));
077            names.put("ADD", new Short(ADD));
078            names.put("PREV_WORD", new Short(PREV_WORD));
079            names.put("CHANGE_META", new Short(CHANGE_META));
080            names.put("DELETE_META", new Short(DELETE_META));
081            names.put("END_WORD", new Short(END_WORD));
082            names.put("NEXT_CHAR", new Short(NEXT_CHAR));
083            names.put("INSERT", new Short(INSERT));
084            names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT));
085            names.put("PASTE_NEXT", new Short(PASTE_NEXT));
086            names.put("REPLACE_CHAR", new Short(REPLACE_CHAR));
087            names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR));
088            names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR));
089            names.put("UNDO", new Short(UNDO));
090            names.put("NEXT_WORD", new Short(NEXT_WORD));
091            names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR));
092            names.put("CHANGE_CASE", new Short(CHANGE_CASE));
093            names.put("COMPLETE", new Short(COMPLETE));
094            names.put("EXIT", new Short(EXIT));
095            names.put("CLEAR_LINE", new Short(CLEAR_LINE));
096    
097            KEYMAP_NAMES = new TreeMap(Collections.unmodifiableMap(names));
098        }
099    
100        /**
101         * The map for logical operations.
102         */
103        private final short[] keybindings;
104    
105        /**
106         * If true, issue an audible keyboard bell when appropriate.
107         */
108        private boolean bellEnabled = true;
109    
110        /**
111         * The current character mask.
112         */
113        private Character mask = null;
114    
115        /**
116         * The null mask.
117         */
118        private static final Character NULL_MASK = new Character((char) 0);
119    
120        /**
121         * The number of tab-completion candidates above which a warning will be
122         * prompted before showing all the candidates.
123         */
124        private int autoprintThreshhold = Integer.getInteger(
125                "jline.completion.threshold", 100).intValue(); // same default as
126    
127        // bash
128    
129        /**
130         * The Terminal to use.
131         */
132        private final Terminal terminal;
133    
134        private CompletionHandler completionHandler = new CandidateListCompletionHandler();
135    
136        InputStream in;
137    
138        final Writer out;
139    
140        final CursorBuffer buf = new CursorBuffer();
141    
142        static PrintWriter debugger;
143    
144        History history = new History();
145    
146        final List completors = new LinkedList();
147    
148        private Character echoCharacter = null;
149    
150            private Map triggeredActions = new HashMap();
151    
152    
153            /**
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    }