blob: d0bca43b97d26584c940d8413819e64188c82e90 [file] [log] [blame]
/*
* 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 <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);
}
}