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 */
019package org.crsh.console;
020
021import org.crsh.util.Utils;
022
023import java.io.IOException;
024import 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 */
033public 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}