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