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_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}