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    }