1
2
3
4
5
6
7
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
61
62
63
64
65
66
67
68
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
124
125
126
127 private NonBlockingInputStream in;
128 private long escapeTimeout;
129 private Reader reader;
130
131
132
133
134
135 private boolean isUnitTestInput;
136
137
138
139
140 private char charSearchChar = 0;
141 private char charSearchLastInvokeChar = 0;
142 private char charSearchFirstInvokeChar = 0;
143
144
145
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
169
170
171
172
173
174 private boolean copyPasteDetection = false;
175
176
177
178
179 private State state = State.NORMAL;
180
181
182
183
184 private static enum State {
185
186
187
188 NORMAL,
189
190
191
192 SEARCH,
193 FORWARD_SEARCH,
194
195
196
197 VI_YANK_TO,
198
199
200
201 VI_DELETE_TO,
202
203
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
260
261
262
263
264
265
266
267
268
269
270 this.isUnitTestInput = in instanceof ByteArrayInputStream;
271 boolean nonBlockingEnabled =
272 escapeTimeout > 0L
273 && terminal.isSupported()
274 && in != null;
275
276
277
278
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
292
293
294
295 public void shutdown() {
296 if (in != null) {
297 in.shutdown();
298 }
299 }
300
301
302
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
340
341
342
343
344 public void setCopyPasteDetection(final boolean onoff) {
345 copyPasteDetection = onoff;
346 }
347
348
349
350
351 public boolean isCopyPasteDetectionEnabled() {
352 return copyPasteDetection;
353 }
354
355
356
357
358
359
360
361 public void setBellEnabled(boolean enabled) {
362 this.bellEnabled = enabled;
363 }
364
365
366
367
368
369
370
371 public boolean getBellEnabled() {
372 return bellEnabled;
373 }
374
375
376
377
378
379
380
381
382
383 public void setHandleUserInterrupt(boolean enabled)
384 {
385 this.handleUserInterrupt = enabled;
386 }
387
388
389
390
391
392
393
394 public boolean getHandleUserInterrupt()
395 {
396 return handleUserInterrupt;
397 }
398
399
400
401
402
403
404
405 public void setCommentBegin(String commentBegin) {
406 this.commentBegin = commentBegin;
407 }
408
409
410
411
412
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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458 public void setEchoCharacter(final Character c) {
459 this.echoCharacter = c;
460 }
461
462
463
464
465 public Character getEchoCharacter() {
466 return echoCharacter;
467 }
468
469
470
471
472
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
499 return promptLen + buf.cursor;
500 }
501
502
503
504
505
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
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
544
545
546
547
548 private void setBuffer(final String buffer) throws IOException {
549
550 if (buffer.equals(buf.buffer.toString())) {
551 return;
552 }
553
554
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) {
569 moveToEnd();
570 diff = buf.buffer.length() - sameIndex;
571 }
572
573 backspace(diff);
574 killLine();
575 buf.buffer.setLength(sameIndex);
576 putString(buffer.substring(sameIndex));
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
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) {
605 back(buf.length() - buf.cursor - 1);
606 }
607
608 drawBuffer();
609 }
610
611
612
613
614 public final void redrawLine() throws IOException {
615 print(RESET_LINE);
616
617 drawLine();
618 }
619
620
621
622
623
624
625 final String finishBuffer() throws IOException {
626 String str = buf.buffer.toString();
627 String historyLine = str;
628
629 if (expandEvents) {
630 try {
631 str = expandEvents(str);
632
633 historyLine = str.replace("!", "\\!");
634
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
645
646
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
666
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
675
676
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
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
827
828 public final void putString(final CharSequence str) throws IOException {
829 buf.write(str);
830 if (mask == null) {
831
832 print(str);
833 } else if (mask == NULL_MASK) {
834
835 } else {
836 print(mask, str.length());
837 }
838 drawBuffer();
839 }
840
841
842
843
844
845
846
847 private void drawBuffer(final int clear) throws IOException {
848
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
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);
863 print(13);
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
881
882
883 if (getCursorPosition() > 0 && (getCursorPosition() % width == 0)
884 && buf.cursor == buf.length() && clear == 0) {
885
886
887 print(32);
888 print(13);
889 }
890 }
891 }
892
893
894
895
896
897 private void drawBuffer() throws IOException {
898 drawBuffer(0);
899 }
900
901
902
903
904
905
906
907
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
918 printAnsiSequence("K");
919
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
935 print(' ', num);
936
937
938
939
940
941
942 back(num);
943
944
945 }
946
947
948
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
969 }
970
971
972
973
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
985
986
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
1002 printAnsiSequence("K");
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020 }
1021 }
1022 drawBuffer(count);
1023
1024 return count;
1025 }
1026
1027
1028
1029
1030
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
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
1058
1059
1060
1061
1062
1063
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
1094
1095
1096
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
1108
1109
1110
1111
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
1123
1124
1125
1126
1127
1128
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
1153
1154
1155
1156
1157
1158
1159 private boolean viChangeChar(int count, int c) throws IOException {
1160
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
1181
1182
1183
1184
1185
1186
1187
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
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
1216
1217
1218
1219
1220
1221
1222
1223
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
1242
1243
1244
1245
1246 if (! isChange && startPos > 0 && startPos == buf.length()) {
1247 moveCursor(-1);
1248 }
1249 return true;
1250 }
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
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
1280
1281
1282 setCursorPosition(cursorPos);
1283 return true;
1284 }
1285
1286
1287
1288
1289
1290
1291
1292
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
1310
1311
1312
1313
1314
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
1327
1328
1329
1330
1331
1332
1333
1334 if (invokeChar == ';' || invokeChar == ',') {
1335
1336 if (charSearchChar == 0) {
1337 return false;
1338 }
1339
1340
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
1385
1386
1387
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
1423
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
1433
1434
1435
1436
1437
1438
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
1446 while (pos < end && !isDelimiter(buf.buffer.charAt(pos))) {
1447 ++pos;
1448 }
1449
1450
1451
1452
1453
1454
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
1469
1470
1471
1472
1473
1474
1475
1476
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
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
1505 }
1506
1507 while (!isDelimiter(buf.current()) && (moveCursor(-1) != 0)) {
1508
1509 }
1510
1511 return true;
1512 }
1513
1514 private boolean nextWord() throws IOException {
1515 while (isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) {
1516
1517 }
1518
1519 while (!isDelimiter(buf.nextChar()) && (moveCursor(1) != 0)) {
1520
1521 }
1522
1523 return true;
1524 }
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
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
1585
1586
1587
1588
1589
1590
1591
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
1598 print(str);
1599 } else if (mask == NULL_MASK) {
1600
1601 } else {
1602 print(mask, str.length());
1603 }
1604 }
1605 drawBuffer();
1606 return true;
1607 }
1608
1609
1610
1611
1612
1613 private int viSearch(char searchChar) throws IOException {
1614 boolean isForward = (searchChar == '/');
1615
1616
1617
1618
1619
1620 CursorBuffer origBuffer = buf.copy();
1621
1622
1623 setCursorPosition (0);
1624 killLine();
1625
1626
1627 putString(Character.toString(searchChar));
1628 flush();
1629
1630 boolean isAborted = false;
1631 boolean isComplete = false;
1632
1633
1634
1635
1636
1637 int ch = -1;
1638 while (!isAborted && !isComplete && (ch = readCharacter()) != -1) {
1639 switch (ch) {
1640 case '\033':
1641
1642
1643
1644
1645 isAborted = true;
1646 break;
1647 case '\010':
1648 case '\177':
1649 backspace();
1650
1651
1652
1653 if (buf.cursor == 0) {
1654 isAborted = true;
1655 }
1656 break;
1657 case '\012':
1658 case '\015':
1659 isComplete = true;
1660 break;
1661 default:
1662 putString(Character.toString((char) ch));
1663 }
1664
1665 flush();
1666 }
1667
1668
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
1679
1680
1681 String searchTerm = buf.buffer.substring(1);
1682 int idx = -1;
1683
1684
1685
1686
1687
1688
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
1712
1713
1714 if (idx == -1) {
1715 setCursorPosition(0);
1716 killLine();
1717 putString(origBuffer.buffer);
1718 setCursorPosition(0);
1719 return -1;
1720 }
1721
1722
1723
1724
1725 setCursorPosition(0);
1726 killLine();
1727 putString(history.get(idx));
1728 setCursorPosition(0);
1729 flush();
1730
1731
1732
1733
1734
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
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
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
1801
1802
1803
1804
1805
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
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
1840
1841
1842 if (move > 0 && isInViMoveOperationState())
1843 ++pos;
1844
1845 setCursorPosition(pos);
1846 return true;
1847 }
1848
1849
1850
1851
1852
1853
1854
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
1962
1963
1964
1965
1966
1967
1968
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
1984 moveInternal(-1);
1985 drawBuffer();
1986 moveInternal(2);
1987 }
1988
1989 return true;
1990 }
1991
1992 public boolean isKeyMap(String name) {
1993
1994 KeyMap map = consoleKeys.getKeys();
1995 KeyMap mapByName = consoleKeys.getKeyMaps().get(name);
1996
1997 if (mapByName == null)
1998 return false;
1999
2000
2001
2002
2003
2004 return map == mapByName;
2005 }
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015 public String accept() throws IOException {
2016 moveToEnd();
2017 println();
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
2031
2032
2033
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
2060
2061
2062
2063 private void moveInternal(final int where) throws IOException {
2064
2065
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
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
2116 if (mask == NULL_MASK) {
2117 return;
2118 }
2119
2120 print(c, Math.abs(where));
2121 }
2122
2123
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
2141
2142
2143
2144 public final int readCharacter() throws IOException {
2145 int c = reader.read();
2146 if (c >= 0) {
2147 Log.trace("Keystroke: ", c);
2148
2149 if (terminal.isSupported()) {
2150 clearEcho(c);
2151 }
2152 }
2153 return c;
2154 }
2155
2156
2157
2158
2159 private int clearEcho(final int c) throws IOException {
2160
2161 if (!terminal.isEchoEnabled()) {
2162 return 0;
2163 }
2164
2165
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
2175
2176 if (c == 9) {
2177 int tabStop = 8;
2178 int position = getCursorPosition();
2179
2180 return tabStop - (position % tabStop);
2181 }
2182
2183 return getPrintableCharacters(c).length();
2184 }
2185
2186
2187
2188
2189
2190
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
2232 char c;
2233
2234 Arrays.sort(allowed);
2235
2236 while (Arrays.binarySearch(allowed, c = (char) readCharacter()) < 0) {
2237
2238 }
2239
2240 return c;
2241 }
2242
2243
2244
2245
2246
2247 public static final String JLINE_COMPLETION_THRESHOLD = "jline.completion.threshold";
2248
2249
2250
2251
2252
2253
2254
2255
2256 public String readLine() throws IOException {
2257 return readLine((String) null);
2258 }
2259
2260
2261
2262
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
2274
2275
2276
2277
2278
2279 public boolean setKeyMap(String name) {
2280 return consoleKeys.setKeyMap(name);
2281 }
2282
2283
2284
2285
2286
2287
2288
2289 public String getKeyMap() {
2290 return consoleKeys.getKeys().getName();
2291 }
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301 public String readLine(String prompt, final Character mask) throws IOException {
2302
2303
2304
2305
2306
2307
2308
2309
2310 int repeatCount = 0;
2311
2312
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
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
2362
2363
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
2384
2385
2386
2387
2388
2389 if ( o instanceof KeyMap ) {
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
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
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
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
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
2466 if (o instanceof ActionListener) {
2467 ((ActionListener) o).actionPerformed(null);
2468 sb.setLength( 0 );
2469 continue;
2470 }
2471
2472
2473
2474
2475
2476
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
2530 if (searchIndex != -1) {
2531 history.moveTo(searchIndex);
2532
2533 cursorDest = history.current().toString().indexOf(searchTerm.toString());
2534 }
2535 state = State.NORMAL;
2536 break;
2537 }
2538
2539
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
2560 else {
2561 restoreLine(originalPrompt, cursorDest);
2562 }
2563 }
2564 if (state != State.SEARCH && state != State.FORWARD_SEARCH) {
2565
2566
2567
2568
2569 boolean isArgDigit = false;
2570
2571
2572
2573
2574
2575
2576 int count = (repeatCount == 0) ? 1 : repeatCount;
2577
2578
2579
2580
2581
2582 success = true;
2583
2584 if (o instanceof Operation) {
2585 Operation op = (Operation)o;
2586
2587
2588
2589
2590
2591 int cursorStart = buf.cursor;
2592 State origState = state;
2593
2594
2595
2596
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:
2607
2608
2609
2610
2611
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:
2645 success = killLine();
2646 break;
2647
2648 case KILL_WHOLE_LINE:
2649 success = setCursorPosition(0) && killLine();
2650 break;
2651
2652 case CLEAR_SCREEN:
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
2687
2688
2689
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
2709
2710
2711
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
2724
2725
2726
2727
2728 case VI_NEXT_HISTORY:
2729 success = moveHistory(true, count)
2730 && setCursorPosition(0);
2731 break;
2732
2733 case BACKWARD_DELETE_CHAR:
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:
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
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
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
2907
2908
2909
2910
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
2934
2935
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
3023
3024
3025
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
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
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
3109
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
3127
3128
3129
3130 if (state == State.NORMAL && !isArgDigit) {
3131
3132
3133
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
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
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
3202
3203
3204 private final List<Completer> completers = new LinkedList<Completer>();
3205
3206 private CompletionHandler completionHandler = new CandidateListCompletionHandler();
3207
3208
3209
3210
3211
3212
3213
3214 public boolean addCompleter(final Completer completer) {
3215 return completers.add(completer);
3216 }
3217
3218
3219
3220
3221
3222
3223
3224 public boolean removeCompleter(final Completer completer) {
3225 return completers.remove(completer);
3226 }
3227
3228
3229
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
3245
3246
3247
3248 protected boolean complete() throws IOException {
3249
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
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
3290
3291
3292 private int autoprintThreshold = Configuration.getInteger(JLINE_COMPLETION_THRESHOLD, 100);
3293
3294
3295
3296
3297 public void setAutoprintThreshold(final int threshold) {
3298 this.autoprintThreshold = threshold;
3299 }
3300
3301
3302
3303
3304 public int getAutoprintThreshold() {
3305 return autoprintThreshold;
3306 }
3307
3308 private boolean paginationEnabled;
3309
3310
3311
3312
3313 public void setPaginationEnabled(final boolean enabled) {
3314 this.paginationEnabled = enabled;
3315 }
3316
3317
3318
3319
3320 public boolean isPaginationEnabled() {
3321 return paginationEnabled;
3322 }
3323
3324
3325
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
3342
3343 public void setHistoryEnabled(final boolean enabled) {
3344 this.historyEnabled = enabled;
3345 }
3346
3347
3348
3349
3350 public boolean isHistoryEnabled() {
3351 return historyEnabled;
3352 }
3353
3354
3355
3356
3357
3358
3359
3360
3361
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
3367 }
3368 return ok;
3369 }
3370
3371
3372
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
3389
3390
3391 public static final String CR = Configuration.getLineSeparator();
3392
3393
3394
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
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
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
3468
3469 public final void println() throws IOException {
3470 print(CR);
3471
3472 }
3473
3474
3475
3476
3477
3478
3479
3480
3481
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
3496
3497
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
3537 return false;
3538 }
3539 backspace(current.length());
3540 String yanked = killRing.yankPop();
3541 if (yanked == null) {
3542
3543 return false;
3544 }
3545
3546 putString(yanked);
3547 return true;
3548 }
3549
3550
3551
3552
3553 public boolean clearScreen() throws IOException {
3554 if (!terminal.isAnsiSupported()) {
3555 return false;
3556 }
3557
3558
3559 printAnsiSequence("2J");
3560
3561
3562 printAnsiSequence("1;1H");
3563
3564 return true;
3565 }
3566
3567
3568
3569
3570 public void beep() throws IOException {
3571 if (bellEnabled) {
3572 print(KEYBOARD_BELL);
3573
3574 flush();
3575 }
3576 }
3577
3578
3579
3580
3581
3582
3583 public boolean paste() throws IOException {
3584 Clipboard clipboard;
3585 try {
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
3606
3607
3608
3609
3610 if (content == null) {
3611 try {
3612 content = new DataFlavor().getReaderForText(transferable);
3613 }
3614 catch (Exception e) {
3615
3616 }
3617 }
3618
3619 if (content == null) {
3620 return false;
3621 }
3622
3623 String value;
3624
3625 if (content instanceof Reader) {
3626
3627
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
3661
3662
3663 private final Map<Character, ActionListener> triggeredActions = new HashMap<Character, ActionListener>();
3664
3665
3666
3667
3668
3669
3670
3671 public void addTriggeredAction(final char c, final ActionListener listener) {
3672 triggeredActions.put(c, listener);
3673 }
3674
3675
3676
3677
3678
3679
3680
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;
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
3713 print(resources.getString("DISPLAY_MORE"));
3714 flush();
3715 int c = readCharacter();
3716 if (c == '\r' || c == '\n') {
3717
3718 showLines = 1;
3719 }
3720 else if (c != 'q') {
3721
3722 showLines = height - 1;
3723 }
3724
3725 back(resources.getString("DISPLAY_MORE").length());
3726 if (c == 'q') {
3727
3728 break;
3729 }
3730 }
3731 }
3732
3733
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
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
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804 public void resetPromptLine(String prompt, String buffer, int cursorDest) throws IOException {
3805
3806 moveToEnd();
3807
3808
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
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
3846 String prompt = lastLine(originalPrompt);
3847 String buffer = buf.buffer.toString();
3848 resetPromptLine(prompt, buffer, cursorDest);
3849 }
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861 public int searchBackwards(String searchTerm, int startIndex) {
3862 return searchBackwards(searchTerm, startIndex, false);
3863 }
3864
3865
3866
3867
3868
3869
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
3895
3896
3897
3898
3899
3900 public int searchForwards(String searchTerm, int startIndex) {
3901 return searchForwards(searchTerm, startIndex, false);
3902 }
3903
3904
3905
3906
3907
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
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950 private boolean isDelimiter(final char c) {
3951 return !Character.isLetterOrDigit(c);
3952 }
3953
3954
3955
3956
3957
3958
3959
3960
3961
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();
3972 }
3973
3974 }