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 }