1 /*
2 * Copyright (c) 2002-2012, the original author or authors.
3 *
4 * This software is distributable under the BSD license. See the terms of the
5 * BSD license in the documentation provided with this software.
6 *
7 * http://www.opensource.org/licenses/bsd-license.php
8 */
9 package jline.console;
10
11 import java.awt.*;
12 import java.awt.datatransfer.Clipboard;
13 import java.awt.datatransfer.DataFlavor;
14 import java.awt.datatransfer.Transferable;
15 import java.awt.datatransfer.UnsupportedFlavorException;
16 import java.awt.event.ActionListener;
17 import java.io.BufferedReader;
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.FileDescriptor;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.io.OutputStreamWriter;
27 import java.io.Reader;
28 import java.io.Writer;
29 import java.net.URL;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.ListIterator;
37 import java.util.Map;
38 import java.util.ResourceBundle;
39 import java.util.Stack;
40
41 import jline.Terminal;
42 import jline.TerminalFactory;
43 import jline.UnixTerminal;
44 import jline.console.completer.CandidateListCompletionHandler;
45 import jline.console.completer.Completer;
46 import jline.console.completer.CompletionHandler;
47 import jline.console.history.History;
48 import jline.console.history.MemoryHistory;
49 import jline.internal.Configuration;
50 import jline.internal.InputStreamReader;
51 import jline.internal.Log;
52 import jline.internal.NonBlockingInputStream;
53 import jline.internal.Nullable;
54 import jline.internal.Urls;
55 import org.fusesource.jansi.AnsiOutputStream;
56
57 import static jline.internal.Preconditions.checkNotNull;
58
59 /**
60 * A reader for console applications. It supports custom tab-completion,
61 * saveable command history, and command line editing. On some platforms,
62 * platform-specific commands will need to be issued before the reader will
63 * function properly. See {@link jline.Terminal#init} for convenience
64 * methods for issuing platform-specific setup commands.
65 *
66 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
67 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
68 * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
69 */
70 public class ConsoleReader
71 {
72 public static final String JLINE_NOBELL = "jline.nobell";
73
74 public static final String JLINE_ESC_TIMEOUT = "jline.esc.timeout";
75
76 public static final String JLINE_INPUTRC = "jline.inputrc";
77
78 public static final String INPUT_RC = ".inputrc";
79
80 public static final String DEFAULT_INPUT_RC = "/etc/inputrc";
81
82 public static final char BACKSPACE = '\b';
83
84 public static final char RESET_LINE = '\r';
85
86 public static final char KEYBOARD_BELL = '\07';
87
88 public static final char NULL_MASK = 0;
89
90 public static final int TAB_WIDTH = 4;
91
92 private static final ResourceBundle
93 resources = ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName());
94
95 private final Terminal terminal;
96
97 private final Writer out;
98
99 private final CursorBuffer buf = new CursorBuffer();
100
101 private String prompt;
102 private int promptLen;
103
104 private boolean expandEvents = true;
105
106 private boolean bellEnabled = !Configuration.getBoolean(JLINE_NOBELL, true);
107
108 private boolean handleUserInterrupt = false;
109
110 private Character mask;
111
112 private Character echoCharacter;
113
114 private StringBuffer searchTerm = null;
115
116 private String previousSearchTerm = "";
117
118 private int searchIndex = -1;
119
120 private int parenBlinkTimeout = 500;
121
122 /*
123 * The reader and the nonBlockingInput go hand-in-hand. The reader wraps
124 * the nonBlockingInput, but we have to retain a handle to it so that
125 * we can shut down its blocking read thread when we go away.
126 */
127 private NonBlockingInputStream in;
128 private long escapeTimeout;
129 private Reader reader;
130
131 /*
132 * TODO: Please read the comments about this in setInput(), but this needs
133 * to be done away with.
134 */
135 private boolean isUnitTestInput;
136
137 /**
138 * Last character searched for with a vi character search
139 */
140 private char charSearchChar = 0; // Character to search for
141 private char charSearchLastInvokeChar = 0; // Most recent invocation key
142 private char charSearchFirstInvokeChar = 0;// First character that invoked
143
144 /**
145 * The vi yank buffer
146 */
147 private String yankBuffer = "";
148
149 private KillRing killRing = new KillRing();
150
151 private String encoding;
152
153 private boolean recording;
154
155 private String macro = "";
156
157 private String appName;
158
159 private URL inputrcUrl;
160
161 private ConsoleKeys consoleKeys;
162
163 private String commentBegin = null;
164
165 private boolean skipLF = false;
166
167 /**
168 * Set to true if the reader should attempt to detect copy-n-paste. The
169 * effect of this that an attempt is made to detect if tab is quickly
170 * followed by another character, then it is assumed that the tab was
171 * a literal tab as part of a copy-and-paste operation and is inserted as
172 * such.
173 */
174 private boolean copyPasteDetection = false;
175
176 /*
177 * Current internal state of the line reader
178 */
179 private State state = State.NORMAL;
180
181 /**
182 * Possible states in which the current readline operation may be in.
183 */
184 private static enum State {
185 /**
186 * The user is just typing away
187 */
188 NORMAL,
189 /**
190 * In the middle of a emacs seach
191 */
192 SEARCH,
193 FORWARD_SEARCH,
194 /**
195 * VI "yank-to" operation ("y" during move mode)
196 */
197 VI_YANK_TO,
198 /**
199 * VI "delete-to" operation ("d" during move mode)
200 */
201 VI_DELETE_TO,
202 /**
203 * VI "change-to" operation ("c" during move mode)
204 */
205 VI_CHANGE_TO
206 }
207
208 public ConsoleReader() throws IOException {
209 this(null, new FileInputStream(FileDescriptor.in), System.out, null);
210 }
211
212 public ConsoleReader(final InputStream in, final OutputStream out) throws IOException {
213 this(null, in, out, null);
214 }
215
216 public ConsoleReader(final InputStream in, final OutputStream out, final Terminal term) throws IOException {
217 this(null, in, out, term);
218 }
219
220 public ConsoleReader(final @Nullable String appName, final InputStream in, final OutputStream out, final @Nullable Terminal term) throws IOException {
221 this(appName, in, out, term, null);
222 }
223
224 public ConsoleReader(final @Nullable String appName, final InputStream in, final OutputStream out, final @Nullable Terminal term, final @Nullable String encoding)
225 throws IOException
226 {
227 this.appName = appName != null ? appName : "JLine";
228 this.encoding = encoding != null ? encoding : Configuration.getEncoding();
229 this.terminal = term != null ? term : TerminalFactory.get();
230 String outEncoding = terminal.getOutputEncoding() != null? terminal.getOutputEncoding() : this.encoding;
231 this.out = new OutputStreamWriter(terminal.wrapOutIfNeeded(out), outEncoding);
232 setInput( in );
233
234 this.inputrcUrl = getInputRc();
235
236 consoleKeys = new ConsoleKeys(this.appName, inputrcUrl);
237 }
238
239 private URL getInputRc() throws IOException {
240 String path = Configuration.getString(JLINE_INPUTRC);
241 if (path == null) {
242 File f = new File(Configuration.getUserHome(), INPUT_RC);
243 if (!f.exists()) {
244 f = new File(DEFAULT_INPUT_RC);
245 }
246 return f.toURI().toURL();
247 } else {
248 return Urls.create(path);
249 }
250 }
251
252 public KeyMap getKeys() {
253 return consoleKeys.getKeys();
254 }
255
256 void setInput(final InputStream in) throws IOException {
257 this.escapeTimeout = Configuration.getLong(JLINE_ESC_TIMEOUT, 100);
258 /*
259 * This is gross and here is how to fix it. In getCurrentPosition()
260 * and getCurrentAnsiRow(), the logic is disabled when running unit
261 * tests and the fact that it is a unit test is determined by knowing
262 * if the original input stream was a ByteArrayInputStream. So, this
263 * is our test to do this. What SHOULD happen is that the unit
264 * tests should pass in a terminal that is appropriately configured
265 * such that whatever behavior they expect to happen (or not happen)
266 * happens (or doesn't).
267 *
268 * So, TODO, get rid of this and fix the unit tests.
269 */
270 this.isUnitTestInput = in instanceof ByteArrayInputStream;
271 boolean nonBlockingEnabled =
272 escapeTimeout > 0L
273 && terminal.isSupported()
274 && in != null;
275
276 /*
277 * If we had a non-blocking thread already going, then shut it down
278 * and start a new one.
279 */
280 if (this.in != null) {
281 this.in.shutdown();
282 }
283
284 final InputStream wrapped = terminal.wrapInIfNeeded( in );
285
286 this.in = new NonBlockingInputStream(wrapped, nonBlockingEnabled);
287 this.reader = new InputStreamReader( this.in, encoding );
288 }
289
290 /**
291 * Shuts the console reader down. This method should be called when you
292 * have completed using the reader as it shuts down and cleans up resources
293 * that would otherwise be "leaked".
294 */
295 public void shutdown() {
296 if (in != null) {
297 in.shutdown();
298 }
299 }
300
301 /**
302 * Shuts down the ConsoleReader if the JVM attempts to clean it up.
303 */
304 @Override
305 protected void finalize() throws Throwable {
306 try {
307 shutdown();
308 }
309 finally {
310 super.finalize();
311 }
312 }
313
314 public InputStream getInput() {
315 return in;
316 }
317
318 public Writer getOutput() {
319 return out;
320 }
321
322 public Terminal getTerminal() {
323 return terminal;
324 }
325
326 public CursorBuffer getCursorBuffer() {
327 return buf;
328 }
329
330 public void setExpandEvents(final boolean expand) {
331 this.expandEvents = expand;
332 }
333
334 public boolean getExpandEvents() {
335 return expandEvents;
336 }
337
338 /**
339 * Enables or disables copy and paste detection. The effect of enabling this
340 * this setting is that when a tab is received immediately followed by another
341 * character, the tab will not be treated as a completion, but as a tab literal.
342 * @param onoff true if detection is enabled
343 */
344 public void setCopyPasteDetection(final boolean onoff) {
345 copyPasteDetection = onoff;
346 }
347
348 /**
349 * @return true if copy and paste detection is enabled.
350 */
351 public boolean isCopyPasteDetectionEnabled() {
352 return copyPasteDetection;
353 }
354
355 /**
356 * Set whether the console bell is enabled.
357 *
358 * @param enabled true if enabled; false otherwise
359 * @since 2.7
360 */
361 public void setBellEnabled(boolean enabled) {
362 this.bellEnabled = enabled;
363 }
364
365 /**
366 * Get whether the console bell is enabled
367 *
368 * @return true if enabled; false otherwise
369 * @since 2.7
370 */
371 public boolean getBellEnabled() {
372 return bellEnabled;
373 }
374
375 /**
376 * Set whether user interrupts (ctrl-C) are handled by having JLine
377 * throw {@link UserInterruptException} from {@link #readLine}.
378 * Otherwise, the JVM will handle {@code SIGINT} as normal, which
379 * usually causes it to exit. The default is {@code false}.
380 *
381 * @since 2.10
382 */
383 public void setHandleUserInterrupt(boolean enabled)
384 {
385 this.handleUserInterrupt = enabled;
386 }
387
388 /**
389 * Get whether user interrupt handling is enabled
390 *
391 * @return true if enabled; false otherwise
392 * @since 2.10
393 */
394 public boolean getHandleUserInterrupt()
395 {
396 return handleUserInterrupt;
397 }
398
399 /**
400 * Sets the string that will be used to start a comment when the
401 * insert-comment key is struck.
402 * @param commentBegin The begin comment string.
403 * @since 2.7
404 */
405 public void setCommentBegin(String commentBegin) {
406 this.commentBegin = commentBegin;
407 }
408
409 /**
410 * @return the string that will be used to start a comment when the
411 * insert-comment key is struck.
412 * @since 2.7
413 */
414 public String getCommentBegin() {
415 String str = commentBegin;
416
417 if (str == null) {
418 str = consoleKeys.getVariable("comment-begin");
419 if (str == null) {
420 str = "#";
421 }
422 }
423 return str;
424 }
425
426 public void setPrompt(final String prompt) {
427 this.prompt = prompt;
428 this.promptLen = ((prompt == null) ? 0 : stripAnsi(lastLine(prompt)).length());
429 }
430
431 public String getPrompt() {
432 return prompt;
433 }
434
435 /**
436 * Set the echo character. For example, to have "*" entered when a password is typed:
437 * <p/>
438 * <pre>
439 * myConsoleReader.setEchoCharacter(new Character('*'));
440 * </pre>
441 * <p/>
442 * Setting the character to
443 * <p/>
444 * <pre>
445 * null
446 * </pre>
447 * <p/>
448 * will restore normal character echoing. Setting the character to
449 * <p/>
450 * <pre>
451 * new Character(0)
452 * </pre>
453 * <p/>
454 * will cause nothing to be echoed.
455 *
456 * @param c the character to echo to the console in place of the typed character.
457 */
458 public void setEchoCharacter(final Character c) {
459 this.echoCharacter = c;
460 }
461
462 /**
463 * Returns the echo character.
464 */
465 public Character getEchoCharacter() {
466 return echoCharacter;
467 }
468
469 /**
470 * Erase the current line.
471 *
472 * @return false if we failed (e.g., the buffer was empty)
473 */
474 protected final boolean resetLine() throws IOException {
475 if (buf.cursor == 0) {
476 return false;
477 }
478
479 StringBuilder killed = new StringBuilder();
480
481 while (buf.cursor > 0) {
482 char c = buf.current();
483 if (c == 0) {
484 break;
485 }
486
487 killed.append(c);
488 backspace();
489 }
490
491 String copy = killed.reverse().toString();
492 killRing.addBackwards(copy);
493
494 return true;
495 }
496
497 int getCursorPosition() {
498 // FIXME: does not handle anything but a line with a prompt absolute position
499 return promptLen + buf.cursor;
500 }
501
502 /**
503 * Returns the text after the last '\n'.
504 * prompt is returned if no '\n' characters are present.
505 * null is returned if prompt is null.
506 */
507 private String lastLine(String str) {
508 if (str == null) return "";
509 int last = str.lastIndexOf("\n");
510
511 if (last >= 0) {
512 return str.substring(last + 1, str.length());
513 }
514
515 return str;
516 }
517
518 private String stripAnsi(String str) {
519 if (str == null) return "";
520 try {
521 ByteArrayOutputStream baos = new ByteArrayOutputStream();
522 AnsiOutputStream aos = new AnsiOutputStream(baos);
523 aos.write(str.getBytes());
524 aos.flush();
525 return baos.toString();
526 } catch (IOException e) {
527 return str;
528 }
529 }
530
531 /**
532 * Move the cursor position to the specified absolute index.
533 */
534 public final boolean setCursorPosition(final int position) throws IOException {
535 if (position == buf.cursor) {
536 return true;
537 }
538
539 return moveCursor(position - buf.cursor) != 0;
540 }
541
542 /**
543 * Set the current buffer's content to the specified {@link String}. The
544 * visual console will be modified to show the current buffer.
545 *
546 * @param buffer the new contents of the buffer.
547 */
548 private void setBuffer(final String buffer) throws IOException {
549 // don't bother modifying it if it is unchanged
550 if (buffer.equals(buf.buffer.toString())) {
551 return;
552 }
553
554 // obtain the difference between the current buffer and the new one
555 int sameIndex = 0;
556
557 for (int i = 0, l1 = buffer.length(), l2 = buf.buffer.length(); (i < l1)
558 && (i < l2); i++) {
559 if (buffer.charAt(i) == buf.buffer.charAt(i)) {
560 sameIndex++;
561 }
562 else {
563 break;
564 }
565 }
566
567 int diff = buf.cursor - sameIndex;
568 if (diff < 0) { // we can't backspace here so try from the end of the buffer
569 moveToEnd();
570 diff = buf.buffer.length() - sameIndex;
571 }
572
573 backspace(diff); // go back for the differences
574 killLine(); // clear to the end of the line
575 buf.buffer.setLength(sameIndex); // the new length
576 putString(buffer.substring(sameIndex)); // append the differences
577 }
578
579 private void setBuffer(final CharSequence buffer) throws IOException {
580 setBuffer(String.valueOf(buffer));
581 }
582
583 private void setBufferKeepPos(final String buffer) throws IOException {
584 int pos = buf.cursor;
585 setBuffer(buffer);
586 setCursorPosition(pos);
587 }
588
589 private void setBufferKeepPos(final CharSequence buffer) throws IOException {
590 setBufferKeepPos(String.valueOf(buffer));
591 }
592
593 /**
594 * Output put the prompt + the current buffer
595 */
596 public final void drawLine() throws IOException {
597 String prompt = getPrompt();
598 if (prompt != null) {
599 print(prompt);
600 }
601
602 print(buf.buffer.toString());
603
604 if (buf.length() != buf.cursor) { // not at end of line
605 back(buf.length() - buf.cursor - 1);
606 }
607 // force drawBuffer to check for weird wrap (after clear screen)
608 drawBuffer();
609 }
610
611 /**
612 * Clear the line and redraw it.
613 */
614 public final void redrawLine() throws IOException {
615 print(RESET_LINE);
616 // flush();
617 drawLine();
618 }
619
620 /**
621 * Clear the buffer and add its contents to the history.
622 *
623 * @return the former contents of the buffer.
624 */
625 final String finishBuffer() throws IOException { // FIXME: Package protected because used by tests
626 String str = buf.buffer.toString();
627 String historyLine = str;
628
629 if (expandEvents) {
630 try {
631 str = expandEvents(str);
632 // all post-expansion occurrences of '!' must have been escaped, so re-add escape to each
633 historyLine = str.replace("!", "\\!");
634 // only leading '^' results in expansion, so only re-add escape for that case
635 historyLine = historyLine.replaceAll("^\\^", "\\\\^");
636 } catch(IllegalArgumentException e) {
637 Log.error("Could not expand event", e);
638 beep();
639 buf.clear();
640 str = "";
641 }
642 }
643
644 // we only add it to the history if the buffer is not empty
645 // and if mask is null, since having a mask typically means
646 // the string was a password. We clear the mask after this call
647 if (str.length() > 0) {
648 if (mask == null && isHistoryEnabled()) {
649 history.add(historyLine);
650 }
651 else {
652 mask = null;
653 }
654 }
655
656 history.moveToEnd();
657
658 buf.buffer.setLength(0);
659 buf.cursor = 0;
660
661 return str;
662 }
663
664 /**
665 * Expand event designator such as !!, !#, !3, etc...
666 * See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
667 */
668 protected String expandEvents(String str) throws IOException {
669 StringBuilder sb = new StringBuilder();
670 for (int i = 0; i < str.length(); i++) {
671 char c = str.charAt(i);
672 switch (c) {
673 case '\\':
674 // any '\!' should be considered an expansion escape, so skip expansion and strip the escape character
675 // a leading '\^' should be considered an expansion escape, so skip expansion and strip the escape character
676 // otherwise, add the escape
677 if (i + 1 < str.length()) {
678 char nextChar = str.charAt(i+1);
679 if (nextChar == '!' || (nextChar == '^' && i == 0)) {
680 c = nextChar;
681 i++;
682 }
683 }
684 sb.append(c);
685 break;
686 case '!':
687 if (i + 1 < str.length()) {
688 c = str.charAt(++i);
689 boolean neg = false;
690 String rep = null;
691 int i1, idx;
692 switch (c) {
693 case '!':
694 if (history.size() == 0) {
695 throw new IllegalArgumentException("!!: event not found");
696 }
697 rep = history.get(history.index() - 1).toString();
698 break;
699 case '#':
700 sb.append(sb.toString());
701 break;
702 case '?':
703 i1 = str.indexOf('?', i + 1);
704 if (i1 < 0) {
705 i1 = str.length();
706 }
707 String sc = str.substring(i + 1, i1);
708 i = i1;
709 idx = searchBackwards(sc);
710 if (idx < 0) {
711 throw new IllegalArgumentException("!?" + sc + ": event not found");
712 } else {
713 rep = history.get(idx).toString();
714 }
715 break;
716 case '$':
717 if (history.size() == 0) {
718 throw new IllegalArgumentException("!$: event not found");
719 }
720 String previous = history.get(history.index() - 1).toString().trim();
721 int lastSpace = previous.lastIndexOf(' ');
722 if(lastSpace != -1) {
723 rep = previous.substring(lastSpace+1);
724 } else {
725 rep = previous;
726 }
727 break;
728 case ' ':
729 case '\t':
730 sb.append('!');
731 sb.append(c);
732 break;
733 case '-':
734 neg = true;
735 i++;
736 // fall through
737 case '0':
738 case '1':
739 case '2':
740 case '3':
741 case '4':
742 case '5':
743 case '6':
744 case '7':
745 case '8':
746 case '9':
747 i1 = i;
748 for (; i < str.length(); i++) {
749 c = str.charAt(i);
750 if (c < '0' || c > '9') {
751 break;
752 }
753 }
754 idx = 0;
755 try {
756 idx = Integer.parseInt(str.substring(i1, i));
757 } catch (NumberFormatException e) {
758 throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found");
759 }
760 if (neg) {
761 if (idx > 0 && idx <= history.size()) {
762 rep = (history.get(history.index() - idx)).toString();
763 } else {
764 throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found");
765 }
766 } else {
767 if (idx > history.index() - history.size() && idx <= history.index()) {
768 rep = (history.get(idx - 1)).toString();
769 } else {
770 throw new IllegalArgumentException((neg ? "!-" : "!") + str.substring(i1, i) + ": event not found");
771 }
772 }
773 break;
774 default:
775 String ss = str.substring(i);
776 i = str.length();
777 idx = searchBackwards(ss, history.index(), true);
778 if (idx < 0) {
779 throw new IllegalArgumentException("!" + ss + ": event not found");
780 } else {
781 rep = history.get(idx).toString();
782 }
783 break;
784 }
785 if (rep != null) {
786 sb.append(rep);
787 }
788 } else {
789 sb.append(c);
790 }
791 break;
792 case '^':
793 if (i == 0) {
794 int i1 = str.indexOf('^', i + 1);
795 int i2 = str.indexOf('^', i1 + 1);
796 if (i2 < 0) {
797 i2 = str.length();
798 }
799 if (i1 > 0 && i2 > 0) {
800 String s1 = str.substring(i + 1, i1);
801 String s2 = str.substring(i1 + 1, i2);
802 String s = history.get(history.index() - 1).toString().replace(s1, s2);
803 sb.append(s);
804 i = i2 + 1;
805 break;
806 }
807 }
808 sb.append(c);
809 break;
810 default:
811 sb.append(c);
812 break;
813 }
814 }
815 String result = sb.toString();
816 if (!str.equals(result)) {
817 print(result);
818 println();
819 flush();
820 }
821 return result;
822
823 }
824
825 /**
826 * Write out the specified string to the buffer and the output stream.
827 */
828 public final void putString(final CharSequence str) throws IOException {
829 buf.write(str);
830 if (mask == null) {
831 // no masking
832 print(str);
833 } else if (mask == NULL_MASK) {
834 // don't print anything
835 } else {
836 print(mask, str.length());
837 }
838 drawBuffer();
839 }
840
841 /**
842 * Redraw the rest of the buffer from the cursor onwards. This is necessary
843 * for inserting text into the buffer.
844 *
845 * @param clear the number of characters to clear after the end of the buffer
846 */
847 private void drawBuffer(final int clear) throws IOException {
848 // debug ("drawBuffer: " + clear);
849 if (buf.cursor == buf.length() && clear == 0) {
850 } else {
851 char[] chars = buf.buffer.substring(buf.cursor).toCharArray();
852 if (mask != null) {
853 Arrays.fill(chars, mask);
854 }
855 if (terminal.hasWeirdWrap()) {
856 // need to determine if wrapping will occur:
857 int width = terminal.getWidth();
858 int pos = getCursorPosition();
859 for (int i = 0; i < chars.length; i++) {
860 print(chars[i]);
861 if ((pos + i + 1) % width == 0) {
862 print(32); // move cursor to next line by printing dummy space
863 print(13); // CR / not newline.
864 }
865 }
866 } else {
867 print(chars);
868 }
869 clearAhead(clear, chars.length);
870 if (terminal.isAnsiSupported()) {
871 if (chars.length > 0) {
872 back(chars.length);
873 }
874 } else {
875 back(chars.length);
876 }
877 }
878 if (terminal.hasWeirdWrap()) {
879 int width = terminal.getWidth();
880 // best guess on whether the cursor is in that weird location...
881 // Need to do this without calling ansi cursor location methods
882 // otherwise it breaks paste of wrapped lines in xterm.
883 if (getCursorPosition() > 0 && (getCursorPosition() % width == 0)
884 && buf.cursor == buf.length() && clear == 0) {
885 // the following workaround is reverse-engineered from looking
886 // at what bash sent to the terminal in the same situation
887 print(32); // move cursor to next line by printing dummy space
888 print(13); // CR / not newline.
889 }
890 }
891 }
892
893 /**
894 * Redraw the rest of the buffer from the cursor onwards. This is necessary
895 * for inserting text into the buffer.
896 */
897 private void drawBuffer() throws IOException {
898 drawBuffer(0);
899 }
900
901 /**
902 * Clear ahead the specified number of characters without moving the cursor.
903 *
904 * @param num the number of characters to clear
905 * @param delta the difference between the internal cursor and the screen
906 * cursor - if > 0, assume some stuff was printed and weird wrap has to be
907 * checked
908 */
909 private void clearAhead(final int num, int delta) throws IOException {
910 if (num == 0) {
911 return;
912 }
913
914 if (terminal.isAnsiSupported()) {
915 int width = terminal.getWidth();
916 int screenCursorCol = getCursorPosition() + delta;
917 // clear current line
918 printAnsiSequence("K");
919 // if cursor+num wraps, then we need to clear the line(s) below too
920 int curCol = screenCursorCol % width;
921 int endCol = (screenCursorCol + num - 1) % width;
922 int lines = num / width;
923 if (endCol < curCol) lines++;
924 for (int i = 0; i < lines; i++) {
925 printAnsiSequence("B");
926 printAnsiSequence("2K");
927 }
928 for (int i = 0; i < lines; i++) {
929 printAnsiSequence("A");
930 }
931 return;
932 }
933
934 // print blank extra characters
935 print(' ', num);
936
937 // we need to flush here so a "clever" console doesn't just ignore the redundancy
938 // of a space followed by a backspace.
939 // flush();
940
941 // reset the visual cursor
942 back(num);
943
944 // flush();
945 }
946
947 /**
948 * Move the visual cursor backwards without modifying the buffer cursor.
949 */
950 protected void back(final int num) throws IOException {
951 if (num == 0) return;
952 if (terminal.isAnsiSupported()) {
953 int width = getTerminal().getWidth();
954 int cursor = getCursorPosition();
955 int realCursor = cursor + num;
956 int realCol = realCursor % width;
957 int newCol = cursor % width;
958 int moveup = num / width;
959 int delta = realCol - newCol;
960 if (delta < 0) moveup++;
961 if (moveup > 0) {
962 printAnsiSequence(moveup + "A");
963 }
964 printAnsiSequence((1 + newCol) + "G");
965 return;
966 }
967 print(BACKSPACE, num);
968 // flush();
969 }
970
971 /**
972 * Flush the console output stream. This is important for printout out single characters (like a backspace or
973 * keyboard) that we want the console to handle immediately.
974 */
975 public void flush() throws IOException {
976 out.flush();
977 }
978
979 private int backspaceAll() throws IOException {
980 return backspace(Integer.MAX_VALUE);
981 }
982
983 /**
984 * Issue <em>num</em> backspaces.
985 *
986 * @return the number of characters backed up
987 */
988 private int backspace(final int num) throws IOException {
989 if (buf.cursor == 0) {
990 return 0;
991 }
992
993 int count = 0;
994
995 int termwidth = getTerminal().getWidth();
996 int lines = getCursorPosition() / termwidth;
997 count = moveCursor(-1 * num) * -1;
998 buf.buffer.delete(buf.cursor, buf.cursor + count);
999 if (getCursorPosition() / termwidth != lines) {
1000 if (terminal.isAnsiSupported()) {
1001 // debug("doing backspace redraw: " + getCursorPosition() + " on " + termwidth + ": " + lines);
1002 printAnsiSequence("K");
1003 // if cursor+num wraps, then we need to clear the line(s) below too
1004 // last char printed is one pos less than cursor so we subtract
1005 // one
1006 /*
1007 // TODO: fixme (does not work - test with reverse search with wrapping line and CTRL-E)
1008 int endCol = (getCursorPosition() + num - 1) % termwidth;
1009 int curCol = getCursorPosition() % termwidth;
1010 if (endCol < curCol) lines++;
1011 for (int i = 1; i < lines; i++) {
1012 printAnsiSequence("B");
1013 printAnsiSequence("2K");
1014 }
1015 for (int i = 1; i < lines; i++) {
1016 printAnsiSequence("A");
1017 }
1018 return count;
1019 */
1020 }
1021 }
1022 drawBuffer(count);
1023
1024 return count;
1025 }
1026
1027 /**
1028 * Issue a backspace.
1029 *
1030 * @return true if successful
1031 */
1032 public boolean backspace() throws IOException {
1033 return backspace(1) == 1;
1034 }
1035
1036 protected boolean moveToEnd() throws IOException {
1037 if (buf.cursor == buf.length()) {
1038 return true;
1039 }
1040 return moveCursor(buf.length() - buf.cursor) > 0;
1041 }
1042
1043 /**
1044 * Delete the character at the current position and redraw the remainder of the buffer.
1045 */
1046 private boolean deleteCurrentCharacter() throws IOException {
1047 if (buf.length() == 0 || buf.cursor == buf.length()) {
1048 return false;
1049 }
1050
1051 buf.buffer.deleteCharAt(buf.cursor);
1052 drawBuffer(1);
1053 return true;
1054 }
1055
1056 /**
1057 * This method is calling while doing a delete-to ("d"), change-to ("c"),
1058 * or yank-to ("y") and it filters out only those movement operations
1059 * that are allowable during those operations. Any operation that isn't
1060 * allow drops you back into movement mode.
1061 *
1062 * @param op The incoming operation to remap
1063 * @return The remaped operation
1064 */
1065 private Operation viDeleteChangeYankToRemap (Operation op) {
1066 switch (op) {
1067 case VI_EOF_MAYBE:
1068 case ABORT:
1069 case BACKWARD_CHAR:
1070 case FORWARD_CHAR:
1071 case END_OF_LINE:
1072 case VI_MATCH:
1073 case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
1074 case VI_ARG_DIGIT:
1075 case VI_PREV_WORD:
1076 case VI_END_WORD:
1077 case VI_CHAR_SEARCH:
1078 case VI_NEXT_WORD:
1079 case VI_FIRST_PRINT:
1080 case VI_GOTO_MARK:
1081 case VI_COLUMN:
1082 case VI_DELETE_TO:
1083 case VI_YANK_TO:
1084 case VI_CHANGE_TO:
1085 return op;
1086
1087 default:
1088 return Operation.VI_MOVEMENT_MODE;
1089 }
1090 }
1091
1092 /**
1093 * Deletes the previous character from the cursor position
1094 * @param count number of times to do it.
1095 * @return true if it was done.
1096 * @throws IOException
1097 */
1098 private boolean viRubout(int count) throws IOException {
1099 boolean ok = true;
1100 for (int i = 0; ok && i < count; i++) {
1101 ok = backspace();
1102 }
1103 return ok;
1104 }
1105
1106 /**
1107 * Deletes the character you are sitting on and sucks the rest of
1108 * the line in from the right.
1109 * @param count Number of times to perform the operation.
1110 * @return true if its works, false if it didn't
1111 * @throws IOException
1112 */
1113 private boolean viDelete(int count) throws IOException {
1114 boolean ok = true;
1115 for (int i = 0; ok && i < count; i++) {
1116 ok = deleteCurrentCharacter();
1117 }
1118 return ok;
1119 }
1120
1121 /**
1122 * Switches the case of the current character from upper to lower
1123 * or lower to upper as necessary and advances the cursor one
1124 * position to the right.
1125 * @param count The number of times to repeat
1126 * @return true if it completed successfully, false if not all
1127 * case changes could be completed.
1128 * @throws IOException
1129 */
1130 private boolean viChangeCase(int count) throws IOException {
1131 boolean ok = true;
1132 for (int i = 0; ok && i < count; i++) {
1133
1134 ok = buf.cursor < buf.buffer.length ();
1135 if (ok) {
1136 char ch = buf.buffer.charAt(buf.cursor);
1137 if (Character.isUpperCase(ch)) {
1138 ch = Character.toLowerCase(ch);
1139 }
1140 else if (Character.isLowerCase(ch)) {
1141 ch = Character.toUpperCase(ch);
1142 }
1143 buf.buffer.setCharAt(buf.cursor, ch);
1144 drawBuffer(1);
1145 moveCursor(1);
1146 }
1147 }
1148 return ok;
1149 }
1150
1151 /**
1152 * Implements the vi change character command (in move-mode "r"
1153 * followed by the character to change to).
1154 * @param count Number of times to perform the action
1155 * @param c The character to change to
1156 * @return Whether or not there were problems encountered
1157 * @throws IOException
1158 */
1159 private boolean viChangeChar(int count, int c) throws IOException {
1160 // EOF, ESC, or CTRL-C aborts.
1161 if (c < 0 || c == '\033' || c == '\003') {
1162 return true;
1163 }
1164
1165 boolean ok = true;
1166 for (int i = 0; ok && i < count; i++) {
1167 ok = buf.cursor < buf.buffer.length ();
1168 if (ok) {
1169 buf.buffer.setCharAt(buf.cursor, (char) c);
1170 drawBuffer(1);
1171 if (i < (count-1)) {
1172 moveCursor(1);
1173 }
1174 }
1175 }
1176 return ok;
1177 }
1178
1179 /**
1180 * This is a close facsimile of the actual vi previous word logic. In
1181 * actual vi words are determined by boundaries of identity characterse.
1182 * This logic is a bit more simple and simply looks at white space or
1183 * digits or characters. It should be revised at some point.
1184 *
1185 * @param count number of iterations
1186 * @return true if the move was successful, false otherwise
1187 * @throws IOException
1188 */
1189 private boolean viPreviousWord(int count) throws IOException {
1190 boolean ok = true;
1191 if (buf.cursor == 0) {
1192 return false;
1193 }
1194
1195 int pos = buf.cursor - 1;
1196 for (int i = 0; pos > 0 && i < count; i++) {
1197 // If we are on white space, then move back.
1198 while (pos > 0 && isWhitespace(buf.buffer.charAt(pos))) {
1199 --pos;
1200 }
1201
1202 while (pos > 0 && !isDelimiter(buf.buffer.charAt(pos-1))) {
1203 --pos;
1204 }
1205
1206 if (pos > 0 && i < (count-1)) {
1207 --pos;
1208 }
1209 }
1210 setCursorPosition(pos);
1211 return ok;
1212 }
1213
1214 /**
1215 * Performs the vi "delete-to" action, deleting characters between a given
1216 * span of the input line.
1217 * @param startPos The start position
1218 * @param endPos The end position.
1219 * @param isChange If true, then the delete is part of a change operationg
1220 * (e.g. "c$" is change-to-end-of line, so we first must delete to end
1221 * of line to start the change
1222 * @return true if it succeeded, false otherwise
1223 * @throws IOException
1224 */
1225 private boolean viDeleteTo(int startPos, int endPos, boolean isChange) throws IOException {
1226 if (startPos == endPos) {
1227 return true;
1228 }
1229
1230 if (endPos < startPos) {
1231 int tmp = endPos;
1232 endPos = startPos;
1233 startPos = tmp;
1234 }
1235
1236 setCursorPosition(startPos);
1237 buf.cursor = startPos;
1238 buf.buffer.delete(startPos, endPos);
1239 drawBuffer(endPos - startPos);
1240
1241 // If we are doing a delete operation (e.g. "d$") then don't leave the
1242 // cursor dangling off the end. In reality the "isChange" flag is silly
1243 // what is really happening is that if we are in "move-mode" then the
1244 // cursor can't be moved off the end of the line, but in "edit-mode" it
1245 // is ok, but I have no easy way of knowing which mode we are in.
1246 if (! isChange && startPos > 0 && startPos == buf.length()) {
1247 moveCursor(-1);
1248 }
1249 return true;
1250 }
1251
1252 /**
1253 * Implement the "vi" yank-to operation. This operation allows you
1254 * to yank the contents of the current line based upon a move operation,
1255 * for exaple "yw" yanks the current word, "3yw" yanks 3 words, etc.
1256 *
1257 * @param startPos The starting position from which to yank
1258 * @param endPos The ending position to which to yank
1259 * @return true if the yank succeeded
1260 * @throws IOException
1261 */
1262 private boolean viYankTo(int startPos, int endPos) throws IOException {
1263 int cursorPos = startPos;
1264
1265 if (endPos < startPos) {
1266 int tmp = endPos;
1267 endPos = startPos;
1268 startPos = tmp;
1269 }
1270
1271 if (startPos == endPos) {
1272 yankBuffer = "";
1273 return true;
1274 }
1275
1276 yankBuffer = buf.buffer.substring(startPos, endPos);
1277
1278 /*
1279 * It was a movement command that moved the cursor to find the
1280 * end position, so put the cursor back where it started.
1281 */
1282 setCursorPosition(cursorPos);
1283 return true;
1284 }
1285
1286 /**
1287 * Pasts the yank buffer to the right of the current cursor position
1288 * and moves the cursor to the end of the pasted region.
1289 *
1290 * @param count Number of times to perform the operation.
1291 * @return true if it worked, false otherwise
1292 * @throws IOException
1293 */
1294 private boolean viPut(int count) throws IOException {
1295 if (yankBuffer.length () == 0) {
1296 return true;
1297 }
1298 if (buf.cursor < buf.buffer.length ()) {
1299 moveCursor(1);
1300 }
1301 for (int i = 0; i < count; i++) {
1302 putString(yankBuffer);
1303 }
1304 moveCursor(-1);
1305 return true;
1306 }
1307
1308 /**
1309 * Searches forward of the current position for a character and moves
1310 * the cursor onto it.
1311 * @param count Number of times to repeat the process.
1312 * @param ch The character to search for
1313 * @return true if the char was found, false otherwise
1314 * @throws IOException
1315 */
1316 private boolean viCharSearch(int count, int invokeChar, int ch) throws IOException {
1317 if (ch < 0 || invokeChar < 0) {
1318 return false;
1319 }
1320
1321 char searchChar = (char)ch;
1322 boolean isForward;
1323 boolean stopBefore;
1324
1325 /*
1326 * The character stuff turns out to be hairy. Here is how it works:
1327 * f - search forward for ch
1328 * F - search backward for ch
1329 * t - search forward for ch, but stop just before the match
1330 * T - search backward for ch, but stop just after the match
1331 * ; - After [fFtT;], repeat the last search, after ',' reverse it
1332 * , - After [fFtT;], reverse the last search, after ',' repeat it
1333 */
1334 if (invokeChar == ';' || invokeChar == ',') {
1335 // No recent search done? Then bail
1336 if (charSearchChar == 0) {
1337 return false;
1338 }
1339
1340 // Reverse direction if switching between ',' and ';'
1341 if (charSearchLastInvokeChar == ';' || charSearchLastInvokeChar == ',') {
1342 if (charSearchLastInvokeChar != invokeChar) {
1343 charSearchFirstInvokeChar = switchCase(charSearchFirstInvokeChar);
1344 }
1345 }
1346 else {
1347 if (invokeChar == ',') {
1348 charSearchFirstInvokeChar = switchCase(charSearchFirstInvokeChar);
1349 }
1350 }
1351
1352 searchChar = charSearchChar;
1353 }
1354 else {
1355 charSearchChar = searchChar;
1356 charSearchFirstInvokeChar = (char) invokeChar;
1357 }
1358
1359 charSearchLastInvokeChar = (char)invokeChar;
1360
1361 isForward = Character.isLowerCase(charSearchFirstInvokeChar);
1362 stopBefore = (Character.toLowerCase(charSearchFirstInvokeChar) == 't');
1363
1364 boolean ok = false;
1365
1366 if (isForward) {
1367 while (count-- > 0) {
1368 int pos = buf.cursor + 1;
1369 while (pos < buf.buffer.length()) {
1370 if (buf.buffer.charAt(pos) == (char) searchChar) {
1371 setCursorPosition(pos);
1372 ok = true;
1373 break;
1374 }
1375 ++pos;
1376 }
1377 }
1378
1379 if (ok) {
1380 if (stopBefore)
1381 moveCursor(-1);
1382
1383 /*
1384 * When in yank-to, move-to, del-to state we actually want to
1385 * go to the character after the one we landed on to make sure
1386 * that the character we ended up on is included in the
1387 * operation
1388 */
1389 if (isInViMoveOperationState()) {
1390 moveCursor(1);
1391 }
1392 }
1393 }
1394 else {
1395 while (count-- > 0) {
1396 int pos = buf.cursor - 1;
1397 while (pos >= 0) {
1398 if (buf.buffer.charAt(pos) == (char) searchChar) {
1399 setCursorPosition(pos);
1400 ok = true;
1401 break;
1402 }
1403 --pos;
1404 }
1405 }
1406
1407 if (ok && stopBefore)
1408 moveCursor(1);
1409 }
1410
1411 return ok;
1412 }
1413
1414 private char switchCase(char ch) {
1415 if (Character.isUpperCase(ch)) {
1416 return Character.toLowerCase(ch);
1417 }
1418 return Character.toUpperCase(ch);
1419 }
1420
1421 /**
1422 * @return true if line reader is in the middle of doing a change-to
1423 * delete-to or yank-to.
1424 */
1425 private final boolean isInViMoveOperationState() {
1426 return state == State.VI_CHANGE_TO
1427 || state == State.VI_DELETE_TO
1428 || state == State.VI_YANK_TO;
1429 }
1430
1431 /**
1432 * This is a close facsimile of the actual vi next word logic.
1433 * As with viPreviousWord() this probably needs to be improved
1434 * at some point.
1435 *
1436 * @param count number of iterations
1437 * @return true if the move was successful, false otherwise
1438 * @throws IOException
1439 */
1440 private boolean viNextWord(int count) throws IOException {
1441 int pos = buf.cursor;
1442 int end = buf.buffer.length();
1443
1444 for (int i = 0; pos < end && i < count; i++) {
1445 // Skip over letter/digits
1446 while (pos < end && !isDelimiter(buf.buffer.charAt(pos))) {
1447 ++pos;
1448 }
1449
1450 /*
1451 * Don't you love special cases? During delete-to and yank-to
1452 * operations the word movement is normal. However, during a
1453 * change-to, the trailing spaces behind the last word are
1454 * left in tact.
1455 */
1456 if (i < (count-1) || !(state == State.VI_CHANGE_TO)) {
1457 while (pos < end && isDelimiter(buf.buffer.charAt(pos))) {
1458 ++pos;
1459 }
1460 }
1461 }
1462
1463 setCursorPosition(pos);
1464 return true;
1465 }
1466
1467 /**
1468 * Implements a close facsimile of the vi end-of-word movement.
1469 * If the character is on white space, it takes you to the end
1470 * of the next word. If it is on the last character of a word
1471 * it takes you to the next of the next word. Any other character
1472 * of a word, takes you to the end of the current word.
1473 *
1474 * @param count Number of times to repeat the action
1475 * @return true if it worked.
1476 * @throws IOException
1477 */
1478 private boolean viEndWord(int count) throws IOException {
1479 int pos = buf.cursor;
1480 int end = buf.buffer.length();
1481
1482 for (int i = 0; pos < end && i < count; i++) {
1483 if (pos < (end-1)
1484 && !isDelimiter(buf.buffer.charAt(pos))
1485 && isDelimiter(buf.buffer.charAt (pos+1))) {
1486 ++pos;
1487 }
1488
1489 // If we are on white space, then move back.
1490 while (pos < end && isDelimiter(buf.buffer.charAt(pos))) {
1491 ++pos;
1492 }
1493
1494 while (pos < (end-1) && !isDelimiter(buf.buffer.charAt(pos+1))) {
1495 ++pos;
1496 }
1497 }
1498 setCursorPosition(pos);
1499 return true;
1500 }
1501
1502 private boolean previousWord() throws IOException {
1503 while (isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1504 // nothing
1505 }
1506
1507 while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1508 // nothing
1509 }
1510
1511 return true;
1512 }
1513
1514 private boolean nextWord() throws IOException {
1515 while (isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) {
1516 // nothing
1517 }
1518
1519 while (!isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) {
1520 // nothing
1521 }
1522
1523 return true;
1524 }
1525
1526 /**
1527 * Deletes to the beginning of the word that the cursor is sitting on.
1528 * If the cursor is on white-space, it deletes that and to the beginning
1529 * of the word before it. If the user is not on a word or whitespace
1530 * it deletes up to the end of the previous word.
1531 *
1532 * @param count Number of times to perform the operation
1533 * @return true if it worked, false if you tried to delete too many words
1534 * @throws IOException
1535 */
1536 private boolean unixWordRubout(int count) throws IOException {
1537 boolean success = true;
1538 StringBuilder killed = new StringBuilder();
1539
1540 for (; count > 0; --count) {
1541 if (buf.cursor == 0) {
1542 success = false;
1543 break;
1544 }
1545
1546 while (isWhitespace(buf.current())) {
1547 char c = buf.current();
1548 if (c == 0) {
1549 break;
1550 }
1551
1552 killed.append(c);
1553 backspace();
1554 }
1555
1556 while (!isWhitespace(buf.current())) {
1557 char c = buf.current();
1558 if (c == 0) {
1559 break;
1560 }
1561
1562 killed.append(c);
1563 backspace();
1564 }
1565 }
1566
1567 String copy = killed.reverse().toString();
1568 killRing.addBackwards(copy);
1569
1570 return success;
1571 }
1572
1573 private String insertComment(boolean isViMode) throws IOException {
1574 String comment = this.getCommentBegin ();
1575 setCursorPosition(0);
1576 putString(comment);
1577 if (isViMode) {
1578 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
1579 }
1580 return accept();
1581 }
1582
1583 /**
1584 * Similar to putString() but allows the string to be repeated a specific
1585 * number of times, allowing easy support of vi digit arguments to a given
1586 * command. The string is placed as the current cursor position.
1587 *
1588 * @param count The count of times to insert the string.
1589 * @param str The string to insert
1590 * @return true if the operation is a success, false otherwise
1591 * @throws IOException
1592 */
1593 private boolean insert(int count, final CharSequence str) throws IOException {
1594 for (int i = 0; i < count; i++) {
1595 buf.write(str);
1596 if (mask == null) {
1597 // no masking
1598 print(str);
1599 } else if (mask == NULL_MASK) {
1600 // don't print anything
1601 } else {
1602 print(mask, str.length());
1603 }
1604 }
1605 drawBuffer();
1606 return true;
1607 }
1608
1609 /**
1610 * Implements vi search ("/" or "?").
1611 * @throws IOException
1612 */
1613 private int viSearch(char searchChar) throws IOException {
1614 boolean isForward = (searchChar == '/');
1615
1616 /*
1617 * This is a little gross, I'm sure there is a more appropriate way
1618 * of saving and restoring state.
1619 */
1620 CursorBuffer origBuffer = buf.copy();
1621
1622 // Clear the contents of the current line and
1623 setCursorPosition (0);
1624 killLine();
1625
1626 // Our new "prompt" is the character that got us into search mode.
1627 putString(Character.toString(searchChar));
1628 flush();
1629
1630 boolean isAborted = false;
1631 boolean isComplete = false;
1632
1633 /*
1634 * Readline doesn't seem to do any special character map handling
1635 * here, so I think we are safe.
1636 */
1637 int ch = -1;
1638 while (!isAborted && !isComplete && (ch = readCharacter()) != -1) {
1639 switch (ch) {
1640 case '\033': // ESC
1641 /*
1642 * The ESC behavior doesn't appear to be readline behavior,
1643 * but it is a little tweak of my own. I like it.
1644 */
1645 isAborted = true;
1646 break;
1647 case '\010': // Backspace
1648 case '\177': // Delete
1649 backspace();
1650 /*
1651 * Backspacing through the "prompt" aborts the search.
1652 */
1653 if (buf.cursor == 0) {
1654 isAborted = true;
1655 }
1656 break;
1657 case '\012': // NL
1658 case '\015': // CR
1659 isComplete = true;
1660 break;
1661 default:
1662 putString(Character.toString((char) ch));
1663 }
1664
1665 flush();
1666 }
1667
1668 // If we aborted, then put ourself at the end of the original buffer.
1669 if (ch == -1 || isAborted) {
1670 setCursorPosition(0);
1671 killLine();
1672 putString(origBuffer.buffer);
1673 setCursorPosition(origBuffer.cursor);
1674 return -1;
1675 }
1676
1677 /*
1678 * The first character of the buffer was the search character itself
1679 * so we discard it.
1680 */
1681 String searchTerm = buf.buffer.substring(1);
1682 int idx = -1;
1683
1684 /*
1685 * The semantics of the history thing is gross when you want to
1686 * explicitly iterate over entries (without an iterator) as size()
1687 * returns the actual number of entries in the list but get()
1688 * doesn't work the way you think.
1689 */
1690 int end = history.index();
1691 int start = (end <= history.size()) ? 0 : end - history.size();
1692
1693 if (isForward) {
1694 for (int i = start; i < end; i++) {
1695 if (history.get(i).toString().contains(searchTerm)) {
1696 idx = i;
1697 break;
1698 }
1699 }
1700 }
1701 else {
1702 for (int i = end-1; i >= start; i--) {
1703 if (history.get(i).toString().contains(searchTerm)) {
1704 idx = i;
1705 break;
1706 }
1707 }
1708 }
1709
1710 /*
1711 * No match? Then restore what we were working on, but make sure
1712 * the cursor is at the beginning of the line.
1713 */
1714 if (idx == -1) {
1715 setCursorPosition(0);
1716 killLine();
1717 putString(origBuffer.buffer);
1718 setCursorPosition(0);
1719 return -1;
1720 }
1721
1722 /*
1723 * Show the match.
1724 */
1725 setCursorPosition(0);
1726 killLine();
1727 putString(history.get(idx));
1728 setCursorPosition(0);
1729 flush();
1730
1731 /*
1732 * While searching really only the "n" and "N" keys are interpreted
1733 * as movement, any other key is treated as if you are editing the
1734 * line with it, so we return it back up to the caller for interpretation.
1735 */
1736 isComplete = false;
1737 while (!isComplete && (ch = readCharacter()) != -1) {
1738 boolean forward = isForward;
1739 switch (ch) {
1740 case 'p': case 'P':
1741 forward = !isForward;
1742 // Fallthru
1743 case 'n': case 'N':
1744 boolean isMatch = false;
1745 if (forward) {
1746 for (int i = idx+1; !isMatch && i < end; i++) {
1747 if (history.get(i).toString().contains(searchTerm)) {
1748 idx = i;
1749 isMatch = true;
1750 }
1751 }
1752 }
1753 else {
1754 for (int i = idx - 1; !isMatch && i >= start; i--) {
1755 if (history.get(i).toString().contains(searchTerm)) {
1756 idx = i;
1757 isMatch = true;
1758 }
1759 }
1760 }
1761 if (isMatch) {
1762 setCursorPosition(0);
1763 killLine();
1764 putString(history.get(idx));
1765 setCursorPosition(0);
1766 }
1767 break;
1768 default:
1769 isComplete = true;
1770 }
1771 flush();
1772 }
1773
1774 /*
1775 * Complete?
1776 */
1777 return ch;
1778 }
1779
1780 public void setParenBlinkTimeout(int timeout) {
1781 parenBlinkTimeout = timeout;
1782 }
1783
1784 private void insertClose(String s) throws IOException {
1785 putString(s);
1786 int closePosition = buf.cursor;
1787
1788 moveCursor(-1);
1789 viMatch();
1790
1791
1792 if (in.isNonBlockingEnabled()) {
1793 in.peek(parenBlinkTimeout);
1794 }
1795
1796 setCursorPosition(closePosition);
1797 }
1798
1799 /**
1800 * Implements vi style bracket matching ("%" command). The matching
1801 * bracket for the current bracket type that you are sitting on is matched.
1802 * The logic works like so:
1803 * @return true if it worked, false if the cursor was not on a bracket
1804 * character or if there was no matching bracket.
1805 * @throws IOException
1806 */
1807 private boolean viMatch() throws IOException {
1808 int pos = buf.cursor;
1809
1810 if (pos == buf.length()) {
1811 return false;
1812 }
1813
1814 int type = getBracketType(buf.buffer.charAt (pos));
1815 int move = (type < 0) ? -1 : 1;
1816 int count = 1;
1817
1818 if (type == 0)
1819 return false;
1820
1821 while (count > 0) {
1822 pos += move;
1823
1824 // Fell off the start or end.
1825 if (pos < 0 || pos >= buf.buffer.length ()) {
1826 return false;
1827 }
1828
1829 int curType = getBracketType(buf.buffer.charAt (pos));
1830 if (curType == type) {
1831 ++count;
1832 }
1833 else if (curType == -type) {
1834 --count;
1835 }
1836 }
1837
1838 /*
1839 * Slight adjustment for delete-to, yank-to, change-to to ensure
1840 * that the matching paren is consumed
1841 */
1842 if (move > 0 && isInViMoveOperationState())
1843 ++pos;
1844
1845 setCursorPosition(pos);
1846 return true;
1847 }
1848
1849 /**
1850 * Given a character determines what type of bracket it is (paren,
1851 * square, curly, or none).
1852 * @param ch The character to check
1853 * @return 1 is square, 2 curly, 3 parent, or zero for none. The value
1854 * will be negated if it is the closing form of the bracket.
1855 */
1856 private int getBracketType (char ch) {
1857 switch (ch) {
1858 case '[': return 1;
1859 case ']': return -1;
1860 case '{': return 2;
1861 case '}': return -2;
1862 case '(': return 3;
1863 case ')': return -3;
1864 default:
1865 return 0;
1866 }
1867 }
1868
1869 private boolean deletePreviousWord() throws IOException {
1870 StringBuilder killed = new StringBuilder();
1871 char c;
1872
1873 while (isDelimiter((c = buf.current()))) {
1874 if (c == 0) {
1875 break;
1876 }
1877
1878 killed.append(c);
1879 backspace();
1880 }
1881
1882 while (!isDelimiter((c = buf.current()))) {
1883 if (c == 0) {
1884 break;
1885 }
1886
1887 killed.append(c);
1888 backspace();
1889 }
1890
1891 String copy = killed.reverse().toString();
1892 killRing.addBackwards(copy);
1893 return true;
1894 }
1895
1896 private boolean deleteNextWord() throws IOException {
1897 StringBuilder killed = new StringBuilder();
1898 char c;
1899
1900 while (isDelimiter((c = buf.nextChar()))) {
1901 if (c == 0) {
1902 break;
1903 }
1904 killed.append(c);
1905 delete();
1906 }
1907
1908 while (!isDelimiter((c = buf.nextChar()))) {
1909 if (c == 0) {
1910 break;
1911 }
1912 killed.append(c);
1913 delete();
1914 }
1915
1916 String copy = killed.toString();
1917 killRing.add(copy);
1918
1919 return true;
1920 }
1921
1922 private boolean capitalizeWord() throws IOException {
1923 boolean first = true;
1924 int i = 1;
1925 char c;
1926 while (buf.cursor + i - 1< buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) {
1927 buf.buffer.setCharAt(buf.cursor + i - 1, first ? Character.toUpperCase(c) : Character.toLowerCase(c));
1928 first = false;
1929 i++;
1930 }
1931 drawBuffer();
1932 moveCursor(i - 1);
1933 return true;
1934 }
1935
1936 private boolean upCaseWord() throws IOException {
1937 int i = 1;
1938 char c;
1939 while (buf.cursor + i - 1 < buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) {
1940 buf.buffer.setCharAt(buf.cursor + i - 1, Character.toUpperCase(c));
1941 i++;
1942 }
1943 drawBuffer();
1944 moveCursor(i - 1);
1945 return true;
1946 }
1947
1948 private boolean downCaseWord() throws IOException {
1949 int i = 1;
1950 char c;
1951 while (buf.cursor + i - 1 < buf.length() && !isDelimiter((c = buf.buffer.charAt(buf.cursor + i - 1)))) {
1952 buf.buffer.setCharAt(buf.cursor + i - 1, Character.toLowerCase(c));
1953 i++;
1954 }
1955 drawBuffer();
1956 moveCursor(i - 1);
1957 return true;
1958 }
1959
1960 /**
1961 * Performs character transpose. The character prior to the cursor and the
1962 * character under the cursor are swapped and the cursor is advanced one
1963 * character unless you are already at the end of the line.
1964 *
1965 * @param count The number of times to perform the transpose
1966 * @return true if the operation succeeded, false otherwise (e.g. transpose
1967 * cannot happen at the beginning of the line).
1968 * @throws IOException
1969 */
1970 private boolean transposeChars(int count) throws IOException {
1971 for (; count > 0; --count) {
1972 if (buf.cursor == 0 || buf.cursor == buf.buffer.length()) {
1973 return false;
1974 }
1975
1976 int first = buf.cursor-1;
1977 int second = buf.cursor;
1978
1979 char tmp = buf.buffer.charAt (first);
1980 buf.buffer.setCharAt(first, buf.buffer.charAt(second));
1981 buf.buffer.setCharAt(second, tmp);
1982
1983 // This could be done more efficiently by only re-drawing at the end.
1984 moveInternal(-1);
1985 drawBuffer();
1986 moveInternal(2);
1987 }
1988
1989 return true;
1990 }
1991
1992 public boolean isKeyMap(String name) {
1993 // Current keymap.
1994 KeyMap map = consoleKeys.getKeys();
1995 KeyMap mapByName = consoleKeys.getKeyMaps().get(name);
1996
1997 if (mapByName == null)
1998 return false;
1999
2000 /*
2001 * This may not be safe to do, but there doesn't appear to be a
2002 * clean way to find this information out.
2003 */
2004 return map == mapByName;
2005 }
2006
2007
2008 /**
2009 * The equivalent of hitting <RET>. The line is considered
2010 * complete and is returned.
2011 *
2012 * @return The completed line of text.
2013 * @throws IOException
2014 */
2015 public String accept() throws IOException {
2016 moveToEnd();
2017 println(); // output newline
2018 flush();
2019 return finishBuffer();
2020 }
2021
2022 private void abort() throws IOException {
2023 beep();
2024 buf.clear();
2025 println();
2026 redrawLine();
2027 }
2028
2029 /**
2030 * Move the cursor <i>where</i> characters.
2031 *
2032 * @param num If less than 0, move abs(<i>where</i>) to the left, otherwise move <i>where</i> to the right.
2033 * @return The number of spaces we moved
2034 */
2035 public int moveCursor(final int num) throws IOException {
2036 int where = num;
2037
2038 if ((buf.cursor == 0) && (where <= 0)) {
2039 return 0;
2040 }
2041
2042 if ((buf.cursor == buf.buffer.length()) && (where >= 0)) {
2043 return 0;
2044 }
2045
2046 if ((buf.cursor + where) < 0) {
2047 where = -buf.cursor;
2048 }
2049 else if ((buf.cursor + where) > buf.buffer.length()) {
2050 where = buf.buffer.length() - buf.cursor;
2051 }
2052
2053 moveInternal(where);
2054
2055 return where;
2056 }
2057
2058 /**
2059 * Move the cursor <i>where</i> characters, without checking the current buffer.
2060 *
2061 * @param where the number of characters to move to the right or left.
2062 */
2063 private void moveInternal(final int where) throws IOException {
2064 // debug ("move cursor " + where + " ("
2065 // + buf.cursor + " => " + (buf.cursor + where) + ")");
2066 buf.cursor += where;
2067
2068 if (terminal.isAnsiSupported()) {
2069 if (where < 0) {
2070 back(Math.abs(where));
2071 } else {
2072 int width = getTerminal().getWidth();
2073 int cursor = getCursorPosition();
2074 int oldLine = (cursor - where) / width;
2075 int newLine = cursor / width;
2076 if (newLine > oldLine) {
2077 printAnsiSequence((newLine - oldLine) + "B");
2078 }
2079 printAnsiSequence(1 +(cursor % width) + "G");
2080 }
2081 // flush();
2082 return;
2083 }
2084
2085 char c;
2086
2087 if (where < 0) {
2088 int len = 0;
2089 for (int i = buf.cursor; i < buf.cursor - where; i++) {
2090 if (buf.buffer.charAt(i) == '\t') {
2091 len += TAB_WIDTH;
2092 }
2093 else {
2094 len++;
2095 }
2096 }
2097
2098 char chars[] = new char[len];
2099 Arrays.fill(chars, BACKSPACE);
2100 out.write(chars);
2101
2102 return;
2103 }
2104 else if (buf.cursor == 0) {
2105 return;
2106 }
2107 else if (mask != null) {
2108 c = mask;
2109 }
2110 else {
2111 print(buf.buffer.substring(buf.cursor - where, buf.cursor).toCharArray());
2112 return;
2113 }
2114
2115 // null character mask: don't output anything
2116 if (mask == NULL_MASK) {
2117 return;
2118 }
2119
2120 print(c, Math.abs(where));
2121 }
2122
2123 // FIXME: replace() is not used
2124
2125 public final boolean replace(final int num, final String replacement) {
2126 buf.buffer.replace(buf.cursor - num, buf.cursor, replacement);
2127 try {
2128 moveCursor(-num);
2129 drawBuffer(Math.max(0, num - replacement.length()));
2130 moveCursor(replacement.length());
2131 }
2132 catch (IOException e) {
2133 e.printStackTrace();
2134 return false;
2135 }
2136 return true;
2137 }
2138
2139 /**
2140 * Read a character from the console.
2141 *
2142 * @return the character, or -1 if an EOF is received.
2143 */
2144 public final int readCharacter() throws IOException {
2145 int c = reader.read();
2146 if (c >= 0) {
2147 Log.trace("Keystroke: ", c);
2148 // clear any echo characters
2149 if (terminal.isSupported()) {
2150 clearEcho(c);
2151 }
2152 }
2153 return c;
2154 }
2155
2156 /**
2157 * Clear the echoed characters for the specified character code.
2158 */
2159 private int clearEcho(final int c) throws IOException {
2160 // if the terminal is not echoing, then ignore
2161 if (!terminal.isEchoEnabled()) {
2162 return 0;
2163 }
2164
2165 // otherwise, clear
2166 int num = countEchoCharacters(c);
2167 back(num);
2168 drawBuffer(num);
2169
2170 return num;
2171 }
2172
2173 private int countEchoCharacters(final int c) {
2174 // tabs as special: we need to determine the number of spaces
2175 // to cancel based on what out current cursor position is
2176 if (c == 9) {
2177 int tabStop = 8; // will this ever be different?
2178 int position = getCursorPosition();
2179
2180 return tabStop - (position % tabStop);
2181 }
2182
2183 return getPrintableCharacters(c).length();
2184 }
2185
2186 /**
2187 * Return the number of characters that will be printed when the specified
2188 * character is echoed to the screen
2189 *
2190 * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
2191 */
2192 private StringBuilder getPrintableCharacters(final int ch) {
2193 StringBuilder sbuff = new StringBuilder();
2194
2195 if (ch >= 32) {
2196 if (ch < 127) {
2197 sbuff.append(ch);
2198 }
2199 else if (ch == 127) {
2200 sbuff.append('^');
2201 sbuff.append('?');
2202 }
2203 else {
2204 sbuff.append('M');
2205 sbuff.append('-');
2206
2207 if (ch >= (128 + 32)) {
2208 if (ch < (128 + 127)) {
2209 sbuff.append((char) (ch - 128));
2210 }
2211 else {
2212 sbuff.append('^');
2213 sbuff.append('?');
2214 }
2215 }
2216 else {
2217 sbuff.append('^');
2218 sbuff.append((char) (ch - 128 + 64));
2219 }
2220 }
2221 }
2222 else {
2223 sbuff.append('^');
2224 sbuff.append((char) (ch + 64));
2225 }
2226
2227 return sbuff;
2228 }
2229
2230 public final int readCharacter(final char... allowed) throws IOException {
2231 // if we restrict to a limited set and the current character is not in the set, then try again.
2232 char c;
2233
2234 Arrays.sort(allowed); // always need to sort before binarySearch
2235
2236 while (Arrays.binarySearch(allowed, c = (char) readCharacter()) < 0) {
2237 // nothing
2238 }
2239
2240 return c;
2241 }
2242
2243 //
2244 // Key Bindings
2245 //
2246
2247 public static final String JLINE_COMPLETION_THRESHOLD = "jline.completion.threshold";
2248
2249 //
2250 // Line Reading
2251 //
2252
2253 /**
2254 * Read the next line and return the contents of the buffer.
2255 */
2256 public String readLine() throws IOException {
2257 return readLine((String) null);
2258 }
2259
2260 /**
2261 * Read the next line with the specified character mask. If null, then
2262 * characters will be echoed. If 0, then no characters will be echoed.
2263 */
2264 public String readLine(final Character mask) throws IOException {
2265 return readLine(null, mask);
2266 }
2267
2268 public String readLine(final String prompt) throws IOException {
2269 return readLine(prompt, null);
2270 }
2271
2272 /**
2273 * Sets the current keymap by name. Supported keymaps are "emacs",
2274 * "vi-insert", "vi-move".
2275 * @param name The name of the keymap to switch to
2276 * @return true if the keymap was set, or false if the keymap is
2277 * not recognized.
2278 */
2279 public boolean setKeyMap(String name) {
2280 return consoleKeys.setKeyMap(name);
2281 }
2282
2283 /**
2284 * Returns the name of the current key mapping.
2285 * @return the name of the key mapping. This will be the canonical name
2286 * of the current mode of the key map and may not reflect the name that
2287 * was used with {@link #setKeyMap(String)}.
2288 */
2289 public String getKeyMap() {
2290 return consoleKeys.getKeys().getName();
2291 }
2292
2293 /**
2294 * Read a line from the <i>in</i> {@link InputStream}, and return the line
2295 * (without any trailing newlines).
2296 *
2297 * @param prompt The prompt to issue to the console, may be null.
2298 * @return A line that is read from the terminal, or null if there was null input (e.g., <i>CTRL-D</i>
2299 * was pressed).
2300 */
2301 public String readLine(String prompt, final Character mask) throws IOException {
2302 // prompt may be null
2303 // mask may be null
2304
2305 /*
2306 * This is the accumulator for VI-mode repeat count. That is, while in
2307 * move mode, if you type 30x it will delete 30 characters. This is
2308 * where the "30" is accumulated until the command is struck.
2309 */
2310 int repeatCount = 0;
2311
2312 // FIXME: This blows, each call to readLine will reset the console's state which doesn't seem very nice.
2313 this.mask = mask;
2314 if (prompt != null) {
2315 setPrompt(prompt);
2316 }
2317 else {
2318 prompt = getPrompt();
2319 }
2320
2321 try {
2322 if (!terminal.isSupported()) {
2323 beforeReadLine(prompt, mask);
2324 }
2325
2326 if (prompt != null && prompt.length() > 0) {
2327 out.write(prompt);
2328 out.flush();
2329 }
2330
2331 // if the terminal is unsupported, just use plain-java reading
2332 if (!terminal.isSupported()) {
2333 return readLineSimple();
2334 }
2335
2336 if (handleUserInterrupt && (terminal instanceof UnixTerminal)) {
2337 ((UnixTerminal) terminal).disableInterruptCharacter();
2338 }
2339
2340 String originalPrompt = this.prompt;
2341
2342 state = State.NORMAL;
2343
2344 boolean success = true;
2345
2346 StringBuilder sb = new StringBuilder();
2347 Stack<Character> pushBackChar = new Stack<Character>();
2348 while (true) {
2349 int c = pushBackChar.isEmpty() ? readCharacter() : pushBackChar.pop ();
2350 if (c == -1) {
2351 return null;
2352 }
2353 sb.appendCodePoint(c);
2354
2355 if (recording) {
2356 macro += new String(new int[]{c}, 0, 1);
2357 }
2358
2359 Object o = getKeys().getBound( sb );
2360 /*
2361 * The kill ring keeps record of whether or not the
2362 * previous command was a yank or a kill. We reset
2363 * that state here if needed.
2364 */
2365 if (!recording && !(o instanceof KeyMap)) {
2366 if (o != Operation.YANK_POP && o != Operation.YANK) {
2367 killRing.resetLastYank();
2368 }
2369 if (o != Operation.KILL_LINE && o != Operation.KILL_WHOLE_LINE
2370 && o != Operation.BACKWARD_KILL_WORD && o != Operation.KILL_WORD
2371 && o != Operation.UNIX_LINE_DISCARD && o != Operation.UNIX_WORD_RUBOUT) {
2372 killRing.resetLastKill();
2373 }
2374 }
2375
2376 if (o == Operation.DO_LOWERCASE_VERSION) {
2377 sb.setLength( sb.length() - 1);
2378 sb.append( Character.toLowerCase( (char) c ));
2379 o = getKeys().getBound( sb );
2380 }
2381
2382 /*
2383 * A KeyMap indicates that the key that was struck has a
2384 * number of keys that can follow it as indicated in the
2385 * map. This is used primarily for Emacs style ESC-META-x
2386 * lookups. Since more keys must follow, go back to waiting
2387 * for the next key.
2388 */
2389 if ( o instanceof KeyMap ) {
2390 /*
2391 * The ESC key (#27) is special in that it is ambiguous until
2392 * you know what is coming next. The ESC could be a literal
2393 * escape, like the user entering vi-move mode, or it could
2394 * be part of a terminal control sequence. The following
2395 * logic attempts to disambiguate things in the same
2396 * fashion as regular vi or readline.
2397 *
2398 * When ESC is encountered and there is no other pending
2399 * character in the pushback queue, then attempt to peek
2400 * into the input stream (if the feature is enabled) for
2401 * 150ms. If nothing else is coming, then assume it is
2402 * not a terminal control sequence, but a raw escape.
2403 */
2404 if (c == 27
2405 && pushBackChar.isEmpty()
2406 && in.isNonBlockingEnabled()
2407 && in.peek(escapeTimeout) == -2) {
2408 o = ((KeyMap) o).getAnotherKey();
2409 if (o == null || o instanceof KeyMap) {
2410 continue;
2411 }
2412 sb.setLength(0);
2413 }
2414 else {
2415 continue;
2416 }
2417 }
2418
2419 /*
2420 * If we didn't find a binding for the key and there is
2421 * more than one character accumulated then start checking
2422 * the largest span of characters from the beginning to
2423 * see if there is a binding for them.
2424 *
2425 * For example if our buffer has ESC,CTRL-M,C the getBound()
2426 * called previously indicated that there is no binding for
2427 * this sequence, so this then checks ESC,CTRL-M, and failing
2428 * that, just ESC. Each keystroke that is pealed off the end
2429 * during these tests is stuffed onto the pushback buffer so
2430 * they won't be lost.
2431 *
2432 * If there is no binding found, then we go back to waiting for
2433 * input.
2434 */
2435 while ( o == null && sb.length() > 0 ) {
2436 c = sb.charAt( sb.length() - 1 );
2437 sb.setLength( sb.length() - 1 );
2438 Object o2 = getKeys().getBound( sb );
2439 if ( o2 instanceof KeyMap ) {
2440 o = ((KeyMap) o2).getAnotherKey();
2441 if ( o == null ) {
2442 continue;
2443 } else {
2444 pushBackChar.push( (char) c );
2445 }
2446 }
2447 }
2448
2449 if ( o == null ) {
2450 continue;
2451 }
2452 Log.trace("Binding: ", o);
2453
2454
2455 // Handle macros
2456 if (o instanceof String) {
2457 String macro = (String) o;
2458 for (int i = 0; i < macro.length(); i++) {
2459 pushBackChar.push(macro.charAt(macro.length() - 1 - i));
2460 }
2461 sb.setLength( 0 );
2462 continue;
2463 }
2464
2465 // Handle custom callbacks
2466 if (o instanceof ActionListener) {
2467 ((ActionListener) o).actionPerformed(null);
2468 sb.setLength( 0 );
2469 continue;
2470 }
2471
2472 // Search mode.
2473 //
2474 // Note that we have to do this first, because if there is a command
2475 // not linked to a search command, we leave the search mode and fall
2476 // through to the normal state.
2477 if (state == State.SEARCH || state == State.FORWARD_SEARCH) {
2478 int cursorDest = -1;
2479 switch ( ((Operation) o )) {
2480 case ABORT:
2481 state = State.NORMAL;
2482 buf.clear();
2483 buf.buffer.append(searchTerm);
2484 break;
2485
2486 case REVERSE_SEARCH_HISTORY:
2487 state = State.SEARCH;
2488 if (searchTerm.length() == 0) {
2489 searchTerm.append(previousSearchTerm);
2490 }
2491
2492 if (searchIndex > 0) {
2493 searchIndex = searchBackwards(searchTerm.toString(), searchIndex);
2494 }
2495 break;
2496
2497 case FORWARD_SEARCH_HISTORY:
2498 state = State.FORWARD_SEARCH;
2499 if (searchTerm.length() == 0) {
2500 searchTerm.append(previousSearchTerm);
2501 }
2502
2503 if (searchIndex > -1 && searchIndex < history.size() - 1) {
2504 searchIndex = searchForwards(searchTerm.toString(), searchIndex);
2505 }
2506 break;
2507
2508 case BACKWARD_DELETE_CHAR:
2509 if (searchTerm.length() > 0) {
2510 searchTerm.deleteCharAt(searchTerm.length() - 1);
2511 if (state == State.SEARCH) {
2512 searchIndex = searchBackwards(searchTerm.toString());
2513 } else {
2514 searchIndex = searchForwards(searchTerm.toString());
2515 }
2516 }
2517 break;
2518
2519 case SELF_INSERT:
2520 searchTerm.appendCodePoint(c);
2521 if (state == State.SEARCH) {
2522 searchIndex = searchBackwards(searchTerm.toString());
2523 } else {
2524 searchIndex = searchForwards(searchTerm.toString());
2525 }
2526 break;
2527
2528 default:
2529 // Set buffer and cursor position to the found string.
2530 if (searchIndex != -1) {
2531 history.moveTo(searchIndex);
2532 // set cursor position to the found string
2533 cursorDest = history.current().toString().indexOf(searchTerm.toString());
2534 }
2535 state = State.NORMAL;
2536 break;
2537 }
2538
2539 // if we're still in search mode, print the search status
2540 if (state == State.SEARCH || state == State.FORWARD_SEARCH) {
2541 if (searchTerm.length() == 0) {
2542 if (state == State.SEARCH) {
2543 printSearchStatus("", "");
2544 } else {
2545 printForwardSearchStatus("", "");
2546 }
2547 searchIndex = -1;
2548 } else {
2549 if (searchIndex == -1) {
2550 beep();
2551 printSearchStatus(searchTerm.toString(), "");
2552 } else if (state == State.SEARCH) {
2553 printSearchStatus(searchTerm.toString(), history.get(searchIndex).toString());
2554 } else {
2555 printForwardSearchStatus(searchTerm.toString(), history.get(searchIndex).toString());
2556 }
2557 }
2558 }
2559 // otherwise, restore the line
2560 else {
2561 restoreLine(originalPrompt, cursorDest);
2562 }
2563 }
2564 if (state != State.SEARCH && state != State.FORWARD_SEARCH) {
2565 /*
2566 * If this is still false at the end of the switch, then
2567 * we reset our repeatCount to 0.
2568 */
2569 boolean isArgDigit = false;
2570
2571 /*
2572 * Every command that can be repeated a specified number
2573 * of times, needs to know how many times to repeat, so
2574 * we figure that out here.
2575 */
2576 int count = (repeatCount == 0) ? 1 : repeatCount;
2577
2578 /*
2579 * Default success to true. You only need to explicitly
2580 * set it if something goes wrong.
2581 */
2582 success = true;
2583
2584 if (o instanceof Operation) {
2585 Operation op = (Operation)o;
2586 /*
2587 * Current location of the cursor (prior to the operation).
2588 * These are used by vi *-to operation (e.g. delete-to)
2589 * so we know where we came from.
2590 */
2591 int cursorStart = buf.cursor;
2592 State origState = state;
2593
2594 /*
2595 * If we are on a "vi" movement based operation, then we
2596 * need to restrict the sets of inputs pretty heavily.
2597 */
2598 if (state == State.VI_CHANGE_TO
2599 || state == State.VI_YANK_TO
2600 || state == State.VI_DELETE_TO) {
2601
2602 op = viDeleteChangeYankToRemap(op);
2603 }
2604
2605 switch ( op ) {
2606 case COMPLETE: // tab
2607 // There is an annoyance with tab completion in that
2608 // sometimes the user is actually pasting input in that
2609 // has physical tabs in it. This attempts to look at how
2610 // quickly a character follows the tab, if the character
2611 // follows *immediately*, we assume it is a tab literal.
2612 boolean isTabLiteral = false;
2613 if (copyPasteDetection
2614 && c == 9
2615 && (!pushBackChar.isEmpty()
2616 || (in.isNonBlockingEnabled() && in.peek(escapeTimeout) != -2))) {
2617 isTabLiteral = true;
2618 }
2619
2620 if (! isTabLiteral) {
2621 success = complete();
2622 }
2623 else {
2624 putString(sb);
2625 }
2626 break;
2627
2628 case POSSIBLE_COMPLETIONS:
2629 printCompletionCandidates();
2630 break;
2631
2632 case BEGINNING_OF_LINE:
2633 success = setCursorPosition(0);
2634 break;
2635
2636 case YANK:
2637 success = yank();
2638 break;
2639
2640 case YANK_POP:
2641 success = yankPop();
2642 break;
2643
2644 case KILL_LINE: // CTRL-K
2645 success = killLine();
2646 break;
2647
2648 case KILL_WHOLE_LINE:
2649 success = setCursorPosition(0) && killLine();
2650 break;
2651
2652 case CLEAR_SCREEN: // CTRL-L
2653 success = clearScreen();
2654 redrawLine();
2655 break;
2656
2657 case OVERWRITE_MODE:
2658 buf.setOverTyping(!buf.isOverTyping());
2659 break;
2660
2661 case SELF_INSERT:
2662 putString(sb);
2663 break;
2664
2665 case ACCEPT_LINE:
2666 return accept();
2667
2668 case ABORT:
2669 if (searchTerm == null) {
2670 abort();
2671 }
2672 break;
2673
2674 case INTERRUPT:
2675 if (handleUserInterrupt) {
2676 println();
2677 flush();
2678 String partialLine = buf.buffer.toString();
2679 buf.clear();
2680 history.moveToEnd();
2681 throw new UserInterruptException(partialLine);
2682 }
2683 break;
2684
2685 /*
2686 * VI_MOVE_ACCEPT_LINE is the result of an ENTER
2687 * while in move mode. This is the same as a normal
2688 * ACCEPT_LINE, except that we need to enter
2689 * insert mode as well.
2690 */
2691 case VI_MOVE_ACCEPT_LINE:
2692 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2693 return accept();
2694
2695 case BACKWARD_WORD:
2696 success = previousWord();
2697 break;
2698
2699 case FORWARD_WORD:
2700 success = nextWord();
2701 break;
2702
2703 case PREVIOUS_HISTORY:
2704 success = moveHistory(false);
2705 break;
2706
2707 /*
2708 * According to bash/readline move through history
2709 * in "vi" mode will move the cursor to the
2710 * start of the line. If there is no previous
2711 * history, then the cursor doesn't move.
2712 */
2713 case VI_PREVIOUS_HISTORY:
2714 success = moveHistory(false, count)
2715 && setCursorPosition(0);
2716 break;
2717
2718 case NEXT_HISTORY:
2719 success = moveHistory(true);
2720 break;
2721
2722 /*
2723 * According to bash/readline move through history
2724 * in "vi" mode will move the cursor to the
2725 * start of the line. If there is no next history,
2726 * then the cursor doesn't move.
2727 */
2728 case VI_NEXT_HISTORY:
2729 success = moveHistory(true, count)
2730 && setCursorPosition(0);
2731 break;
2732
2733 case BACKWARD_DELETE_CHAR: // backspace
2734 success = backspace();
2735 break;
2736
2737 case EXIT_OR_DELETE_CHAR:
2738 if (buf.buffer.length() == 0) {
2739 return null;
2740 }
2741 success = deleteCurrentCharacter();
2742 break;
2743
2744 case DELETE_CHAR: // delete
2745 success = deleteCurrentCharacter();
2746 break;
2747
2748 case BACKWARD_CHAR:
2749 success = moveCursor(-(count)) != 0;
2750 break;
2751
2752 case FORWARD_CHAR:
2753 success = moveCursor(count) != 0;
2754 break;
2755
2756 case UNIX_LINE_DISCARD:
2757 success = resetLine();
2758 break;
2759
2760 case UNIX_WORD_RUBOUT:
2761 success = unixWordRubout(count);
2762 break;
2763
2764 case BACKWARD_KILL_WORD:
2765 success = deletePreviousWord();
2766 break;
2767
2768 case KILL_WORD:
2769 success = deleteNextWord();
2770 break;
2771
2772 case BEGINNING_OF_HISTORY:
2773 success = history.moveToFirst();
2774 if (success) {
2775 setBuffer(history.current());
2776 }
2777 break;
2778
2779 case END_OF_HISTORY:
2780 success = history.moveToLast();
2781 if (success) {
2782 setBuffer(history.current());
2783 }
2784 break;
2785
2786 case HISTORY_SEARCH_BACKWARD:
2787 searchTerm = new StringBuffer(buf.upToCursor());
2788 searchIndex = searchBackwards(searchTerm.toString(), history.index(), true);
2789
2790 if (searchIndex == -1) {
2791 beep();
2792 } else {
2793 // Maintain cursor position while searching.
2794 success = history.moveTo(searchIndex);
2795 if (success) {
2796 setBufferKeepPos(history.current());
2797 }
2798 }
2799 break;
2800
2801 case HISTORY_SEARCH_FORWARD:
2802 searchTerm = new StringBuffer(buf.upToCursor());
2803 int index = history.index() + 1;
2804
2805 if (index == history.size()) {
2806 history.moveToEnd();
2807 setBufferKeepPos(searchTerm.toString());
2808 } else if (index < history.size()) {
2809 searchIndex = searchForwards(searchTerm.toString(), index, true);
2810 if (searchIndex == -1) {
2811 beep();
2812 } else {
2813 // Maintain cursor position while searching.
2814 success = history.moveTo(searchIndex);
2815 if (success) {
2816 setBufferKeepPos(history.current());
2817 }
2818 }
2819 }
2820 break;
2821
2822 case REVERSE_SEARCH_HISTORY:
2823 if (searchTerm != null) {
2824 previousSearchTerm = searchTerm.toString();
2825 }
2826 searchTerm = new StringBuffer(buf.buffer);
2827 state = State.SEARCH;
2828 if (searchTerm.length() > 0) {
2829 searchIndex = searchBackwards(searchTerm.toString());
2830 if (searchIndex == -1) {
2831 beep();
2832 }
2833 printSearchStatus(searchTerm.toString(),
2834 searchIndex > -1 ? history.get(searchIndex).toString() : "");
2835 } else {
2836 searchIndex = -1;
2837 printSearchStatus("", "");
2838 }
2839 break;
2840
2841 case FORWARD_SEARCH_HISTORY:
2842 if (searchTerm != null) {
2843 previousSearchTerm = searchTerm.toString();
2844 }
2845 searchTerm = new StringBuffer(buf.buffer);
2846 state = State.FORWARD_SEARCH;
2847 if (searchTerm.length() > 0) {
2848 searchIndex = searchForwards(searchTerm.toString());
2849 if (searchIndex == -1) {
2850 beep();
2851 }
2852 printForwardSearchStatus(searchTerm.toString(),
2853 searchIndex > -1 ? history.get(searchIndex).toString() : "");
2854 } else {
2855 searchIndex = -1;
2856 printForwardSearchStatus("", "");
2857 }
2858 break;
2859
2860 case CAPITALIZE_WORD:
2861 success = capitalizeWord();
2862 break;
2863
2864 case UPCASE_WORD:
2865 success = upCaseWord();
2866 break;
2867
2868 case DOWNCASE_WORD:
2869 success = downCaseWord();
2870 break;
2871
2872 case END_OF_LINE:
2873 success = moveToEnd();
2874 break;
2875
2876 case TAB_INSERT:
2877 putString( "\t" );
2878 break;
2879
2880 case RE_READ_INIT_FILE:
2881 consoleKeys.loadKeys(appName, inputrcUrl);
2882 break;
2883
2884 case START_KBD_MACRO:
2885 recording = true;
2886 break;
2887
2888 case END_KBD_MACRO:
2889 recording = false;
2890 macro = macro.substring(0, macro.length() - sb.length());
2891 break;
2892
2893 case CALL_LAST_KBD_MACRO:
2894 for (int i = 0; i < macro.length(); i++) {
2895 pushBackChar.push(macro.charAt(macro.length() - 1 - i));
2896 }
2897 sb.setLength( 0 );
2898 break;
2899
2900 case VI_EDITING_MODE:
2901 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2902 break;
2903
2904 case VI_MOVEMENT_MODE:
2905 /*
2906 * If we are re-entering move mode from an
2907 * aborted yank-to, delete-to, change-to then
2908 * don't move the cursor back. The cursor is
2909 * only move on an expclit entry to movement
2910 * mode.
2911 */
2912 if (state == state.NORMAL) {
2913 moveCursor(-1);
2914 }
2915 consoleKeys.setKeyMap(KeyMap.VI_MOVE);
2916 break;
2917
2918 case VI_INSERTION_MODE:
2919 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2920 break;
2921
2922 case VI_APPEND_MODE:
2923 moveCursor(1);
2924 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2925 break;
2926
2927 case VI_APPEND_EOL:
2928 success = moveToEnd();
2929 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
2930 break;
2931
2932 /*
2933 * Handler for CTRL-D. Attempts to follow readline
2934 * behavior. If the line is empty, then it is an EOF
2935 * otherwise it is as if the user hit enter.
2936 */
2937 case VI_EOF_MAYBE:
2938 if (buf.buffer.length() == 0) {
2939 return null;
2940 }
2941 return accept();
2942
2943 case TRANSPOSE_CHARS:
2944 success = transposeChars(count);
2945 break;
2946
2947 case INSERT_COMMENT:
2948 return insertComment (false);
2949
2950 case INSERT_CLOSE_CURLY:
2951 insertClose("}");
2952 break;
2953
2954 case INSERT_CLOSE_PAREN:
2955 insertClose(")");
2956 break;
2957
2958 case INSERT_CLOSE_SQUARE:
2959 insertClose("]");
2960 break;
2961
2962 case VI_INSERT_COMMENT:
2963 return insertComment (true);
2964
2965 case VI_MATCH:
2966 success = viMatch ();
2967 break;
2968
2969 case VI_SEARCH:
2970 int lastChar = viSearch(sb.charAt (0));
2971 if (lastChar != -1) {
2972 pushBackChar.push((char)lastChar);
2973 }
2974 break;
2975
2976 case VI_ARG_DIGIT:
2977 repeatCount = (repeatCount * 10) + sb.charAt(0) - '0';
2978 isArgDigit = true;
2979 break;
2980
2981 case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
2982 if (repeatCount > 0) {
2983 repeatCount = (repeatCount * 10) + sb.charAt(0) - '0';
2984 isArgDigit = true;
2985 }
2986 else {
2987 success = setCursorPosition(0);
2988 }
2989 break;
2990
2991 case VI_FIRST_PRINT:
2992 success = setCursorPosition(0) && viNextWord(1);
2993 break;
2994
2995 case VI_PREV_WORD:
2996 success = viPreviousWord(count);
2997 break;
2998
2999 case VI_NEXT_WORD:
3000 success = viNextWord(count);
3001 break;
3002
3003 case VI_END_WORD:
3004 success = viEndWord(count);
3005 break;
3006
3007 case VI_INSERT_BEG:
3008 success = setCursorPosition(0);
3009 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3010 break;
3011
3012 case VI_RUBOUT:
3013 success = viRubout(count);
3014 break;
3015
3016 case VI_DELETE:
3017 success = viDelete(count);
3018 break;
3019
3020 case VI_DELETE_TO:
3021 /*
3022 * This is a weird special case. In vi
3023 * "dd" deletes the current line. So if we
3024 * get a delete-to, followed by a delete-to,
3025 * we delete the line.
3026 */
3027 if (state == State.VI_DELETE_TO) {
3028 success = setCursorPosition(0) && killLine();
3029 state = origState = State.NORMAL;
3030 }
3031 else {
3032 state = State.VI_DELETE_TO;
3033 }
3034 break;
3035
3036 case VI_YANK_TO:
3037 // Similar to delete-to, a "yy" yanks the whole line.
3038 if (state == State.VI_YANK_TO) {
3039 yankBuffer = buf.buffer.toString();
3040 state = origState = State.NORMAL;
3041 }
3042 else {
3043 state = State.VI_YANK_TO;
3044 }
3045 break;
3046
3047 case VI_CHANGE_TO:
3048 if (state == State.VI_CHANGE_TO) {
3049 success = setCursorPosition(0) && killLine();
3050 state = origState = State.NORMAL;
3051 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3052 }
3053 else {
3054 state = State.VI_CHANGE_TO;
3055 }
3056 break;
3057
3058 case VI_KILL_WHOLE_LINE:
3059 success = setCursorPosition(0) && killLine();
3060 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3061 break;
3062
3063 case VI_PUT:
3064 success = viPut(count);
3065 break;
3066
3067 case VI_CHAR_SEARCH: {
3068 // ';' and ',' don't need another character. They indicate repeat next or repeat prev.
3069 int searchChar = (c != ';' && c != ',')
3070 ? (pushBackChar.isEmpty()
3071 ? readCharacter()
3072 : pushBackChar.pop ())
3073 : 0;
3074
3075 success = viCharSearch(count, c, searchChar);
3076 }
3077 break;
3078
3079 case VI_CHANGE_CASE:
3080 success = viChangeCase(count);
3081 break;
3082
3083 case VI_CHANGE_CHAR:
3084 success = viChangeChar(count,
3085 pushBackChar.isEmpty()
3086 ? readCharacter()
3087 : pushBackChar.pop());
3088 break;
3089
3090 case VI_DELETE_TO_EOL:
3091 success = viDeleteTo(buf.cursor, buf.buffer.length(), false);
3092 break;
3093
3094 case VI_CHANGE_TO_EOL:
3095 success = viDeleteTo(buf.cursor, buf.buffer.length(), true);
3096 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3097 break;
3098
3099 case EMACS_EDITING_MODE:
3100 consoleKeys.setKeyMap(KeyMap.EMACS);
3101 break;
3102
3103 default:
3104 break;
3105 }
3106
3107 /*
3108 * If we were in a yank-to, delete-to, move-to
3109 * when this operation started, then fall back to
3110 */
3111 if (origState != State.NORMAL) {
3112 if (origState == State.VI_DELETE_TO) {
3113 success = viDeleteTo(cursorStart, buf.cursor, false);
3114 }
3115 else if (origState == State.VI_CHANGE_TO) {
3116 success = viDeleteTo(cursorStart, buf.cursor, true);
3117 consoleKeys.setKeyMap(KeyMap.VI_INSERT);
3118 }
3119 else if (origState == State.VI_YANK_TO) {
3120 success = viYankTo(cursorStart, buf.cursor);
3121 }
3122 state = State.NORMAL;
3123 }
3124
3125 /*
3126 * Another subtly. The check for the NORMAL state is
3127 * to ensure that we do not clear out the repeat
3128 * count when in delete-to, yank-to, or move-to modes.
3129 */
3130 if (state == State.NORMAL && !isArgDigit) {
3131 /*
3132 * If the operation performed wasn't a vi argument
3133 * digit, then clear out the current repeatCount;
3134 */
3135 repeatCount = 0;
3136 }
3137
3138 if (state != State.SEARCH && state != State.FORWARD_SEARCH) {
3139 previousSearchTerm = "";
3140 searchTerm = null;
3141 searchIndex = -1;
3142 }
3143 }
3144 }
3145 if (!success) {
3146 beep();
3147 }
3148 sb.setLength( 0 );
3149 flush();
3150 }
3151 }
3152 finally {
3153 if (!terminal.isSupported()) {
3154 afterReadLine();
3155 }
3156 if (handleUserInterrupt && (terminal instanceof UnixTerminal)) {
3157 ((UnixTerminal) terminal).enableInterruptCharacter();
3158 }
3159 }
3160 }
3161
3162 /**
3163 * Read a line for unsupported terminals.
3164 */
3165 private String readLineSimple() throws IOException {
3166 StringBuilder buff = new StringBuilder();
3167
3168 if (skipLF) {
3169 skipLF = false;
3170
3171 int i = readCharacter();
3172
3173 if (i == -1 || i == '\r') {
3174 return buff.toString();
3175 } else if (i == '\n') {
3176 // ignore
3177 } else {
3178 buff.append((char) i);
3179 }
3180 }
3181
3182 while (true) {
3183 int i = readCharacter();
3184
3185 if (i == -1 && buff.length() == 0) {
3186 return null;
3187 }
3188
3189 if (i == -1 || i == '\n') {
3190 return buff.toString();
3191 } else if (i == '\r') {
3192 skipLF = true;
3193 return buff.toString();
3194 } else {
3195 buff.append((char) i);
3196 }
3197 }
3198 }
3199
3200 //
3201 // Completion
3202 //
3203
3204 private final List<Completer> completers = new LinkedList<Completer>();
3205
3206 private CompletionHandler completionHandler = new CandidateListCompletionHandler();
3207
3208 /**
3209 * Add the specified {@link jline.console.completer.Completer} to the list of handlers for tab-completion.
3210 *
3211 * @param completer the {@link jline.console.completer.Completer} to add
3212 * @return true if it was successfully added
3213 */
3214 public boolean addCompleter(final Completer completer) {
3215 return completers.add(completer);
3216 }
3217
3218 /**
3219 * Remove the specified {@link jline.console.completer.Completer} from the list of handlers for tab-completion.
3220 *
3221 * @param completer The {@link Completer} to remove
3222 * @return True if it was successfully removed
3223 */
3224 public boolean removeCompleter(final Completer completer) {
3225 return completers.remove(completer);
3226 }
3227
3228 /**
3229 * Returns an unmodifiable list of all the completers.
3230 */
3231 public Collection<Completer> getCompleters() {
3232 return Collections.unmodifiableList(completers);
3233 }
3234
3235 public void setCompletionHandler(final CompletionHandler handler) {
3236 this.completionHandler = checkNotNull(handler);
3237 }
3238
3239 public CompletionHandler getCompletionHandler() {
3240 return this.completionHandler;
3241 }
3242
3243 /**
3244 * Use the completers to modify the buffer with the appropriate completions.
3245 *
3246 * @return true if successful
3247 */
3248 protected boolean complete() throws IOException {
3249 // debug ("tab for (" + buf + ")");
3250 if (completers.size() == 0) {
3251 return false;
3252 }
3253
3254 List<CharSequence> candidates = new LinkedList<CharSequence>();
3255 String bufstr = buf.buffer.toString();
3256 int cursor = buf.cursor;
3257
3258 int position = -1;
3259
3260 for (Completer comp : completers) {
3261 if ((position = comp.complete(bufstr, cursor, candidates)) != -1) {
3262 break;
3263 }
3264 }
3265
3266 return candidates.size() != 0 && getCompletionHandler().complete(this, candidates, position);
3267 }
3268
3269 protected void printCompletionCandidates() throws IOException {
3270 // debug ("tab for (" + buf + ")");
3271 if (completers.size() == 0) {
3272 return;
3273 }
3274
3275 List<CharSequence> candidates = new LinkedList<CharSequence>();
3276 String bufstr = buf.buffer.toString();
3277 int cursor = buf.cursor;
3278
3279 for (Completer comp : completers) {
3280 if (comp.complete(bufstr, cursor, candidates) != -1) {
3281 break;
3282 }
3283 }
3284 CandidateListCompletionHandler.printCandidates(this, candidates);
3285 drawLine();
3286 }
3287
3288 /**
3289 * The number of tab-completion candidates above which a warning will be
3290 * prompted before showing all the candidates.
3291 */
3292 private int autoprintThreshold = Configuration.getInteger(JLINE_COMPLETION_THRESHOLD, 100); // same default as bash
3293
3294 /**
3295 * @param threshold the number of candidates to print without issuing a warning.
3296 */
3297 public void setAutoprintThreshold(final int threshold) {
3298 this.autoprintThreshold = threshold;
3299 }
3300
3301 /**
3302 * @return the number of candidates to print without issuing a warning.
3303 */
3304 public int getAutoprintThreshold() {
3305 return autoprintThreshold;
3306 }
3307
3308 private boolean paginationEnabled;
3309
3310 /**
3311 * Whether to use pagination when the number of rows of candidates exceeds the height of the terminal.
3312 */
3313 public void setPaginationEnabled(final boolean enabled) {
3314 this.paginationEnabled = enabled;
3315 }
3316
3317 /**
3318 * Whether to use pagination when the number of rows of candidates exceeds the height of the terminal.
3319 */
3320 public boolean isPaginationEnabled() {
3321 return paginationEnabled;
3322 }
3323
3324 //
3325 // History
3326 //
3327
3328 private History history = new MemoryHistory();
3329
3330 public void setHistory(final History history) {
3331 this.history = history;
3332 }
3333
3334 public History getHistory() {
3335 return history;
3336 }
3337
3338 private boolean historyEnabled = true;
3339
3340 /**
3341 * Whether or not to add new commands to the history buffer.
3342 */
3343 public void setHistoryEnabled(final boolean enabled) {
3344 this.historyEnabled = enabled;
3345 }
3346
3347 /**
3348 * Whether or not to add new commands to the history buffer.
3349 */
3350 public boolean isHistoryEnabled() {
3351 return historyEnabled;
3352 }
3353
3354 /**
3355 * Used in "vi" mode for argumented history move, to move a specific
3356 * number of history entries forward or back.
3357 *
3358 * @param next If true, move forward
3359 * @param count The number of entries to move
3360 * @return true if the move was successful
3361 * @throws IOException
3362 */
3363 private boolean moveHistory(final boolean next, int count) throws IOException {
3364 boolean ok = true;
3365 for (int i = 0; i < count && (ok = moveHistory(next)); i++) {
3366 /* empty */
3367 }
3368 return ok;
3369 }
3370
3371 /**
3372 * Move up or down the history tree.
3373 */
3374 private boolean moveHistory(final boolean next) throws IOException {
3375 if (next && !history.next()) {
3376 return false;
3377 }
3378 else if (!next && !history.previous()) {
3379 return false;
3380 }
3381
3382 setBuffer(history.current());
3383
3384 return true;
3385 }
3386
3387 //
3388 // Printing
3389 //
3390
3391 public static final String CR = Configuration.getLineSeparator();
3392
3393 /**
3394 * Output the specified character to the output stream without manipulating the current buffer.
3395 */
3396 private void print(final int c) throws IOException {
3397 if (c == '\t') {
3398 char chars[] = new char[TAB_WIDTH];
3399 Arrays.fill(chars, ' ');
3400 out.write(chars);
3401 return;
3402 }
3403
3404 out.write(c);
3405 }
3406
3407 /**
3408 * Output the specified characters to the output stream without manipulating the current buffer.
3409 */
3410 private void print(final char... buff) throws IOException {
3411 int len = 0;
3412 for (char c : buff) {
3413 if (c == '\t') {
3414 len += TAB_WIDTH;
3415 }
3416 else {
3417 len++;
3418 }
3419 }
3420
3421 char chars[];
3422 if (len == buff.length) {
3423 chars = buff;
3424 }
3425 else {
3426 chars = new char[len];
3427 int pos = 0;
3428 for (char c : buff) {
3429 if (c == '\t') {
3430 Arrays.fill(chars, pos, pos + TAB_WIDTH, ' ');
3431 pos += TAB_WIDTH;
3432 }
3433 else {
3434 chars[pos] = c;
3435 pos++;
3436 }
3437 }
3438 }
3439
3440 out.write(chars);
3441 }
3442
3443 private void print(final char c, final int num) throws IOException {
3444 if (num == 1) {
3445 print(c);
3446 }
3447 else {
3448 char[] chars = new char[num];
3449 Arrays.fill(chars, c);
3450 print(chars);
3451 }
3452 }
3453
3454 /**
3455 * Output the specified string to the output stream (but not the buffer).
3456 */
3457 public final void print(final CharSequence s) throws IOException {
3458 print(checkNotNull(s).toString().toCharArray());
3459 }
3460
3461 public final void println(final CharSequence s) throws IOException {
3462 print(checkNotNull(s).toString().toCharArray());
3463 println();
3464 }
3465
3466 /**
3467 * Output a platform-dependant newline.
3468 */
3469 public final void println() throws IOException {
3470 print(CR);
3471 // flush();
3472 }
3473
3474 //
3475 // Actions
3476 //
3477
3478 /**
3479 * Issue a delete.
3480 *
3481 * @return true if successful
3482 */
3483 public final boolean delete() throws IOException {
3484 if (buf.cursor == buf.buffer.length()) {
3485 return false;
3486 }
3487
3488 buf.buffer.delete(buf.cursor, buf.cursor + 1);
3489 drawBuffer(1);
3490
3491 return true;
3492 }
3493
3494 /**
3495 * Kill the buffer ahead of the current cursor position.
3496 *
3497 * @return true if successful
3498 */
3499 public boolean killLine() throws IOException {
3500 int cp = buf.cursor;
3501 int len = buf.buffer.length();
3502
3503 if (cp >= len) {
3504 return false;
3505 }
3506
3507 int num = len - cp;
3508 clearAhead(num, 0);
3509
3510 char[] killed = new char[num];
3511 buf.buffer.getChars(cp, (cp + num), killed, 0);
3512 buf.buffer.delete(cp, (cp + num));
3513
3514 String copy = new String(killed);
3515 killRing.add(copy);
3516
3517 return true;
3518 }
3519
3520 public boolean yank() throws IOException {
3521 String yanked = killRing.yank();
3522
3523 if (yanked == null) {
3524 return false;
3525 }
3526 putString(yanked);
3527 return true;
3528 }
3529
3530 public boolean yankPop() throws IOException {
3531 if (!killRing.lastYank()) {
3532 return false;
3533 }
3534 String current = killRing.yank();
3535 if (current == null) {
3536 // This shouldn't happen.
3537 return false;
3538 }
3539 backspace(current.length());
3540 String yanked = killRing.yankPop();
3541 if (yanked == null) {
3542 // This shouldn't happen.
3543 return false;
3544 }
3545
3546 putString(yanked);
3547 return true;
3548 }
3549
3550 /**
3551 * Clear the screen by issuing the ANSI "clear screen" code.
3552 */
3553 public boolean clearScreen() throws IOException {
3554 if (!terminal.isAnsiSupported()) {
3555 return false;
3556 }
3557
3558 // send the ANSI code to clear the screen
3559 printAnsiSequence("2J");
3560
3561 // then send the ANSI code to go to position 1,1
3562 printAnsiSequence("1;1H");
3563
3564 return true;
3565 }
3566
3567 /**
3568 * Issue an audible keyboard bell.
3569 */
3570 public void beep() throws IOException {
3571 if (bellEnabled) {
3572 print(KEYBOARD_BELL);
3573 // need to flush so the console actually beeps
3574 flush();
3575 }
3576 }
3577
3578 /**
3579 * Paste the contents of the clipboard into the console buffer
3580 *
3581 * @return true if clipboard contents pasted
3582 */
3583 public boolean paste() throws IOException {
3584 Clipboard clipboard;
3585 try { // May throw ugly exception on system without X
3586 clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
3587 }
3588 catch (Exception e) {
3589 return false;
3590 }
3591
3592 if (clipboard == null) {
3593 return false;
3594 }
3595
3596 Transferable transferable = clipboard.getContents(null);
3597
3598 if (transferable == null) {
3599 return false;
3600 }
3601
3602 try {
3603 Object content = transferable.getTransferData(DataFlavor.plainTextFlavor);
3604
3605 // This fix was suggested in bug #1060649 at
3606 // http://sourceforge.net/tracker/index.php?func=detail&aid=1060649&group_id=64033&atid=506056
3607 // to get around the deprecated DataFlavor.plainTextFlavor, but it
3608 // raises a UnsupportedFlavorException on Mac OS X
3609
3610 if (content == null) {
3611 try {
3612 content = new DataFlavor().getReaderForText(transferable);
3613 }
3614 catch (Exception e) {
3615 // ignore
3616 }
3617 }
3618
3619 if (content == null) {
3620 return false;
3621 }
3622
3623 String value;
3624
3625 if (content instanceof Reader) {
3626 // TODO: we might want instead connect to the input stream
3627 // so we can interpret individual lines
3628 value = "";
3629 String line;
3630
3631 BufferedReader read = new BufferedReader((Reader) content);
3632 while ((line = read.readLine()) != null) {
3633 if (value.length() > 0) {
3634 value += "\n";
3635 }
3636
3637 value += line;
3638 }
3639 }
3640 else {
3641 value = content.toString();
3642 }
3643
3644 if (value == null) {
3645 return true;
3646 }
3647
3648 putString(value);
3649
3650 return true;
3651 }
3652 catch (UnsupportedFlavorException e) {
3653 Log.error("Paste failed: ", e);
3654
3655 return false;
3656 }
3657 }
3658
3659 //
3660 // Triggered Actions
3661 //
3662
3663 private final Map<Character, ActionListener> triggeredActions = new HashMap<Character, ActionListener>();
3664
3665 /**
3666 * Adding a triggered Action allows to give another curse of action if a character passed the pre-processing.
3667 * <p/>
3668 * Say you want to close the application if the user enter q.
3669 * addTriggerAction('q', new ActionListener(){ System.exit(0); }); would do the trick.
3670 */
3671 public void addTriggeredAction(final char c, final ActionListener listener) {
3672 triggeredActions.put(c, listener);
3673 }
3674
3675 //
3676 // Formatted Output
3677 //
3678
3679 /**
3680 * Output the specified {@link Collection} in proper columns.
3681 */
3682 public void printColumns(final Collection<? extends CharSequence> items) throws IOException {
3683 if (items == null || items.isEmpty()) {
3684 return;
3685 }
3686
3687 int width = getTerminal().getWidth();
3688 int height = getTerminal().getHeight();
3689
3690 int maxWidth = 0;
3691 for (CharSequence item : items) {
3692 maxWidth = Math.max(maxWidth, item.length());
3693 }
3694 maxWidth = maxWidth + 3;
3695 Log.debug("Max width: ", maxWidth);
3696
3697 int showLines;
3698 if (isPaginationEnabled()) {
3699 showLines = height - 1; // page limit
3700 }
3701 else {
3702 showLines = Integer.MAX_VALUE;
3703 }
3704
3705 StringBuilder buff = new StringBuilder();
3706 for (CharSequence item : items) {
3707 if ((buff.length() + maxWidth) > width) {
3708 println(buff);
3709 buff.setLength(0);
3710
3711 if (--showLines == 0) {
3712 // Overflow
3713 print(resources.getString("DISPLAY_MORE"));
3714 flush();
3715 int c = readCharacter();
3716 if (c == '\r' || c == '\n') {
3717 // one step forward
3718 showLines = 1;
3719 }
3720 else if (c != 'q') {
3721 // page forward
3722 showLines = height - 1;
3723 }
3724
3725 back(resources.getString("DISPLAY_MORE").length());
3726 if (c == 'q') {
3727 // cancel
3728 break;
3729 }
3730 }
3731 }
3732
3733 // NOTE: toString() is important here due to AnsiString being retarded
3734 buff.append(item.toString());
3735 for (int i = 0; i < (maxWidth - item.length()); i++) {
3736 buff.append(' ');
3737 }
3738 }
3739
3740 if (buff.length() > 0) {
3741 println(buff);
3742 }
3743 }
3744
3745 //
3746 // Non-supported Terminal Support
3747 //
3748
3749 private Thread maskThread;
3750
3751 private void beforeReadLine(final String prompt, final Character mask) {
3752 if (mask != null && maskThread == null) {
3753 final String fullPrompt = "\r" + prompt
3754 + " "
3755 + " "
3756 + " "
3757 + "\r" + prompt;
3758
3759 maskThread = new Thread()
3760 {
3761 public void run() {
3762 while (!interrupted()) {
3763 try {
3764 Writer out = getOutput();
3765 out.write(fullPrompt);
3766 out.flush();
3767 sleep(3);
3768 }
3769 catch (IOException e) {
3770 return;
3771 }
3772 catch (InterruptedException e) {
3773 return;
3774 }
3775 }
3776 }
3777 };
3778
3779 maskThread.setPriority(Thread.MAX_PRIORITY);
3780 maskThread.setDaemon(true);
3781 maskThread.start();
3782 }
3783 }
3784
3785 private void afterReadLine() {
3786 if (maskThread != null && maskThread.isAlive()) {
3787 maskThread.interrupt();
3788 }
3789
3790 maskThread = null;
3791 }
3792
3793 /**
3794 * Erases the current line with the existing prompt, then redraws the line
3795 * with the provided prompt and buffer
3796 * @param prompt
3797 * the new prompt
3798 * @param buffer
3799 * the buffer to be drawn
3800 * @param cursorDest
3801 * where you want the cursor set when the line has been drawn.
3802 * -1 for end of line.
3803 * */
3804 public void resetPromptLine(String prompt, String buffer, int cursorDest) throws IOException {
3805 // move cursor to end of line
3806 moveToEnd();
3807
3808 // backspace all text, including prompt
3809 buf.buffer.append(this.prompt);
3810 int promptLength = 0;
3811 if (this.prompt != null) {
3812 promptLength = this.prompt.length();
3813 }
3814
3815 buf.cursor += promptLength;
3816 setPrompt("");
3817 backspaceAll();
3818
3819 setPrompt(prompt);
3820 redrawLine();
3821 setBuffer(buffer);
3822
3823 // move cursor to destination (-1 will move to end of line)
3824 if (cursorDest < 0) cursorDest = buffer.length();
3825 setCursorPosition(cursorDest);
3826
3827 flush();
3828 }
3829
3830 public void printSearchStatus(String searchTerm, String match) throws IOException {
3831 printSearchStatus(searchTerm, match, "(reverse-i-search)`");
3832 }
3833
3834 public void printForwardSearchStatus(String searchTerm, String match) throws IOException {
3835 printSearchStatus(searchTerm, match, "(i-search)`");
3836 }
3837
3838 private void printSearchStatus(String searchTerm, String match, String searchLabel) throws IOException {
3839 String prompt = searchLabel + searchTerm + "': ";
3840 int cursorDest = match.indexOf(searchTerm);
3841 resetPromptLine(prompt, match, cursorDest);
3842 }
3843
3844 public void restoreLine(String originalPrompt, int cursorDest) throws IOException {
3845 // TODO move cursor to matched string
3846 String prompt = lastLine(originalPrompt);
3847 String buffer = buf.buffer.toString();
3848 resetPromptLine(prompt, buffer, cursorDest);
3849 }
3850
3851 //
3852 // History search
3853 //
3854 /**
3855 * Search backward in history from a given position.
3856 *
3857 * @param searchTerm substring to search for.
3858 * @param startIndex the index from which on to search
3859 * @return index where this substring has been found, or -1 else.
3860 */
3861 public int searchBackwards(String searchTerm, int startIndex) {
3862 return searchBackwards(searchTerm, startIndex, false);
3863 }
3864
3865 /**
3866 * Search backwards in history from the current position.
3867 *
3868 * @param searchTerm substring to search for.
3869 * @return index where the substring has been found, or -1 else.
3870 */
3871 public int searchBackwards(String searchTerm) {
3872 return searchBackwards(searchTerm, history.index());
3873 }
3874
3875
3876 public int searchBackwards(String searchTerm, int startIndex, boolean startsWith) {
3877 ListIterator<History.Entry> it = history.entries(startIndex);
3878 while (it.hasPrevious()) {
3879 History.Entry e = it.previous();
3880 if (startsWith) {
3881 if (e.value().toString().startsWith(searchTerm)) {
3882 return e.index();
3883 }
3884 } else {
3885 if (e.value().toString().contains(searchTerm)) {
3886 return e.index();
3887 }
3888 }
3889 }
3890 return -1;
3891 }
3892
3893 /**
3894 * Search forward in history from a given position.
3895 *
3896 * @param searchTerm substring to search for.
3897 * @param startIndex the index from which on to search
3898 * @return index where this substring has been found, or -1 else.
3899 */
3900 public int searchForwards(String searchTerm, int startIndex) {
3901 return searchForwards(searchTerm, startIndex, false);
3902 }
3903 /**
3904 * Search forwards in history from the current position.
3905 *
3906 * @param searchTerm substring to search for.
3907 * @return index where the substring has been found, or -1 else.
3908 */
3909 public int searchForwards(String searchTerm) {
3910 return searchForwards(searchTerm, history.index());
3911 }
3912
3913 public int searchForwards(String searchTerm, int startIndex, boolean startsWith) {
3914 if (startIndex >= history.size()) {
3915 startIndex = history.size() - 1;
3916 }
3917
3918 ListIterator<History.Entry> it = history.entries(startIndex);
3919
3920 if (searchIndex != -1 && it.hasNext()) {
3921 it.next();
3922 }
3923
3924 while (it.hasNext()) {
3925 History.Entry e = it.next();
3926 if (startsWith) {
3927 if (e.value().toString().startsWith(searchTerm)) {
3928 return e.index();
3929 }
3930 } else {
3931 if (e.value().toString().contains(searchTerm)) {
3932 return e.index();
3933 }
3934 }
3935 }
3936 return -1;
3937 }
3938
3939 //
3940 // Helpers
3941 //
3942
3943 /**
3944 * Checks to see if the specified character is a delimiter. We consider a
3945 * character a delimiter if it is anything but a letter or digit.
3946 *
3947 * @param c The character to test
3948 * @return True if it is a delimiter
3949 */
3950 private boolean isDelimiter(final char c) {
3951 return !Character.isLetterOrDigit(c);
3952 }
3953
3954 /**
3955 * Checks to see if a character is a whitespace character. Currently
3956 * this delegates to {@link Character#isWhitespace(char)}, however
3957 * eventually it should be hooked up so that the definition of whitespace
3958 * can be configured, as readline does.
3959 *
3960 * @param c The character to check
3961 * @return true if the character is a whitespace
3962 */
3963 private boolean isWhitespace(final char c) {
3964 return Character.isWhitespace (c);
3965 }
3966
3967 private void printAnsiSequence(String sequence) throws IOException {
3968 print(27);
3969 print('[');
3970 print(sequence);
3971 flush(); // helps with step debugging
3972 }
3973
3974 }