001 /*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020 package org.crsh.console;
021
022 import org.crsh.cli.impl.Delimiter;
023 import org.crsh.cli.impl.completion.CompletionMatch;
024 import org.crsh.cli.impl.line.LineParser;
025 import org.crsh.cli.impl.line.MultiLineVisitor;
026 import org.crsh.cli.spi.Completion;
027 import org.crsh.util.Utils;
028
029 import java.io.IOException;
030 import java.util.Iterator;
031 import java.util.List;
032 import java.util.Map;
033
034 /**
035 * An action on the editor.
036 */
037 class EditorAction {
038
039 static class InsertKey extends EditorAction {
040
041 private final int[] sequence;
042
043 public InsertKey(int[] sequence) {
044 this.sequence = sequence;
045 }
046
047 void perform(Editor editor, EditorBuffer buffer) throws IOException {
048 for (int c : sequence) {
049 buffer.append((char)c);
050 }
051 }
052 }
053
054 static EditorAction COMPLETE = new EditorAction() {
055 @Override
056 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
057
058 // Compute prefix
059 MultiLineVisitor visitor = new MultiLineVisitor();
060 LineParser parser = new LineParser(visitor);
061 List<String> lines = buffer.getLines();
062 for (int i = 0;i < lines.size();i++) {
063 if (i > 0) {
064 parser.crlf();
065 }
066 parser.append(lines.get(i));
067 }
068 String prefix = visitor.getRaw();
069
070 // log.log(Level.FINE, "About to get completions for " + prefix);
071 CompletionMatch completion = editor.console.shell.complete(prefix);
072 // log.log(Level.FINE, "Completions for " + prefix + " are " + completions);
073
074 //
075 if (completion != null) {
076 Completion completions = completion.getValue();
077
078 //
079 Delimiter delimiter = completion.getDelimiter();
080
081 try {
082 // Try to find the greatest prefix among all the results
083 if (completions.getSize() == 0) {
084 // Do nothing
085 } else if (completions.getSize() == 1) {
086 Map.Entry<String, Boolean> entry = completions.iterator().next();
087 String insert = entry.getKey();
088 StringBuilder sb = new StringBuilder();
089 sb.append(delimiter.escape(insert));
090 if (entry.getValue()) {
091 sb.append(completion.getDelimiter().getValue());
092 }
093 buffer.append(sb);
094 editor.console.driver.flush();
095 } else {
096 String commonCompletion = Utils.findLongestCommonPrefix(completions.getValues());
097
098 // Format stuff
099 int width = editor.console.driver.getWidth();
100
101 //
102 String completionPrefix = completions.getPrefix();
103
104 // Get the max length
105 int max = 0;
106 for (String suffix : completions.getValues()) {
107 max = Math.max(max, completionPrefix.length() + suffix.length());
108 }
109
110 // Separator : use two whitespace like in BASH
111 max += 2;
112
113 //
114 StringBuilder sb = new StringBuilder().append('\n');
115 if (max < width) {
116 int columns = width / max;
117 int index = 0;
118 for (String suffix : completions.getValues()) {
119 sb.append(completionPrefix).append(suffix);
120 for (int l = completionPrefix.length() + suffix.length();l < max;l++) {
121 sb.append(' ');
122 }
123 if (++index >= columns) {
124 index = 0;
125 sb.append('\n');
126 }
127 }
128 if (index > 0) {
129 sb.append('\n');
130 }
131 } else {
132 for (Iterator<String> i = completions.getValues().iterator();i.hasNext();) {
133 String suffix = i.next();
134 sb.append(commonCompletion).append(suffix);
135 if (i.hasNext()) {
136 sb.append('\n');
137 }
138 }
139 sb.append('\n');
140 }
141
142 // Add current buffer
143 int index = 0;
144 for (String line : lines) {
145 if (index == 0) {
146 String prompt = editor.console.shell.getPrompt();
147 sb.append(prompt == null ? "" : prompt);
148 } else {
149 sb.append("\n> ");
150 }
151 sb.append(line);
152 index++;
153 }
154
155 // Redraw everything
156 editor.console.driver.write(sb.toString());
157
158 // If we have common completion we append it now in the buffer
159 if (commonCompletion.length() > 0) {
160 buffer.append(delimiter.escape(commonCompletion));
161 }
162
163 // Flush
164 buffer.flush(true);
165 }
166 }
167 catch (IOException e) {
168 // log.log(Level.SEVERE, "Could not write completion", e);
169 }
170 }
171
172 //
173 return null;
174 }
175 };
176
177 static EditorAction INTERRUPT = new EditorAction() {
178 @Override
179 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
180 editor.lineParser.reset();
181 buffer.reset();
182 editor.console.driver.writeCRLF();
183 String prompt = editor.console.shell.getPrompt();
184 if (prompt != null) {
185 editor.console.driver.write(prompt);
186 }
187 if (flush) {
188 editor.console.driver.flush();
189 }
190 return null;
191 }
192 };
193
194 static EditorAction EOF_MAYBE = new EditorAction() {
195 @Override
196 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
197 if (editor.isEmpty()) {
198 editor.console.running = false;
199 return null;
200 } else {
201 if (editor.console.getMode() == Mode.EMACS) {
202 return EditorAction.DELETE_PREV_CHAR.execute(editor, buffer, sequence, true);
203 } else {
204 return EditorAction.ENTER.execute(editor, buffer, sequence, true);
205 }
206 }
207 }
208 };
209
210 public abstract static class History extends EditorAction {
211
212 protected abstract int getNext(Editor editor);
213
214 @Override
215 void perform(Editor editor, EditorBuffer buffer) throws IOException {
216 int nextHistoryCursor = getNext(editor);
217 if (nextHistoryCursor >= -1 && nextHistoryCursor < editor.history.size()) {
218 String s = nextHistoryCursor == -1 ? editor.historyBuffer : editor.history.get(nextHistoryCursor);
219 while (buffer.moveRight()) {
220 // Do nothing
221 }
222 String t = buffer.replace(s);
223 if (editor.historyCursor == -1) {
224 editor.historyBuffer = t;
225 } else {
226 editor.history.set(editor.historyCursor, t);
227 }
228 editor.historyCursor = nextHistoryCursor;
229 }
230 }
231 }
232
233 static EditorAction HISTORY_FIRST = new History() {
234 @Override
235 protected int getNext(Editor editor) {
236 return editor.history.size() - 1;
237 }
238 };
239
240 static EditorAction HISTORY_LAST = new History() {
241 @Override
242 protected int getNext(Editor editor) {
243 return 0;
244 }
245 };
246
247 static EditorAction HISTORY_PREV = new History() {
248 @Override
249 protected int getNext(Editor editor) {
250 return editor.historyCursor + 1;
251 }
252 };
253
254 static EditorAction HISTORY_NEXT = new History() {
255 @Override
256 protected int getNext(Editor editor) {
257 return editor.historyCursor - 1;
258 }
259 };
260
261 static EditorAction LEFT = new EditorAction() {
262 @Override
263 void perform(Editor editor, EditorBuffer buffer) throws IOException {
264 buffer.moveLeft();
265 }
266 };
267
268 static EditorAction RIGHT = new EditorAction() {
269 @Override
270 void perform(Editor editor, EditorBuffer buffer) throws IOException {
271 if (buffer.getCursor() < editor.getCursorBound()) {
272 buffer.moveRight();
273 }
274 }
275 };
276
277 static EditorAction MOVE_BEGINNING = new EditorAction() {
278 @Override
279 void perform(Editor editor, EditorBuffer buffer) throws IOException {
280 int cursor = buffer.getCursor();
281 if (cursor > 0) {
282 buffer.moveLeftBy(cursor);
283 }
284 }
285 };
286
287 static class MovePrevWord extends EditorAction {
288
289 final boolean atBeginning /* otherwise at end */;
290
291 public MovePrevWord(boolean atBeginning) {
292 this.atBeginning = atBeginning;
293 }
294
295 @Override
296 void perform(Editor editor, EditorBuffer buffer) throws IOException {
297 int cursor = buffer.getCursor();
298 int pos = cursor;
299 while (pos > 0) {
300 char c = buffer.charAt(pos - 1);
301 if ((atBeginning && Character.isLetterOrDigit(c)) || (!atBeginning && !Character.isLetterOrDigit(c))) {
302 break;
303 } else {
304 pos--;
305 }
306 }
307 while (pos > 0) {
308 char c = buffer.charAt(pos - 1);
309 if ((atBeginning && !Character.isLetterOrDigit(c)) || (!atBeginning && Character.isLetterOrDigit(c))) {
310 break;
311 } else {
312 pos--;
313 }
314 }
315 if (pos < cursor) {
316 buffer.moveLeftBy(cursor - pos);
317 }
318 }
319 }
320
321 static EditorAction MOVE_PREV_WORD_AT_BEGINNING = new MovePrevWord(true);
322
323 static EditorAction MOVE_PREV_WORD_AT_END = new MovePrevWord(false);
324
325 static class MoveNextWord extends EditorAction {
326
327 final At at;
328
329 public MoveNextWord(At at) {
330 this.at = at;
331 }
332
333 @Override
334 void perform(Editor editor, EditorBuffer buffer) throws IOException {
335 int to = editor.getCursorBound();
336 int from = buffer.getCursor();
337 int pos = from;
338 while (true) {
339 int look = at == At.BEFORE_END ? pos + 1 : pos;
340 if (look < to) {
341 char c = buffer.charAt(look);
342 if ((at != At.BEGINNING && Character.isLetterOrDigit(c)) || (at == At.BEGINNING && !Character.isLetterOrDigit(c))) {
343 break;
344 } else {
345 pos++;
346 }
347 } else {
348 break;
349 }
350 }
351 while (true) {
352 int look = at == At.BEFORE_END ? pos + 1 : pos;
353 if (look < to) {
354 char c = buffer.charAt(look);
355 if ((at != At.BEGINNING && !Character.isLetterOrDigit(c)) || (at == At.BEGINNING && Character.isLetterOrDigit(c))) {
356 break;
357 } else {
358 pos++;
359 }
360 } else {
361 break;
362 }
363 }
364 if (pos > from) {
365 buffer.moveRightBy(pos - from);
366 }
367 }
368 }
369
370 static EditorAction MOVE_NEXT_WORD_AT_BEGINNING = new MoveNextWord(At.BEGINNING);
371
372 static EditorAction MOVE_NEXT_WORD_AFTER_END = new MoveNextWord(At.AFTER_END);
373
374 static EditorAction MOVE_NEXT_WORD_BEFORE_END = new MoveNextWord(At.BEFORE_END);
375
376 static EditorAction DELETE_PREV_WORD = new EditorAction() {
377 @Override
378 void perform(Editor editor, EditorBuffer buffer) throws IOException {
379 editor.killBuffer.setLength(0);
380 boolean chars = false;
381 while (true) {
382 int cursor = buffer.getCursor();
383 if (cursor > 0) {
384 if (buffer.charAt(cursor - 1) == ' ') {
385 if (!chars) {
386 editor.killBuffer.appendCodePoint(buffer.del());
387 } else {
388 break;
389 }
390 } else {
391 editor.killBuffer.appendCodePoint(buffer.del());
392 chars = true;
393 }
394 } else {
395 break;
396 }
397 }
398 editor.killBuffer.reverse();
399 }
400 };
401
402 static EditorAction DELETE_NEXT_WORD = new EditorAction() {
403 @Override
404 void perform(Editor editor, EditorBuffer buffer) throws IOException {
405 int count = 0;
406 boolean chars = false;
407 while (true) {
408 if (buffer.getCursor() < buffer.getSize()) {
409 char c = buffer.charAt(buffer.getCursor());
410 if (!Character.isLetterOrDigit(c)) {
411 if (!chars) {
412 count++;
413 buffer.moveRight();
414 } else {
415 break;
416 }
417 } else {
418 chars = true;
419 count++;
420 buffer.moveRight();
421 }
422 } else {
423 break;
424 }
425 }
426 editor.killBuffer.setLength(0);
427 while (count-- > 0) {
428 editor.killBuffer.appendCodePoint(buffer.del());
429 }
430 editor.killBuffer.reverse();
431 }
432 };
433
434 static EditorAction DELETE_UNTIL_NEXT_WORD = new EditorAction() {
435 @Override
436 void perform(Editor editor, EditorBuffer buffer) throws IOException {
437 int pos = buffer.getCursor();
438 EditorAction.MOVE_NEXT_WORD_AT_BEGINNING.perform(editor, buffer);
439 while (buffer.getCursor() > pos) {
440 buffer.del();
441 }
442 }
443 };
444
445 static EditorAction DELETE_END = new EditorAction() {
446 @Override
447 void perform(Editor editor, EditorBuffer buffer) throws IOException {
448 int count = 0;
449 while (buffer.moveRight()) {
450 count++;
451 }
452 editor.killBuffer.setLength(0);
453 while (count-- > 0) {
454 editor.killBuffer.appendCodePoint(buffer.del());
455 }
456 editor.killBuffer.reverse();
457 if (buffer.getCursor() > editor.getCursorBound()) {
458 buffer.moveLeft();
459 }
460 }
461 };
462
463 static EditorAction DELETE_BEGINNING = new EditorAction() {
464 @Override
465 void perform(Editor editor, EditorBuffer buffer) throws IOException {
466 editor.killBuffer.setLength(0);
467 while (editor.buffer.getCursor() > 0) {
468 editor.killBuffer.appendCodePoint(buffer.del());
469 }
470 editor.killBuffer.reverse();
471 }
472 };
473
474 static EditorAction UNIX_LINE_DISCARD = new EditorAction() {
475 @Override
476 void perform(Editor editor, EditorBuffer buffer) throws IOException {
477 // Not really efficient
478 if (buffer.getCursor() > 0) {
479 editor.killBuffer.setLength(0);
480 while (buffer.getCursor() > 0) {
481 int c = buffer.del();
482 editor.killBuffer.appendCodePoint(c);
483 }
484 editor.killBuffer.reverse();
485 }
486 }
487 };
488
489 static EditorAction DELETE_LINE = new EditorAction() {
490 @Override
491 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
492 buffer.moveRightBy(buffer.getSize() - buffer.getCursor());
493 buffer.replace("");
494 return null;
495 }
496 };
497
498 static EditorAction PASTE_AFTER = new EditorAction() {
499 @Override
500 void perform(Editor editor, EditorBuffer buffer) throws IOException {
501 if (editor.killBuffer.length() > 0) {
502 for (int i = 0;i < editor.killBuffer.length();i++) {
503 char c = editor.killBuffer.charAt(i);
504 buffer.append(c);
505 }
506 }
507 }
508 };
509
510 static EditorAction MOVE_END = new EditorAction() {
511 @Override
512 void perform(Editor editor, EditorBuffer buffer) throws IOException {
513 int cursor = editor.getCursorBound() - buffer.getCursor();
514 if (cursor > 0) {
515 buffer.moveRightBy(cursor);
516 }
517 }
518 };
519
520 static abstract class Copy extends EditorAction {
521
522 protected abstract int getFrom(EditorBuffer buffer);
523
524 protected abstract int getTo(EditorBuffer buffer);
525
526 @Override
527 void perform(Editor editor, EditorBuffer buffer) throws IOException {
528 int from = getFrom(buffer);
529 int to = getTo(buffer);
530 editor.killBuffer.setLength(0);
531 for (int i = from;i < to;i++) {
532 editor.killBuffer.append(editor.buffer.charAt(i));
533 }
534 }
535 }
536
537 static EditorAction COPY = new Copy() {
538 @Override
539 protected int getFrom(EditorBuffer buffer) {
540 return 0;
541 }
542 @Override
543 protected int getTo(EditorBuffer buffer) {
544 return buffer.getSize();
545 }
546 };
547
548 static EditorAction COPY_END_OF_LINE = new Copy() {
549 @Override
550 protected int getFrom(EditorBuffer buffer) {
551 return buffer.getCursor();
552 }
553 @Override
554 protected int getTo(EditorBuffer buffer) {
555 return buffer.getSize();
556 }
557 };
558
559 static EditorAction COPY_BEGINNING_OF_LINE = new Copy() {
560 @Override
561 protected int getFrom(EditorBuffer buffer) {
562 return 0;
563 }
564 @Override
565 protected int getTo(EditorBuffer buffer) {
566 return buffer.getCursor();
567 }
568 };
569
570 static EditorAction COPY_NEXT_WORD = new EditorAction() {
571 @Override
572 void perform(Editor editor, EditorBuffer buffer) throws IOException {
573 int size = editor.buffer.getSize();
574 int cursor = editor.buffer.getCursor();
575 editor.killBuffer.setLength(0);
576 while (cursor < size && editor.buffer.charAt(cursor) != ' ') {
577 editor.killBuffer.append(editor.buffer.charAt(cursor++));
578 }
579 while (cursor < size && editor.buffer.charAt(cursor) == ' ') {
580 editor.killBuffer.append(editor.buffer.charAt(cursor++));
581 }
582 }
583 };
584
585 static EditorAction COPY_PREV_WORD = new EditorAction() {
586 @Override
587 void perform(Editor editor, EditorBuffer buffer) throws IOException {
588 int cursor = buffer.getCursor() - 1;
589 editor.killBuffer.setLength(0);
590 while (cursor > 0 && buffer.charAt(cursor) != ' ') {
591 editor.killBuffer.append(buffer.charAt(cursor--));
592 }
593 while (cursor > 0 && editor.buffer.charAt(cursor) == ' ') {
594 editor.killBuffer.append(buffer.charAt(cursor--));
595 }
596 editor.killBuffer.reverse();
597 }
598 };
599
600 static class ChangeChars extends EditorAction {
601
602 /** . */
603 public final int count;
604
605 /** . */
606 public final int c;
607
608 public ChangeChars(int count, int c) {
609 this.count = count;
610 this.c = c;
611 }
612
613 @Override
614 void perform(Editor editor, EditorBuffer buffer) throws IOException {
615 int a = Math.min(count, buffer.getSize() - buffer.getCursor());
616 while (a-- > 0) {
617 buffer.moveRight((char)c);
618 }
619 buffer.moveLeft();
620 }
621 }
622
623 static EditorAction DELETE_PREV_CHAR = new EditorAction() {
624 @Override
625 void perform(Editor editor, EditorBuffer buffer) throws IOException {
626 buffer.del();
627 }
628 };
629
630 static class DeleteNextChars extends EditorAction {
631
632 /** . */
633 public final int count;
634
635 public DeleteNextChars(int count) {
636 this.count = count;
637 }
638
639 @Override
640 void perform(Editor editor, EditorBuffer buffer) throws IOException {
641 int tmp = count;
642 while (tmp > 0 && buffer.moveRight()) {
643 tmp--;
644 }
645 while (tmp++ < count) {
646 buffer.del();
647 }
648 if (buffer.getCursor() > editor.getCursorBound()) {
649 buffer.moveLeft();
650 }
651 }
652 }
653
654 static EditorAction DELETE_NEXT_CHAR = ((EditorAction)new DeleteNextChars(1));
655
656 static EditorAction CHANGE_CASE = new EditorAction() {
657 @Override
658 void perform(Editor editor, EditorBuffer buffer) throws IOException {
659 if (buffer.getCursor() < buffer.getSize()) {
660 char c = buffer.charAt(buffer.getCursor());
661 if (Character.isUpperCase(c)) {
662 c = Character.toLowerCase(c);
663 }
664 else if (Character.isLowerCase(c)) {
665 c = Character.toUpperCase(c);
666 }
667 buffer.moveRight(c);
668 if (buffer.getCursor() > editor.getCursorBound()) {
669 buffer.moveLeft();
670 }
671 }
672 }
673 };
674
675 static EditorAction TRANSPOSE_CHARS = new EditorAction() {
676 @Override
677 void perform(Editor editor, EditorBuffer buffer) throws IOException {
678 if (buffer.getSize() > 2) {
679 int pos = buffer.getCursor();
680 if (pos > 0) {
681 if (pos < buffer.getSize()) {
682 if (buffer.moveLeft()) {
683 char a = buffer.charAt(pos - 1);
684 char b = buffer.charAt(pos);
685 buffer.moveRight(b); // Should be assertion
686 buffer.moveRight(a); // Should be assertion
687 // A bit not great : need to find a better way to do that...
688 if (editor.console.getMode() == Mode.VI_MOVE && buffer.getCursor() > editor.getCursorBound()) {
689 buffer.moveLeft();
690 }
691 }
692 } else {
693 if (buffer.moveLeft() && buffer.moveLeft()) {
694 char a = buffer.charAt(pos - 2);
695 char b = buffer.charAt(pos - 1);
696 buffer.moveRight(b); // Should be assertion
697 buffer.moveRight(a); // Should be assertion
698 }
699 }
700 }
701 }
702 }
703 };
704
705 static EditorAction INSERT_COMMENT = new EditorAction() {
706 @Override
707 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
708 EditorAction.MOVE_BEGINNING.perform(editor, buffer);
709 buffer.append("#");
710 return EditorAction.ENTER.execute(editor, buffer, sequence, flush);
711 }
712 };
713
714 static EditorAction CLS = new EditorAction() {
715 @Override
716 void perform(Editor editor, EditorBuffer buffer) throws IOException {
717 editor.console.driver.cls();
718 StringBuilder sb = new StringBuilder();
719 int index = 0;
720 List<String> lines = buffer.getLines();
721 for (String line : lines) {
722 if (index == 0) {
723 String prompt = editor.console.shell.getPrompt();
724 sb.append(prompt == null ? "" : prompt);
725 } else {
726 sb.append("\n> ");
727 }
728 sb.append(line);
729 index++;
730 }
731 editor.console.driver.write(sb.toString());
732 editor.console.driver.flush();
733 }
734 };
735
736 static EditorAction ENTER = new EditorAction() {
737 @Override
738 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
739 editor.historyCursor = -1;
740 editor.historyBuffer = null;
741 String line = buffer.getLine();
742 editor.lineParser.append(line);
743 if (editor.console.getMode() == Mode.VI_MOVE) {
744 editor.console.setMode(Mode.VI_INSERT);
745 }
746 if (editor.lineParser.crlf()) {
747 editor.console.driver.writeCRLF();
748 editor.console.driver.flush();
749 String request = editor.visitor.getRaw();
750 editor.addToHistory(request);
751 return request;
752 } else {
753 buffer.append('\n');
754 editor.console.driver.write("> ");
755 if (flush) {
756 buffer.flush();
757 }
758 return null;
759 }
760 }
761 };
762
763 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
764 perform(editor, buffer);
765 if (flush) {
766 buffer.flush();
767 }
768 return null;
769 }
770
771 void perform(Editor editor, EditorBuffer buffer) throws IOException {
772 throw new UnsupportedOperationException("Implement the edition logic");
773 }
774
775 public EditorAction then(final EditorAction action) {
776 return new EditorAction() {
777 @Override
778 String execute(Editor editor, EditorBuffer buffer, int[] sequence, boolean flush) throws IOException {
779 EditorAction.this.execute(editor, buffer, sequence, flush);
780 return action.execute(editor, buffer, sequence, flush);
781 }
782 };
783 }
784
785 public EditorAction repeat(final int count) {
786 return new EditorAction() {
787 @Override
788 void perform(Editor editor, EditorBuffer buffer) throws IOException {
789 for (int i = 0;i < count;i++) {
790 EditorAction.this.perform(editor, buffer);
791 }
792 }
793 };
794 }
795 }