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}