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_EOL:
257              return EditorAction.DELETE_END;
258            case VI_DELETE_TO:
259              return DELETE_TO;
260            case VI_NEXT_WORD:
261              return EditorAction.MOVE_NEXT_WORD_AT_BEGINNING;
262            case BACKWARD_WORD:
263              return EditorAction.MOVE_PREV_WORD_AT_BEGINNING;
264            case VI_CHANGE_TO_EOL:
265              return EMACS.then(EditorAction.DELETE_END).then(VI_INSERT);
266            case VI_CHANGE_TO:
267              return CHANGE_TO;
268            case VI_YANK_TO:
269              return YANK_TO;
270            case VI_ARG_DIGIT:
271              Digit digit = new Digit();
272              digit.count = buffer[0] - '0';
273              return digit;
274            case VI_APPEND_MODE:
275              // That's a trick to let the cursor go to the end of the line
276              // then we set to VI_INSERT
277              return EMACS.then(EditorAction.RIGHT).then(VI_INSERT);
278            case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
279              return EditorAction.MOVE_BEGINNING;
280            case FORWARD_CHAR:
281              return EditorAction.RIGHT;
282            case TRANSPOSE_CHARS:
283              return EditorAction.TRANSPOSE_CHARS;
284            case UNIX_LINE_DISCARD:
285              return EditorAction.UNIX_LINE_DISCARD;
286            case UNIX_WORD_RUBOUT:
287              return EditorAction.DELETE_PREV_WORD;
288            case END_OF_LINE:
289              return EditorAction.MOVE_END;
290            case VI_PREV_WORD:
291              return EditorAction.MOVE_PREV_WORD_AT_BEGINNING;
292            case BACKWARD_CHAR:
293              return EditorAction.LEFT;
294            case VI_END_WORD:
295              return EditorAction.MOVE_NEXT_WORD_BEFORE_END;
296            case VI_CHANGE_CASE:
297              return EditorAction.CHANGE_CASE;
298            case VI_KILL_WHOLE_LINE:
299              return EditorAction.DELETE_LINE.then(VI_INSERT);
300            case VI_PUT:
301              return EditorAction.PASTE_AFTER;
302            case VI_CHANGE_CHAR:
303              return new ChangeChar(1);
304            case INTERRUPT:
305              return EditorAction.INTERRUPT;
306            case VI_SEARCH:
307              // Unmapped
308              return null;
309            case PREVIOUS_HISTORY:
310              return EditorAction.HISTORY_PREV;
311            case NEXT_HISTORY:
312              return EditorAction.HISTORY_NEXT;
313            case BEGINNING_OF_HISTORY:
314              return EditorAction.HISTORY_FIRST;
315            case END_OF_HISTORY:
316              return EditorAction.HISTORY_LAST;
317            case CLEAR_SCREEN:
318              return EditorAction.CLS;
319            default:
320              return super.on(keyStroke);
321          }
322        }
323    
324        @Override
325        public String toString() {
326          return "Mode.VI_MOVE";
327        }
328      };
329    
330      public static final Mode DELETE_TO =  new Mode() {
331    
332        @Override
333        public String getKeyMap() {
334          return "vi-move";
335        }
336    
337        @Override
338        public EditorAction on(KeyStroke keyStroke) {
339          switch (keyStroke.operation) {
340            case BACKWARD_CHAR:
341              return EditorAction.DELETE_PREV_CHAR.then(VI_MOVE);
342            case FORWARD_CHAR:
343              return EditorAction.DELETE_NEXT_CHAR.then(VI_MOVE);
344            case END_OF_LINE:
345              return EditorAction.DELETE_END.then(VI_MOVE);
346            case VI_NEXT_WORD:
347              return EditorAction.DELETE_UNTIL_NEXT_WORD.then(VI_MOVE);
348            case VI_DELETE_TO:
349              return EditorAction.DELETE_LINE.then(VI_MOVE);
350            case INTERRUPT:
351              return EditorAction.INTERRUPT.then(VI_MOVE);
352            default:
353              return VI_MOVE;
354          }
355        }
356    
357        @Override
358        public String toString() {
359          return "Mode.DELETE_TO";
360        }
361      };
362    
363      public static final Mode CHANGE_TO = new Mode() {
364    
365        @Override
366        public String getKeyMap() {
367          return "vi-move";
368        }
369    
370        @Override
371        public EditorAction on(KeyStroke keyStroke) {
372          switch (keyStroke.operation) {
373            case BACKWARD_CHAR:
374              return EditorAction.DELETE_PREV_CHAR.then(VI_INSERT);
375            case END_OF_LINE:
376              return EMACS.then(EditorAction.DELETE_END).then(VI_INSERT);
377            case VI_NEXT_WORD:
378              return EditorAction.DELETE_NEXT_WORD.then(VI_INSERT);
379            case VI_CHANGE_TO:
380              return EditorAction.DELETE_LINE.then(VI_INSERT);
381            case INTERRUPT:
382              return EditorAction.INTERRUPT.then(VI_MOVE);
383            default:
384              return VI_MOVE;
385          }
386        }
387    
388        @Override
389        public String toString() {
390          return "Mode.CHANGE_TO";
391        }
392      };
393    
394      public static final Mode YANK_TO = new Mode() {
395    
396        @Override
397        public String getKeyMap() {
398          return "vi-move";
399        }
400    
401    
402        @Override
403        public EditorAction on(KeyStroke keyStroke) {
404          switch (keyStroke.operation) {
405            case VI_YANK_TO:
406              return EditorAction.COPY.then(VI_MOVE);
407            case END_OF_LINE:
408              return COPY_END_OF_LINE.then(VI_MOVE);
409            case VI_BEGNNING_OF_LINE_OR_ARG_DIGIT:
410              return COPY_BEGINNING_OF_LINE.then(VI_MOVE);
411            case VI_NEXT_WORD:
412              return EditorAction.COPY_NEXT_WORD.then(VI_MOVE);
413            case VI_FIRST_PRINT:
414              return EditorAction.COPY_PREV_WORD.then(VI_MOVE);
415            case INTERRUPT:
416              return EditorAction.INTERRUPT.then(VI_MOVE);
417            default:
418              return super.on(keyStroke);
419          }
420        }
421    
422        @Override
423        public String toString() {
424          return "Mode.YANK_TO";
425        }
426      };
427    
428      public static class ChangeChar extends Mode {
429    
430        @Override
431        public String getKeyMap() {
432          return "vi-insert"; // We use insert for ESC
433        }
434    
435        /** / */
436        final int count;
437    
438        public ChangeChar(int count) {
439          this.count = count;
440        }
441    
442        @Override
443        public EditorAction on(KeyStroke keyStroke) {
444          switch (keyStroke.operation) {
445            case VI_MOVEMENT_MODE: // ESC
446              return VI_MOVE;
447            case INTERRUPT:
448              return EditorAction.INTERRUPT.then(VI_MOVE);
449            default:
450              return new EditorAction.ChangeChars(count, keyStroke.sequence[0]).then(VI_MOVE);
451          }
452        }
453    
454        @Override
455        public boolean equals(Object obj) {
456          if (obj == this) {
457            return true;
458          } else if (obj instanceof ChangeChar) {
459            ChangeChar that = (ChangeChar)obj;
460            return count == that.count;
461          } else {
462            return false;
463          }
464        }
465    
466        @Override
467        public String toString() {
468          return "Mode.ChangeChat[count=" + count + "]";
469        }
470      }
471    
472      public static class Digit extends Mode {
473    
474        /** . */
475        int count = 0;
476    
477        /** . */
478        Character to = null; // null | d:delete-to
479    
480        public Digit(int count) {
481          this.count = count;
482        }
483    
484        public Digit() {
485          this(0);
486        }
487    
488        public int getCount() {
489          return count;
490        }
491    
492        public Character getTo() {
493          return to;
494        }
495    
496        @Override
497        public String getKeyMap() {
498          return "vi-move";
499        }
500    
501        @Override
502        public boolean equals(Object obj) {
503          if (obj == this) {
504            return true;
505          } else if (obj instanceof Digit) {
506            Digit that = (Digit)obj;
507            return count == that.count && Utils.equals(to, that.to);
508          } else {
509            return false;
510          }
511        }
512    
513        @Override
514        public String toString() {
515          return "Mode.Digit[count=" + count + ",to=" + to + "]";
516        }
517    
518        @Override
519        public EditorAction on(KeyStroke keyStroke) {
520          switch (keyStroke.operation) {
521            case VI_ARG_DIGIT:
522              count = count * 10 + keyStroke.sequence[0] - '0';
523              return null;
524            case BACKWARD_CHAR:
525              if (to == null) {
526                return EditorAction.LEFT.repeat(count).then(VI_MOVE);
527              } else if (to == 'd') {
528                return EditorAction.DELETE_PREV_CHAR.repeat(count).then(VI_MOVE);
529              } else if (to == 'c') {
530                return EditorAction.DELETE_PREV_CHAR.repeat(count).then(VI_INSERT);
531              } else if (to == 'y') {
532                // Not implemented
533                return VI_MOVE;
534              } else {
535                throw new AssertionError();
536              }
537            case FORWARD_CHAR:
538              if (to == null) {
539                return EditorAction.RIGHT.repeat(count).then(VI_MOVE);
540              } else if (to == 'd') {
541                return EditorAction.DELETE_NEXT_CHAR.repeat(count).then(VI_MOVE);
542              } else if (to == 'c') {
543                return EditorAction.DELETE_NEXT_CHAR.repeat(count).then(VI_INSERT);
544              } else if (to == 'y') {
545                throw new UnsupportedOperationException("Not yet handled");
546              } else {
547                return super.on(keyStroke);
548              }
549            case VI_NEXT_WORD:
550              if (to == null) {
551                return EditorAction.MOVE_NEXT_WORD_AT_BEGINNING.repeat(count).then(VI_MOVE);
552              } else if (to == 'd') {
553                return EditorAction.DELETE_UNTIL_NEXT_WORD.repeat(count).then(VI_MOVE);
554              } else if (to == 'c') {
555                return EditorAction.DELETE_NEXT_WORD.repeat(count).then(VI_INSERT);
556              } else {
557                return super.on(keyStroke);
558              }
559            case VI_PREV_WORD:
560              if (to == null) {
561                return EditorAction.MOVE_PREV_WORD_AT_END.repeat(count).then(VI_MOVE);
562              } else {
563                super.on(keyStroke);
564              }
565            case VI_END_WORD:
566              if (to == null) {
567                return EditorAction.MOVE_NEXT_WORD_BEFORE_END.repeat(count).then(VI_MOVE);
568              } else {
569                super.on(keyStroke);
570              }
571            case BACKWARD_DELETE_CHAR:
572              if (to == null) {
573                return EditorAction.DELETE_PREV_CHAR.repeat(count).then(VI_MOVE);
574              } else {
575                return super.on(keyStroke);
576              }
577            case VI_CHANGE_CASE:
578              if (to == null) {
579                return EditorAction.CHANGE_CASE.repeat(count).then(VI_MOVE);
580              } else {
581                return super.on(keyStroke);
582              }
583            case VI_DELETE:
584              if (to == null) {
585                return new EditorAction.DeleteNextChars(count).then(VI_MOVE);
586              } else {
587                return super.on(keyStroke);
588              }
589            case VI_DELETE_TO:
590              if (to != null) {
591                throw new UnsupportedOperationException("Not yet handled");
592              }
593              to = 'd';
594              return null;
595            case VI_CHANGE_TO:
596              if (to != null) {
597                throw new UnsupportedOperationException("Not yet handled");
598              }
599              to = 'c';
600              return null;
601            case VI_YANK_TO:
602              return YANK_TO;
603            case VI_CHANGE_CHAR:
604              if (to != null) {
605                throw new UnsupportedOperationException("Not yet handled");
606              }
607              return new ChangeChar(count);
608            case INTERRUPT:
609              return EditorAction.INTERRUPT.then(VI_MOVE);
610            default:
611              return VI_MOVE;
612          }
613        }
614      }
615    }