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