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 package org.crsh.console;
020
021 import org.crsh.util.Utils;
022
023 import java.io.IOException;
024 import java.util.logging.Logger;
025
026 /**
027 * <p>The current mode of the editor state machine. It decodes a command line operation according
028 * to the current status and its possible state and provide an editor action that will modify the
029 * state of the editor.</p>
030 *
031 * @author Julien Viet
032 */
033 public abstract class Mode extends EditorAction {
034
035 /** The logger. */
036 private static final Logger log = Logger.getLogger(Mode.class.getName());
037
038 public abstract String getKeyMap();
039
040 public abstract String toString();
041
042 @Override
043 void perform(Editor editor, EditorBuffer buffer) throws IOException {
044 editor.console.setMode(this);
045 }
046
047 /**
048 * Transform a key stroke into a editor action. If no action must be taken, null should be returned.
049 *
050 * @param keyStroke the key stroke
051 * @return the editor action
052 */
053 public EditorAction on(KeyStroke keyStroke) {
054 String message = "Operation " + keyStroke.operation + " not mapped in " + getClass().getSimpleName() + " mode " + this;
055 log.warning(message);
056 return null;
057 }
058
059 public static final Mode EMACS = new Mode() {
060
061 @Override
062 public final String getKeyMap() {
063 return "emacs";
064 }
065
066 @Override
067 public EditorAction on(KeyStroke keyStroke) {
068 switch (keyStroke.operation) {
069 case SELF_INSERT:
070 return new InsertKey(keyStroke.sequence);
071 case VI_EDITING_MODE:
072 return VI_INSERT;
073 case BACKWARD_DELETE_CHAR:
074 return EditorAction.DELETE_PREV_CHAR;
075 case BACKWARD_CHAR:
076 return EditorAction.LEFT;
077 case FORWARD_CHAR:
078 return EditorAction.RIGHT;
079 case DELETE_CHAR:
080 return EditorAction.DELETE_NEXT_CHAR;
081 case BACKWARD_WORD:
082 return EditorAction.MOVE_PREV_WORD_AT_BEGINNING;
083 case FORWARD_WORD:
084 return EditorAction.MOVE_NEXT_WORD_AFTER_END;
085 case BEGINNING_OF_LINE:
086 return EditorAction.MOVE_BEGINNING;
087 case EXIT_OR_DELETE_CHAR:
088 return EditorAction.EOF_MAYBE;
089 case END_OF_LINE:
090 return EditorAction.MOVE_END;
091 case COMPLETE:
092 return EditorAction.COMPLETE;
093 case ACCEPT_LINE:
094 return EditorAction.ENTER;
095 case KILL_LINE:
096 return EditorAction.DELETE_END;
097 case BACKWARD_KILL_LINE:
098 return EditorAction.DELETE_BEGINNING;
099 case PREVIOUS_HISTORY:
100 return EditorAction.HISTORY_PREV;
101 case NEXT_HISTORY:
102 return EditorAction.HISTORY_NEXT;
103 case TRANSPOSE_CHARS:
104 return EditorAction.TRANSPOSE_CHARS;
105 case UNIX_LINE_DISCARD:
106 return EditorAction.UNIX_LINE_DISCARD;
107 case UNIX_WORD_RUBOUT:
108 return EditorAction.DELETE_PREV_WORD;
109 case BACKWARD_KILL_WORD:
110 return EditorAction.DELETE_PREV_WORD;
111 case INSERT_COMMENT:
112 return EditorAction.INSERT_COMMENT;
113 case BEGINNING_OF_HISTORY:
114 return EditorAction.HISTORY_FIRST;
115 case END_OF_HISTORY:
116 return EditorAction.HISTORY_LAST;
117 case INTERRUPT:
118 return EditorAction.INTERRUPT;
119 case CLEAR_SCREEN:
120 return EditorAction.CLS;
121 case YANK:
122 return EditorAction.PASTE_AFTER;
123 case KILL_WORD:
124 return EditorAction.DELETE_NEXT_WORD;
125 case DO_LOWERCASE_VERSION:
126 case ABORT:
127 case EXCHANGE_POINT_AND_MARK:
128 case QUOTED_INSERT:
129 case REVERSE_SEARCH_HISTORY:
130 case FORWARD_SEARCH_HISTORY:
131 case CHARACTER_SEARCH:
132 case UNDO:
133 case RE_READ_INIT_FILE:
134 case START_KBD_MACRO:
135 case END_KBD_MACRO:
136 case CALL_LAST_KBD_MACRO:
137 case TAB_INSERT:
138 case REVERT_LINE:
139 case YANK_NTH_ARG:
140 case CHARACTER_SEARCH_BACKWARD:
141 case SET_MARK:
142 case TILDE_EXPAND:
143 case INSERT_COMPLETIONS:
144 case DIGIT_ARGUMENT:
145 case YANK_LAST_ARG:
146 case POSSIBLE_COMPLETIONS:
147 case DELETE_HORIZONTAL_SPACE:
148 case CAPITALIZE_WORD:
149 case DOWNCASE_WORD:
150 case NON_INCREMENTAL_REVERSE_SEARCH_HISTORY:
151 case TRANSPOSE_WORDS:
152 case UPCASE_WORD:
153 case YANK_POP:
154 // Not yet implemented
155 default:
156 return super.on(keyStroke);
157 }
158 }
159
160 @Override
161 public String toString() {
162 return "Mode.EMACS";
163 }
164 };
165
166 public static final Mode VI_INSERT = new Mode() {
167
168 @Override
169 public final String getKeyMap() {
170 return "vi-insert";
171 }
172
173 @Override
174 public EditorAction on(KeyStroke keyStroke) {
175 switch (keyStroke.operation) {
176 case VI_MOVEMENT_MODE:
177 return VI_MOVE.then(EditorAction.LEFT);
178 case FORWARD_CHAR:
179 return EditorAction.RIGHT;
180 case BACKWARD_CHAR:
181 return EditorAction.LEFT;
182 case VI_NEXT_WORD:
183 return EditorAction.MOVE_NEXT_WORD_AT_BEGINNING;
184 case VI_EOF_MAYBE:
185 return EditorAction.EOF_MAYBE;
186 case SELF_INSERT:
187 return new InsertKey(keyStroke.sequence);
188 case BACKWARD_DELETE_CHAR:
189 return EditorAction.DELETE_PREV_CHAR;
190 case COMPLETE:
191 return EditorAction.COMPLETE;
192 case ACCEPT_LINE:
193 return EditorAction.ENTER;
194 case TRANSPOSE_CHARS:
195 return EditorAction.TRANSPOSE_CHARS;
196 case UNIX_LINE_DISCARD:
197 return EditorAction.UNIX_LINE_DISCARD;
198 case UNIX_WORD_RUBOUT:
199 return EditorAction.DELETE_PREV_WORD;
200 case INTERRUPT:
201 return EditorAction.INTERRUPT;
202 case PREVIOUS_HISTORY:
203 return EditorAction.HISTORY_PREV;
204 case NEXT_HISTORY:
205 return EditorAction.HISTORY_NEXT;
206 case BEGINNING_OF_HISTORY:
207 return EditorAction.HISTORY_FIRST;
208 case END_OF_HISTORY:
209 return EditorAction.HISTORY_LAST;
210 case YANK:
211 case MENU_COMPLETE:
212 case MENU_COMPLETE_BACKWARD:
213 case REVERSE_SEARCH_HISTORY:
214 case FORWARD_SEARCH_HISTORY:
215 case QUOTED_INSERT:
216 case UNDO:
217 // Not yet implemented
218 default:
219 return super.on(keyStroke);
220 }
221 }
222
223 @Override
224 public String toString() {
225 return "Mode.VI_INSERT";
226 }
227 };
228
229 public static final Mode VI_MOVE = new Mode() {
230
231 @Override
232 public final String getKeyMap() {
233 return "vi-move";
234 }
235
236 @Override
237 public EditorAction on(KeyStroke keyStroke) {
238 int[] buffer = keyStroke.sequence;
239 switch (keyStroke.operation) {
240 case VI_MOVE_ACCEPT_LINE:
241 return EditorAction.ENTER;
242 case VI_INSERTION_MODE:
243 return VI_INSERT;
244 case VI_INSERT_BEG:
245 return EditorAction.MOVE_BEGINNING.then(VI_INSERT);
246 case VI_INSERT_COMMENT:
247 return EditorAction.INSERT_COMMENT;
248 case BACKWARD_DELETE_CHAR:
249 return EditorAction.DELETE_PREV_CHAR;
250 case VI_DELETE:
251 return EditorAction.DELETE_NEXT_CHAR;
252 case KILL_LINE:
253 return EditorAction.DELETE_END;
254 case BACKWARD_KILL_LINE:
255 return EditorAction.DELETE_BEGINNING;
256 case VI_DELETE_TO:
257 if (buffer.length > 0 && buffer[0] == 'D') {
258 // Workaround since it is only implemented in jline 2.12 with Operation.VI_DELETE_TO_EOL
259 // see https://github.com/jline/jline2/commit/f60432ffbd8322f53abb2d284e1f92f94acf0cc8
260 return EditorAction.DELETE_END;
261 } else {
262 return DELETE_TO;
263 }
264 case VI_NEXT_WORD:
265 return EditorAction.MOVE_NEXT_WORD_AT_BEGINNING;
266 case BACKWARD_WORD:
267 return EditorAction.MOVE_PREV_WORD_AT_BEGINNING;
268 case VI_CHANGE_TO:
269 if (buffer.length > 0 && buffer[0] == 'C') {
270 // Workaround since it is only implemented in jline 2.12 with Operation.VI_CHANGE_TO_EOL
271 // see https://github.com/jline/jline2/commit/f60432ffbd8322f53abb2d284e1f92f94acf0cc8
272 return EMACS.then(EditorAction.DELETE_END).then(VI_INSERT);
273 } else {
274 return CHANGE_TO;
275 }
276 case VI_YANK_TO:
277 return YANK_TO;
278 case VI_ARG_DIGIT:
279 Digit digit = new Digit();
280 digit.count = buffer[0] - '0';
281 return digit;
282 case VI_APPEND_MODE:
283 // That's a trick to let the cursor go to the end of the line
284 // then we set to VI_INSERT
285 return EMACS.then(EditorAction.RIGHT).then(VI_INSERT);
286 case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
287 return EditorAction.MOVE_BEGINNING;
288 case FORWARD_CHAR:
289 return EditorAction.RIGHT;
290 case TRANSPOSE_CHARS:
291 return EditorAction.TRANSPOSE_CHARS;
292 case UNIX_LINE_DISCARD:
293 return EditorAction.UNIX_LINE_DISCARD;
294 case UNIX_WORD_RUBOUT:
295 return EditorAction.DELETE_PREV_WORD;
296 case END_OF_LINE:
297 return EditorAction.MOVE_END;
298 case VI_PREV_WORD:
299 return EditorAction.MOVE_PREV_WORD_AT_BEGINNING;
300 case BACKWARD_CHAR:
301 return EditorAction.LEFT;
302 case VI_END_WORD:
303 return EditorAction.MOVE_NEXT_WORD_BEFORE_END;
304 case VI_CHANGE_CASE:
305 return EditorAction.CHANGE_CASE;
306 case VI_SUBST:
307 if (buffer.length > 0 && buffer[0] == 'S') {
308 // Workaround since it is only implemented in jline 2.12 with Operation.KILL_WHOLE_LINE
309 // see https://github.com/jline/jline2/commit/f60432ffbd8322f53abb2d284e1f92f94acf0cc8
310 return EditorAction.DELETE_LINE.then(VI_INSERT);
311 } else {
312 return super.on(keyStroke);
313 }
314 case VI_PUT:
315 return EditorAction.PASTE_AFTER;
316 case VI_CHANGE_CHAR:
317 return new ChangeChar(1);
318 case INTERRUPT:
319 return EditorAction.INTERRUPT;
320 case VI_SEARCH:
321 // Unmapped
322 return null;
323 case PREVIOUS_HISTORY:
324 return EditorAction.HISTORY_PREV;
325 case NEXT_HISTORY:
326 return EditorAction.HISTORY_NEXT;
327 case BEGINNING_OF_HISTORY:
328 return EditorAction.HISTORY_FIRST;
329 case END_OF_HISTORY:
330 return EditorAction.HISTORY_LAST;
331 case CLEAR_SCREEN:
332 return EditorAction.CLS;
333 default:
334 return super.on(keyStroke);
335 }
336 }
337
338 @Override
339 public String toString() {
340 return "Mode.VI_MOVE";
341 }
342 };
343
344 public static final Mode DELETE_TO = new Mode() {
345
346 @Override
347 public String getKeyMap() {
348 return "vi-move";
349 }
350
351 @Override
352 public EditorAction on(KeyStroke keyStroke) {
353 switch (keyStroke.operation) {
354 case BACKWARD_CHAR:
355 return EditorAction.DELETE_PREV_CHAR.then(VI_MOVE);
356 case FORWARD_CHAR:
357 return EditorAction.DELETE_NEXT_CHAR.then(VI_MOVE);
358 case END_OF_LINE:
359 return EditorAction.DELETE_END.then(VI_MOVE);
360 case VI_NEXT_WORD:
361 return EditorAction.DELETE_UNTIL_NEXT_WORD.then(VI_MOVE);
362 case VI_DELETE_TO:
363 return EditorAction.DELETE_LINE.then(VI_MOVE);
364 case INTERRUPT:
365 return EditorAction.INTERRUPT.then(VI_MOVE);
366 default:
367 return VI_MOVE;
368 }
369 }
370
371 @Override
372 public String toString() {
373 return "Mode.DELETE_TO";
374 }
375 };
376
377 public static final Mode CHANGE_TO = new Mode() {
378
379 @Override
380 public String getKeyMap() {
381 return "vi-move";
382 }
383
384 @Override
385 public EditorAction on(KeyStroke keyStroke) {
386 switch (keyStroke.operation) {
387 case BACKWARD_CHAR:
388 return EditorAction.DELETE_PREV_CHAR.then(VI_INSERT);
389 case END_OF_LINE:
390 return EMACS.then(EditorAction.DELETE_END).then(VI_INSERT);
391 case VI_NEXT_WORD:
392 return EditorAction.DELETE_NEXT_WORD.then(VI_INSERT);
393 case VI_CHANGE_TO:
394 return EditorAction.DELETE_LINE.then(VI_INSERT);
395 case INTERRUPT:
396 return EditorAction.INTERRUPT.then(VI_MOVE);
397 default:
398 return VI_MOVE;
399 }
400 }
401
402 @Override
403 public String toString() {
404 return "Mode.CHANGE_TO";
405 }
406 };
407
408 public static final Mode YANK_TO = new Mode() {
409
410 @Override
411 public String getKeyMap() {
412 return "vi-move";
413 }
414
415
416 @Override
417 public EditorAction on(KeyStroke keyStroke) {
418 switch (keyStroke.operation) {
419 case VI_YANK_TO:
420 return EditorAction.COPY.then(VI_MOVE);
421 case END_OF_LINE:
422 return COPY_END_OF_LINE.then(VI_MOVE);
423 case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
424 return COPY_BEGINNING_OF_LINE.then(VI_MOVE);
425 case VI_NEXT_WORD:
426 return EditorAction.COPY_NEXT_WORD.then(VI_MOVE);
427 case VI_FIRST_PRINT:
428 return EditorAction.COPY_PREV_WORD.then(VI_MOVE);
429 case INTERRUPT:
430 return EditorAction.INTERRUPT.then(VI_MOVE);
431 default:
432 return super.on(keyStroke);
433 }
434 }
435
436 @Override
437 public String toString() {
438 return "Mode.YANK_TO";
439 }
440 };
441
442 public static class ChangeChar extends Mode {
443
444 @Override
445 public String getKeyMap() {
446 return "vi-insert"; // We use insert for ESC
447 }
448
449 /** / */
450 final int count;
451
452 public ChangeChar(int count) {
453 this.count = count;
454 }
455
456 @Override
457 public EditorAction on(KeyStroke keyStroke) {
458 switch (keyStroke.operation) {
459 case VI_MOVEMENT_MODE: // ESC
460 return VI_MOVE;
461 case INTERRUPT:
462 return EditorAction.INTERRUPT.then(VI_MOVE);
463 default:
464 return new EditorAction.ChangeChars(count, keyStroke.sequence[0]).then(VI_MOVE);
465 }
466 }
467
468 @Override
469 public boolean equals(Object obj) {
470 if (obj == this) {
471 return true;
472 } else if (obj instanceof ChangeChar) {
473 ChangeChar that = (ChangeChar)obj;
474 return count == that.count;
475 } else {
476 return false;
477 }
478 }
479
480 @Override
481 public String toString() {
482 return "Mode.ChangeChat[count=" + count + "]";
483 }
484 }
485
486 public static class Digit extends Mode {
487
488 /** . */
489 int count = 0;
490
491 /** . */
492 Character to = null; // null | d:delete-to
493
494 public Digit(int count) {
495 this.count = count;
496 }
497
498 public Digit() {
499 this(0);
500 }
501
502 public int getCount() {
503 return count;
504 }
505
506 public Character getTo() {
507 return to;
508 }
509
510 @Override
511 public String getKeyMap() {
512 return "vi-move";
513 }
514
515 @Override
516 public boolean equals(Object obj) {
517 if (obj == this) {
518 return true;
519 } else if (obj instanceof Digit) {
520 Digit that = (Digit)obj;
521 return count == that.count && Utils.equals(to, that.to);
522 } else {
523 return false;
524 }
525 }
526
527 @Override
528 public String toString() {
529 return "Mode.Digit[count=" + count + ",to=" + to + "]";
530 }
531
532 @Override
533 public EditorAction on(KeyStroke keyStroke) {
534 switch (keyStroke.operation) {
535 case VI_ARG_DIGIT:
536 count = count * 10 + keyStroke.sequence[0] - '0';
537 return null;
538 case BACKWARD_CHAR:
539 if (to == null) {
540 return EditorAction.LEFT.repeat(count).then(VI_MOVE);
541 } else if (to == 'd') {
542 return EditorAction.DELETE_PREV_CHAR.repeat(count).then(VI_MOVE);
543 } else if (to == 'c') {
544 return EditorAction.DELETE_PREV_CHAR.repeat(count).then(VI_INSERT);
545 } else if (to == 'y') {
546 // Not implemented
547 return VI_MOVE;
548 } else {
549 throw new AssertionError();
550 }
551 case FORWARD_CHAR:
552 if (to == null) {
553 return EditorAction.RIGHT.repeat(count).then(VI_MOVE);
554 } else if (to == 'd') {
555 return EditorAction.DELETE_NEXT_CHAR.repeat(count).then(VI_MOVE);
556 } else if (to == 'c') {
557 return EditorAction.DELETE_NEXT_CHAR.repeat(count).then(VI_INSERT);
558 } else if (to == 'y') {
559 throw new UnsupportedOperationException("Not yet handled");
560 } else {
561 return super.on(keyStroke);
562 }
563 case VI_NEXT_WORD:
564 if (to == null) {
565 return EditorAction.MOVE_NEXT_WORD_AT_BEGINNING.repeat(count).then(VI_MOVE);
566 } else if (to == 'd') {
567 return EditorAction.DELETE_UNTIL_NEXT_WORD.repeat(count).then(VI_MOVE);
568 } else if (to == 'c') {
569 return EditorAction.DELETE_NEXT_WORD.repeat(count).then(VI_INSERT);
570 } else {
571 return super.on(keyStroke);
572 }
573 case VI_PREV_WORD:
574 if (to == null) {
575 return EditorAction.MOVE_PREV_WORD_AT_END.repeat(count).then(VI_MOVE);
576 } else {
577 super.on(keyStroke);
578 }
579 case VI_END_WORD:
580 if (to == null) {
581 return EditorAction.MOVE_NEXT_WORD_BEFORE_END.repeat(count).then(VI_MOVE);
582 } else {
583 super.on(keyStroke);
584 }
585 case BACKWARD_DELETE_CHAR:
586 if (to == null) {
587 return EditorAction.DELETE_PREV_CHAR.repeat(count).then(VI_MOVE);
588 } else {
589 return super.on(keyStroke);
590 }
591 case VI_CHANGE_CASE:
592 if (to == null) {
593 return EditorAction.CHANGE_CASE.repeat(count).then(VI_MOVE);
594 } else {
595 return super.on(keyStroke);
596 }
597 case VI_DELETE:
598 if (to == null) {
599 return new EditorAction.DeleteNextChars(count).then(VI_MOVE);
600 } else {
601 return super.on(keyStroke);
602 }
603 case VI_DELETE_TO:
604 if (to != null) {
605 throw new UnsupportedOperationException("Not yet handled");
606 }
607 to = 'd';
608 return null;
609 case VI_CHANGE_TO:
610 if (to != null) {
611 throw new UnsupportedOperationException("Not yet handled");
612 }
613 to = 'c';
614 return null;
615 case VI_YANK_TO:
616 return YANK_TO;
617 case VI_CHANGE_CHAR:
618 if (to != null) {
619 throw new UnsupportedOperationException("Not yet handled");
620 }
621 return new ChangeChar(count);
622 case INTERRUPT:
623 return EditorAction.INTERRUPT.then(VI_MOVE);
624 default:
625 return VI_MOVE;
626 }
627 }
628 }
629 }