blob: 43c3a1b4fd12407b0e856e5544d22b71f5534aeb [file] [log] [blame]
/*
* Copyright (c) 2002-2016, 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.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.keymap;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
/**
* 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<T> {
public static final int KEYMAP_LENGTH = 128;
public static final long DEFAULT_AMBIGUOUS_TIMEOUT = 1000L;
private Object[] mapping = new Object[KEYMAP_LENGTH];
private T anotherKey = null;
private T unicode;
private T nomatch;
private long ambiguousTimeout = DEFAULT_AMBIGUOUS_TIMEOUT;
public static String display(String key) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
if (c < 32) {
sb.append('^');
sb.append((char) (c + 'A' - 1));
} else if (c == 127) {
sb.append("^?");
} else if (c == '^' || c == '\\') {
sb.append('\\').append(c);
} else if (c >= 128) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
sb.append("\"");
return sb.toString();
}
public static String translate(String str) {
int i;
if (!str.isEmpty()) {
char c = str.charAt(0);
if ((c == '\'' || c == '"') && str.charAt(str.length() - 1) == c) {
str = str.substring(1, str.length() - 1);
}
}
StringBuilder keySeq = new StringBuilder();
for (i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\\') {
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
switch (c) {
case 'a':
c = 0x07;
break;
case 'b':
c = '\b';
break;
case 'd':
c = 0x7f;
break;
case 'e':
case 'E':
c = 0x1b;
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'v':
c = 0x0b;
break;
case '\\':
c = '\\';
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
c = 0;
for (int j = 0; j < 3; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 8);
if (k < 0) {
break;
}
c = (char) (c * 8 + k);
}
i--;
c &= 0xFF;
break;
case 'x':
i++;
c = 0;
for (int j = 0; j < 2; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 16);
if (k < 0) {
break;
}
c = (char) (c * 16 + k);
}
i--;
c &= 0xFF;
break;
case 'u':
i++;
c = 0;
for (int j = 0; j < 4; j++, i++) {
if (i >= str.length()) {
break;
}
int k = Character.digit(str.charAt(i), 16);
if (k < 0) {
break;
}
c = (char) (c * 16 + k);
}
break;
case 'C':
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
if (c == '-') {
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
}
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
break;
}
} else if (c == '^') {
if (++i >= str.length()) {
break;
}
c = str.charAt(i);
if (c != '^') {
c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f);
}
}
keySeq.append(c);
}
return keySeq.toString();
}
public static Collection<String> range(String range) {
String[] keys = range.split("-");
if (keys.length != 2) {
return null;
}
keys[0] = translate(keys[0]);
keys[1] = translate(keys[1]);
if (keys[0].length() != keys[1].length()) {
return null;
}
String pfx;
if (keys[0].length() > 1) {
pfx = keys[0].substring(0, keys[0].length() - 1);
if (!keys[1].startsWith(pfx)) {
return null;
}
} else {
pfx = "";
}
char c0 = keys[0].charAt(keys[0].length() - 1);
char c1 = keys[1].charAt(keys[1].length() - 1);
if (c0 > c1) {
return null;
}
Collection<String> seqs = new ArrayList<>();
for (char c = c0; c <= c1; c++) {
seqs.add(pfx + c);
}
return seqs;
}
public static String esc() {
return "\033";
}
public static String alt(char c) {
return "\033" + c;
}
public static String alt(String c) {
return "\033" + c;
}
public static String del() {
return "\177";
}
public static String ctrl(char key) {
return key == '?' ? del() : Character.toString((char) (Character.toUpperCase(key) & 0x1f));
}
public static String key(Terminal terminal, Capability capability) {
return Curses.tputs(terminal.getStringCapability(capability));
}
public static final Comparator<String> KEYSEQ_COMPARATOR = (s1, s2) -> {
int len1 = s1.length();
int len2 = s2.length();
int lim = Math.min(len1, len2);
int k = 0;
while (k < lim) {
char c1 = s1.charAt(k);
char c2 = s2.charAt(k);
if (c1 != c2) {
int l = len1 - len2;
return l != 0 ? l : c1 - c2;
}
k++;
}
return len1 - len2;
};
//
// Methods
//
public T getUnicode() {
return unicode;
}
public void setUnicode(T unicode) {
this.unicode = unicode;
}
public T getNomatch() {
return nomatch;
}
public void setNomatch(T nomatch) {
this.nomatch = nomatch;
}
public long getAmbiguousTimeout() {
return ambiguousTimeout;
}
public void setAmbiguousTimeout(long ambiguousTimeout) {
this.ambiguousTimeout = ambiguousTimeout;
}
public T getAnotherKey() {
return anotherKey;
}
public Map<String, T> getBoundKeys() {
Map<String, T> bound = new TreeMap<>(KEYSEQ_COMPARATOR);
doGetBoundKeys(this, "", bound);
return bound;
}
@SuppressWarnings("unchecked")
private static <T> void doGetBoundKeys(KeyMap<T> keyMap, String prefix, Map<String, T> bound) {
if (keyMap.anotherKey != null) {
bound.put(prefix, keyMap.anotherKey);
}
for (int c = 0; c < keyMap.mapping.length; c++) {
if (keyMap.mapping[c] instanceof KeyMap) {
doGetBoundKeys((KeyMap<T>) keyMap.mapping[c],
prefix + (char) (c),
bound);
} else if (keyMap.mapping[c] != null) {
bound.put(prefix + (char) (c), (T) keyMap.mapping[c]);
}
}
}
@SuppressWarnings("unchecked")
public T getBound(CharSequence keySeq, int[] remaining) {
remaining[0] = -1;
if (keySeq != null && keySeq.length() > 0) {
char c = keySeq.charAt(0);
if (c >= mapping.length) {
remaining[0] = Character.codePointCount(keySeq, 0, keySeq.length());
return null;
} else {
if (mapping[c] instanceof KeyMap) {
CharSequence sub = keySeq.subSequence(1, keySeq.length());
return ((KeyMap<T>) mapping[c]).getBound(sub, remaining);
} else if (mapping[c] != null) {
remaining[0] = keySeq.length() - 1;
return (T) mapping[c];
} else {
remaining[0] = keySeq.length();
return anotherKey;
}
}
} else {
return anotherKey;
}
}
public T getBound(CharSequence keySeq) {
int[] remaining = new int[1];
T res = getBound(keySeq, remaining);
return remaining[0] <= 0 ? res : null;
}
public void bindIfNotBound(T function, CharSequence keySeq) {
if (function != null && keySeq != null) {
bind(this, keySeq, function, true);
}
}
public void bind(T function, CharSequence... keySeqs) {
for (CharSequence keySeq : keySeqs) {
bind(function, keySeq);
}
}
public void bind(T function, Iterable<? extends CharSequence> keySeqs) {
for (CharSequence keySeq : keySeqs) {
bind(function, keySeq);
}
}
public void bind(T function, CharSequence keySeq) {
if (keySeq != null) {
if (function == null) {
unbind(keySeq);
} else {
bind(this, keySeq, function, false);
}
}
}
public void unbind(CharSequence... keySeqs) {
for (CharSequence keySeq : keySeqs) {
unbind(keySeq);
}
}
public void unbind(CharSequence keySeq) {
if (keySeq != null) {
unbind(this, keySeq);
}
}
@SuppressWarnings("unchecked")
private static <T> T unbind(KeyMap<T> map, CharSequence keySeq) {
KeyMap<T> prev = null;
if (keySeq != null && keySeq.length() > 0) {
for (int i = 0; i < keySeq.length() - 1; i++) {
char c = keySeq.charAt(i);
if (c > map.mapping.length) {
return null;
}
if (!(map.mapping[c] instanceof KeyMap)) {
return null;
}
prev = map;
map = (KeyMap<T>) map.mapping[c];
}
char c = keySeq.charAt(keySeq.length() - 1);
if (c > map.mapping.length) {
return null;
}
if (map.mapping[c] instanceof KeyMap) {
KeyMap<?> sub = (KeyMap) map.mapping[c];
Object res = sub.anotherKey;
sub.anotherKey = null;
return (T) res;
} else {
Object res = map.mapping[c];
map.mapping[c] = null;
int nb = 0;
for (int i = 0; i < map.mapping.length; i++) {
if (map.mapping[i] != null) {
nb++;
}
}
if (nb == 0 && prev != null) {
prev.mapping[keySeq.charAt(keySeq.length() - 2)] = map.anotherKey;
}
return (T) res;
}
}
return null;
}
@SuppressWarnings("unchecked")
private static <T> void bind(KeyMap<T> map, CharSequence keySeq, T 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<T> m = new KeyMap<>();
m.anotherKey = (T) map.mapping[c];
map.mapping[c] = m;
}
map = (KeyMap) map.mapping[c];
} else {
if (map.mapping[c] instanceof KeyMap) {
((KeyMap) map.mapping[c]).anotherKey = function;
} else {
Object op = map.mapping[c];
if (!onlyIfNotBound || op == null) {
map.mapping[c] = function;
}
}
}
}
}
}
}