/*
 * Copyright (c) 2002-2012, the original author or authors.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * http://www.opensource.org/licenses/bsd-license.php
 */
package jdk.internal.jline.console;

import java.util.HashMap;
import java.util.Map;

The KeyMap class contains all bindings from keys to operations.
Author:Guillaume Nodet
Since:2.6
/** * The KeyMap class contains all bindings from keys to operations. * * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a> * @since 2.6 */
public class KeyMap { public static final String VI_MOVE = "vi-move"; public static final String VI_INSERT = "vi-insert"; public static final String EMACS = "emacs"; public static final String EMACS_STANDARD = "emacs-standard"; public static final String EMACS_CTLX = "emacs-ctlx"; public static final String EMACS_META = "emacs-meta"; private static final int KEYMAP_LENGTH = 256; private static final Object NULL_FUNCTION = new Object(); private Object[] mapping = new Object[KEYMAP_LENGTH]; private Object anotherKey = null; private String name; private boolean isViKeyMap; public KeyMap(String name, boolean isViKeyMap) { this(name, new Object[KEYMAP_LENGTH], isViKeyMap); } protected KeyMap(String name, Object[] mapping, boolean isViKeyMap) { this.mapping = mapping; this.name = name; this.isViKeyMap = isViKeyMap; } public boolean isViKeyMap() { return isViKeyMap; } public String getName() { return name; } public Object getAnotherKey() { return anotherKey; } public void from(KeyMap other) { this.mapping = other.mapping; this.anotherKey = other.anotherKey; } public Object getBound( CharSequence keySeq ) { if (keySeq != null && keySeq.length() > 0) { KeyMap map = this; for (int i = 0; i < keySeq.length(); i++) { char c = keySeq.charAt(i); if (c > 255) { return Operation.SELF_INSERT; } if (map.mapping[c] instanceof KeyMap) { if (i == keySeq.length() - 1) { return map.mapping[c]; } else { map = (KeyMap) map.mapping[c]; } } else { return map.mapping[c]; } } } return null; } public void bindIfNotBound( CharSequence keySeq, Object function ) { bind (this, keySeq, function, true); } public void bind( CharSequence keySeq, Object function ) { bind (this, keySeq, function, false); } private static void bind( KeyMap map, CharSequence keySeq, Object function ) { bind (map, keySeq, function, false); } private static void bind( KeyMap map, CharSequence keySeq, Object function, boolean onlyIfNotBound ) { if (keySeq != null && keySeq.length() > 0) { for (int i = 0; i < keySeq.length(); i++) { char c = keySeq.charAt(i); if (c >= map.mapping.length) { return; } if (i < keySeq.length() - 1) { if (!(map.mapping[c] instanceof KeyMap)) { KeyMap m = new KeyMap("anonymous", false); if (map.mapping[c] != Operation.DO_LOWERCASE_VERSION) { m.anotherKey = map.mapping[c]; } map.mapping[c] = m; } map = (KeyMap) map.mapping[c]; } else { if (function == null) { function = NULL_FUNCTION; } if (map.mapping[c] instanceof KeyMap) { map.anotherKey = function; } else { Object op = map.mapping[c]; if (onlyIfNotBound == false || op == null || op == Operation.DO_LOWERCASE_VERSION || op == Operation.VI_MOVEMENT_MODE ) { } map.mapping[c] = function; } } } } } public void setBlinkMatchingParen(boolean on) { if (on) { bind( "}", Operation.INSERT_CLOSE_CURLY ); bind( ")", Operation.INSERT_CLOSE_PAREN ); bind( "]", Operation.INSERT_CLOSE_SQUARE ); } } private static void bindArrowKeys(KeyMap map) { // MS-DOS bind( map, "\033[0A", Operation.PREVIOUS_HISTORY ); bind( map, "\033[0B", Operation.BACKWARD_CHAR ); bind( map, "\033[0C", Operation.FORWARD_CHAR ); bind( map, "\033[0D", Operation.NEXT_HISTORY ); // Windows bind( map, "\340\000", Operation.KILL_WHOLE_LINE ); bind( map, "\340\107", Operation.BEGINNING_OF_LINE ); bind( map, "\340\110", Operation.PREVIOUS_HISTORY ); bind( map, "\340\111", Operation.BEGINNING_OF_HISTORY ); bind( map, "\340\113", Operation.BACKWARD_CHAR ); bind( map, "\340\115", Operation.FORWARD_CHAR ); bind( map, "\340\117", Operation.END_OF_LINE ); bind( map, "\340\120", Operation.NEXT_HISTORY ); bind( map, "\340\121", Operation.END_OF_HISTORY ); bind( map, "\340\122", Operation.OVERWRITE_MODE ); bind( map, "\340\123", Operation.DELETE_CHAR ); bind( map, "\000\107", Operation.BEGINNING_OF_LINE ); bind( map, "\000\110", Operation.PREVIOUS_HISTORY ); bind( map, "\000\111", Operation.BEGINNING_OF_HISTORY ); bind( map, "\000\110", Operation.PREVIOUS_HISTORY ); bind( map, "\000\113", Operation.BACKWARD_CHAR ); bind( map, "\000\115", Operation.FORWARD_CHAR ); bind( map, "\000\117", Operation.END_OF_LINE ); bind( map, "\000\120", Operation.NEXT_HISTORY ); bind( map, "\000\121", Operation.END_OF_HISTORY ); bind( map, "\000\122", Operation.OVERWRITE_MODE ); bind( map, "\000\123", Operation.DELETE_CHAR ); bind( map, "\033[A", Operation.PREVIOUS_HISTORY ); bind( map, "\033[B", Operation.NEXT_HISTORY ); bind( map, "\033[C", Operation.FORWARD_CHAR ); bind( map, "\033[D", Operation.BACKWARD_CHAR ); bind( map, "\033[H", Operation.BEGINNING_OF_LINE ); bind( map, "\033[F", Operation.END_OF_LINE ); bind( map, "\033OA", Operation.PREVIOUS_HISTORY ); bind( map, "\033OB", Operation.NEXT_HISTORY ); bind( map, "\033OC", Operation.FORWARD_CHAR ); bind( map, "\033OD", Operation.BACKWARD_CHAR ); bind( map, "\033OH", Operation.BEGINNING_OF_LINE ); bind( map, "\033OF", Operation.END_OF_LINE ); bind( map, "\033[1~", Operation.BEGINNING_OF_LINE); bind( map, "\033[4~", Operation.END_OF_LINE); bind( map, "\033[3~", Operation.DELETE_CHAR); // MINGW32 bind( map, "\0340H", Operation.PREVIOUS_HISTORY ); bind( map, "\0340P", Operation.NEXT_HISTORY ); bind( map, "\0340M", Operation.FORWARD_CHAR ); bind( map, "\0340K", Operation.BACKWARD_CHAR ); } // public boolean isConvertMetaCharsToAscii() { // return convertMetaCharsToAscii; // } // public void setConvertMetaCharsToAscii(boolean convertMetaCharsToAscii) { // this.convertMetaCharsToAscii = convertMetaCharsToAscii; // } public static boolean isMeta( char c ) { return c > 0x7f && c <= 0xff; } public static char unMeta( char c ) { return (char) (c & 0x7F); } public static char meta( char c ) { return (char) (c | 0x80); } public static Map<String, KeyMap> keyMaps() { Map<String, KeyMap> keyMaps = new HashMap<String, KeyMap>(); KeyMap emacs = emacs(); bindArrowKeys(emacs); keyMaps.put(EMACS, emacs); keyMaps.put(EMACS_STANDARD, emacs); keyMaps.put(EMACS_CTLX, (KeyMap) emacs.getBound("\u0018")); keyMaps.put(EMACS_META, (KeyMap) emacs.getBound("\u001b")); KeyMap viMov = viMovement(); bindArrowKeys(viMov); keyMaps.put(VI_MOVE, viMov); keyMaps.put("vi-command", viMov); KeyMap viIns = viInsertion(); bindArrowKeys(viIns); keyMaps.put(VI_INSERT, viIns); keyMaps.put("vi", viIns); return keyMaps; } public static KeyMap emacs() { Object[] map = new Object[KEYMAP_LENGTH]; Object[] ctrl = new Object[] { // Control keys. Operation.SET_MARK, /* Control-@ */ Operation.BEGINNING_OF_LINE, /* Control-A */ Operation.BACKWARD_CHAR, /* Control-B */ Operation.INTERRUPT, /* Control-C */ Operation.EXIT_OR_DELETE_CHAR, /* Control-D */ Operation.END_OF_LINE, /* Control-E */ Operation.FORWARD_CHAR, /* Control-F */ Operation.ABORT, /* Control-G */ Operation.BACKWARD_DELETE_CHAR, /* Control-H */ Operation.COMPLETE, /* Control-I */ Operation.ACCEPT_LINE, /* Control-J */ Operation.KILL_LINE, /* Control-K */ Operation.CLEAR_SCREEN, /* Control-L */ Operation.ACCEPT_LINE, /* Control-M */ Operation.NEXT_HISTORY, /* Control-N */ null, /* Control-O */ Operation.PREVIOUS_HISTORY, /* Control-P */ Operation.QUOTED_INSERT, /* Control-Q */ Operation.REVERSE_SEARCH_HISTORY, /* Control-R */ Operation.FORWARD_SEARCH_HISTORY, /* Control-S */ Operation.TRANSPOSE_CHARS, /* Control-T */ Operation.UNIX_LINE_DISCARD, /* Control-U */ Operation.QUOTED_INSERT, /* Control-V */ Operation.UNIX_WORD_RUBOUT, /* Control-W */ emacsCtrlX(), /* Control-X */ Operation.YANK, /* Control-Y */ null, /* Control-Z */ emacsMeta(), /* Control-[ */ null, /* Control-\ */ Operation.CHARACTER_SEARCH, /* Control-] */ null, /* Control-^ */ Operation.UNDO, /* Control-_ */ }; System.arraycopy( ctrl, 0, map, 0, ctrl.length ); for (int i = 32; i < 256; i++) { map[i] = Operation.SELF_INSERT; } map[DELETE] = Operation.BACKWARD_DELETE_CHAR; return new KeyMap(EMACS, map, false); } public static final char CTRL_D = (char) 4; public static final char CTRL_G = (char) 7; public static final char CTRL_H = (char) 8; public static final char CTRL_I = (char) 9; public static final char CTRL_J = (char) 10; public static final char CTRL_M = (char) 13; public static final char CTRL_R = (char) 18; public static final char CTRL_S = (char) 19; public static final char CTRL_U = (char) 21; public static final char CTRL_X = (char) 24; public static final char CTRL_Y = (char) 25; public static final char ESCAPE = (char) 27; /* Ctrl-[ */ public static final char CTRL_OB = (char) 27; /* Ctrl-[ */ public static final char CTRL_CB = (char) 29; /* Ctrl-] */ public static final int DELETE = (char) 127; public static KeyMap emacsCtrlX() { Object[] map = new Object[KEYMAP_LENGTH]; map[CTRL_G] = Operation.ABORT; map[CTRL_R] = Operation.RE_READ_INIT_FILE; map[CTRL_U] = Operation.UNDO; map[CTRL_X] = Operation.EXCHANGE_POINT_AND_MARK; map['('] = Operation.START_KBD_MACRO; map[')'] = Operation.END_KBD_MACRO; for (int i = 'A'; i <= 'Z'; i++) { map[i] = Operation.DO_LOWERCASE_VERSION; } map['e'] = Operation.CALL_LAST_KBD_MACRO; map[DELETE] = Operation.KILL_LINE; return new KeyMap(EMACS_CTLX, map, false); } public static KeyMap emacsMeta() { Object[] map = new Object[KEYMAP_LENGTH]; map[CTRL_G] = Operation.ABORT; map[CTRL_H] = Operation.BACKWARD_KILL_WORD; map[CTRL_I] = Operation.TAB_INSERT; map[CTRL_J] = Operation.VI_EDITING_MODE; map[CTRL_M] = Operation.VI_EDITING_MODE; map[CTRL_R] = Operation.REVERT_LINE; map[CTRL_Y] = Operation.YANK_NTH_ARG; map[CTRL_OB] = Operation.COMPLETE; map[CTRL_CB] = Operation.CHARACTER_SEARCH_BACKWARD; map[' '] = Operation.SET_MARK; map['#'] = Operation.INSERT_COMMENT; map['&'] = Operation.TILDE_EXPAND; map['*'] = Operation.INSERT_COMPLETIONS; map['-'] = Operation.DIGIT_ARGUMENT; map['.'] = Operation.YANK_LAST_ARG; map['<'] = Operation.BEGINNING_OF_HISTORY; map['='] = Operation.POSSIBLE_COMPLETIONS; map['>'] = Operation.END_OF_HISTORY; map['?'] = Operation.POSSIBLE_COMPLETIONS; for (int i = 'A'; i <= 'Z'; i++) { map[i] = Operation.DO_LOWERCASE_VERSION; } map['\\'] = Operation.DELETE_HORIZONTAL_SPACE; map['_'] = Operation.YANK_LAST_ARG; map['b'] = Operation.BACKWARD_WORD; map['c'] = Operation.CAPITALIZE_WORD; map['d'] = Operation.KILL_WORD; map['f'] = Operation.FORWARD_WORD; map['l'] = Operation.DOWNCASE_WORD; map['p'] = Operation.NON_INCREMENTAL_REVERSE_SEARCH_HISTORY; map['r'] = Operation.REVERT_LINE; map['t'] = Operation.TRANSPOSE_WORDS; map['u'] = Operation.UPCASE_WORD; map['y'] = Operation.YANK_POP; map['~'] = Operation.TILDE_EXPAND; map[DELETE] = Operation.BACKWARD_KILL_WORD; return new KeyMap(EMACS_META, map, false); } public static KeyMap viInsertion() { Object[] map = new Object[KEYMAP_LENGTH]; Object[] ctrl = new Object[] { // Control keys. null, /* Control-@ */ Operation.SELF_INSERT, /* Control-A */ Operation.SELF_INSERT, /* Control-B */ Operation.SELF_INSERT, /* Control-C */ Operation.VI_EOF_MAYBE, /* Control-D */ Operation.SELF_INSERT, /* Control-E */ Operation.SELF_INSERT, /* Control-F */ Operation.SELF_INSERT, /* Control-G */ Operation.BACKWARD_DELETE_CHAR, /* Control-H */ Operation.COMPLETE, /* Control-I */ Operation.ACCEPT_LINE, /* Control-J */ Operation.SELF_INSERT, /* Control-K */ Operation.SELF_INSERT, /* Control-L */ Operation.ACCEPT_LINE, /* Control-M */ Operation.MENU_COMPLETE, /* Control-N */ Operation.SELF_INSERT, /* Control-O */ Operation.MENU_COMPLETE_BACKWARD, /* Control-P */ Operation.SELF_INSERT, /* Control-Q */ Operation.REVERSE_SEARCH_HISTORY, /* Control-R */ Operation.FORWARD_SEARCH_HISTORY, /* Control-S */ Operation.TRANSPOSE_CHARS, /* Control-T */ Operation.UNIX_LINE_DISCARD, /* Control-U */ Operation.QUOTED_INSERT, /* Control-V */ Operation.UNIX_WORD_RUBOUT, /* Control-W */ Operation.SELF_INSERT, /* Control-X */ Operation.YANK, /* Control-Y */ Operation.SELF_INSERT, /* Control-Z */ Operation.VI_MOVEMENT_MODE, /* Control-[ */ Operation.SELF_INSERT, /* Control-\ */ Operation.SELF_INSERT, /* Control-] */ Operation.SELF_INSERT, /* Control-^ */ Operation.UNDO, /* Control-_ */ }; System.arraycopy( ctrl, 0, map, 0, ctrl.length ); for (int i = 32; i < 256; i++) { map[i] = Operation.SELF_INSERT; } map[DELETE] = Operation.BACKWARD_DELETE_CHAR; return new KeyMap(VI_INSERT, map, false); } public static KeyMap viMovement() { Object[] map = new Object[KEYMAP_LENGTH]; Object[] low = new Object[] { // Control keys. null, /* Control-@ */ null, /* Control-A */ null, /* Control-B */ Operation.INTERRUPT, /* Control-C */ /* * ^D is supposed to move down half a screen. In bash * appears to be ignored. */ Operation.VI_EOF_MAYBE, /* Control-D */ Operation.EMACS_EDITING_MODE, /* Control-E */ null, /* Control-F */ Operation.ABORT, /* Control-G */ Operation.BACKWARD_CHAR, /* Control-H */ null, /* Control-I */ Operation.VI_MOVE_ACCEPT_LINE, /* Control-J */ Operation.KILL_LINE, /* Control-K */ Operation.CLEAR_SCREEN, /* Control-L */ Operation.VI_MOVE_ACCEPT_LINE, /* Control-M */ Operation.VI_NEXT_HISTORY, /* Control-N */ null, /* Control-O */ Operation.VI_PREVIOUS_HISTORY, /* Control-P */ /* * My testing with readline is the ^Q is ignored. * Maybe this should be null? */ Operation.QUOTED_INSERT, /* Control-Q */ /* * TODO - Very broken. While in forward/reverse * history search the VI keyset should go out the * window and we need to enter a very simple keymap. */ Operation.REVERSE_SEARCH_HISTORY, /* Control-R */ /* TODO */ Operation.FORWARD_SEARCH_HISTORY, /* Control-S */ Operation.TRANSPOSE_CHARS, /* Control-T */ Operation.UNIX_LINE_DISCARD, /* Control-U */ /* TODO */ Operation.QUOTED_INSERT, /* Control-V */ Operation.UNIX_WORD_RUBOUT, /* Control-W */ null, /* Control-X */ /* TODO */ Operation.YANK, /* Control-Y */ null, /* Control-Z */ emacsMeta(), /* Control-[ */ null, /* Control-\ */ /* TODO */ Operation.CHARACTER_SEARCH, /* Control-] */ null, /* Control-^ */ /* TODO */ Operation.UNDO, /* Control-_ */ Operation.FORWARD_CHAR, /* SPACE */ null, /* ! */ null, /* " */ Operation.VI_INSERT_COMMENT, /* # */ Operation.END_OF_LINE, /* $ */ Operation.VI_MATCH, /* % */ Operation.VI_TILDE_EXPAND, /* & */ null, /* ' */ null, /* ( */ null, /* ) */ /* TODO */ Operation.VI_COMPLETE, /* * */ Operation.VI_NEXT_HISTORY, /* + */ Operation.VI_CHAR_SEARCH, /* , */ Operation.VI_PREVIOUS_HISTORY, /* - */ /* TODO */ Operation.VI_REDO, /* . */ Operation.VI_SEARCH, /* / */ Operation.VI_BEGNNING_OF_LINE_OR_ARG_DIGIT, /* 0 */ Operation.VI_ARG_DIGIT, /* 1 */ Operation.VI_ARG_DIGIT, /* 2 */ Operation.VI_ARG_DIGIT, /* 3 */ Operation.VI_ARG_DIGIT, /* 4 */ Operation.VI_ARG_DIGIT, /* 5 */ Operation.VI_ARG_DIGIT, /* 6 */ Operation.VI_ARG_DIGIT, /* 7 */ Operation.VI_ARG_DIGIT, /* 8 */ Operation.VI_ARG_DIGIT, /* 9 */ null, /* : */ Operation.VI_CHAR_SEARCH, /* ; */ null, /* < */ Operation.VI_COMPLETE, /* = */ null, /* > */ Operation.VI_SEARCH, /* ? */ null, /* @ */ Operation.VI_APPEND_EOL, /* A */ Operation.VI_PREV_WORD, /* B */ Operation.VI_CHANGE_TO_EOL, /* C */ Operation.VI_DELETE_TO_EOL, /* D */ Operation.VI_END_WORD, /* E */ Operation.VI_CHAR_SEARCH, /* F */ /* I need to read up on what this does */ Operation.VI_FETCH_HISTORY, /* G */ null, /* H */ Operation.VI_INSERT_BEG, /* I */ null, /* J */ null, /* K */ null, /* L */ null, /* M */ Operation.VI_SEARCH_AGAIN, /* N */ null, /* O */ Operation.VI_PUT, /* P */ null, /* Q */ /* TODO */ Operation.VI_REPLACE, /* R */ Operation.VI_KILL_WHOLE_LINE, /* S */ Operation.VI_CHAR_SEARCH, /* T */ /* TODO */ Operation.REVERT_LINE, /* U */ null, /* V */ Operation.VI_NEXT_WORD, /* W */ Operation.VI_RUBOUT, /* X */ Operation.VI_YANK_TO, /* Y */ null, /* Z */ null, /* [ */ Operation.VI_COMPLETE, /* \ */ null, /* ] */ Operation.VI_FIRST_PRINT, /* ^ */ Operation.VI_YANK_ARG, /* _ */ Operation.VI_GOTO_MARK, /* ` */ Operation.VI_APPEND_MODE, /* a */ Operation.VI_PREV_WORD, /* b */ Operation.VI_CHANGE_TO, /* c */ Operation.VI_DELETE_TO, /* d */ Operation.VI_END_WORD, /* e */ Operation.VI_CHAR_SEARCH, /* f */ null, /* g */ Operation.BACKWARD_CHAR, /* h */ Operation.VI_INSERTION_MODE, /* i */ Operation.NEXT_HISTORY, /* j */ Operation.PREVIOUS_HISTORY, /* k */ Operation.FORWARD_CHAR, /* l */ Operation.VI_SET_MARK, /* m */ Operation.VI_SEARCH_AGAIN, /* n */ null, /* o */ Operation.VI_PUT, /* p */ null, /* q */ Operation.VI_CHANGE_CHAR, /* r */ Operation.VI_SUBST, /* s */ Operation.VI_CHAR_SEARCH, /* t */ Operation.UNDO, /* u */ null, /* v */ Operation.VI_NEXT_WORD, /* w */ Operation.VI_DELETE, /* x */ Operation.VI_YANK_TO, /* y */ null, /* z */ null, /* { */ Operation.VI_COLUMN, /* | */ null, /* } */ Operation.VI_CHANGE_CASE, /* ~ */ Operation.VI_DELETE /* DEL */ }; System.arraycopy( low, 0, map, 0, low.length ); for (int i = 128; i < 256; i++) { map[i] = null; } return new KeyMap(VI_MOVE, map, false); } }