1 /* 2 * Copyright (c) 2002-2016, the original author or authors. 3 * 4 * This software is distributable under the BSD license. See the terms of the 5 * BSD license in the documentation provided with this software. 6 * 7 * https://opensource.org/licenses/BSD-3-Clause 8 */ 9 package jdk.internal.org.jline.keymap; 10 11 import java.io.IOException; 12 import java.io.StringWriter; 13 import java.util.ArrayList; 14 import java.util.Collection; 15 import java.util.Comparator; 16 import java.util.Map; 17 import java.util.TreeMap; 18 19 import jdk.internal.org.jline.terminal.Terminal; 20 import jdk.internal.org.jline.utils.Curses; 21 import jdk.internal.org.jline.utils.InfoCmp.Capability; 22 23 /** 24 * The KeyMap class contains all bindings from keys to operations. 25 * 26 * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a> 27 * @since 2.6 28 */ 29 public class KeyMap<T> { 30 31 public static final int KEYMAP_LENGTH = 128; 32 public static final long DEFAULT_AMBIGUOUS_TIMEOUT = 1000L; 33 34 private Object[] mapping = new Object[KEYMAP_LENGTH]; 35 private T anotherKey = null; 36 private T unicode; 37 private T nomatch; 38 private long ambiguousTimeout = DEFAULT_AMBIGUOUS_TIMEOUT; 39 display(String key)40 public static String display(String key) { 41 StringBuilder sb = new StringBuilder(); 42 sb.append("\""); 43 for (int i = 0; i < key.length(); i++) { 44 char c = key.charAt(i); 45 if (c < 32) { 46 sb.append('^'); 47 sb.append((char) (c + 'A' - 1)); 48 } else if (c == 127) { 49 sb.append("^?"); 50 } else if (c == '^' || c == '\\') { 51 sb.append('\\').append(c); 52 } else if (c >= 128) { 53 sb.append(String.format("\\u%04x", (int) c)); 54 } else { 55 sb.append(c); 56 } 57 } 58 sb.append("\""); 59 return sb.toString(); 60 } 61 translate(String str)62 public static String translate(String str) { 63 int i; 64 if (!str.isEmpty()) { 65 char c = str.charAt(0); 66 if ((c == '\'' || c == '"') && str.charAt(str.length() - 1) == c) { 67 str = str.substring(1, str.length() - 1); 68 } 69 } 70 StringBuilder keySeq = new StringBuilder(); 71 for (i = 0; i < str.length(); i++) { 72 char c = str.charAt(i); 73 if (c == '\\') { 74 if (++i >= str.length()) { 75 break; 76 } 77 c = str.charAt(i); 78 switch (c) { 79 case 'a': 80 c = 0x07; 81 break; 82 case 'b': 83 c = '\b'; 84 break; 85 case 'd': 86 c = 0x7f; 87 break; 88 case 'e': 89 case 'E': 90 c = 0x1b; 91 break; 92 case 'f': 93 c = '\f'; 94 break; 95 case 'n': 96 c = '\n'; 97 break; 98 case 'r': 99 c = '\r'; 100 break; 101 case 't': 102 c = '\t'; 103 break; 104 case 'v': 105 c = 0x0b; 106 break; 107 case '\\': 108 c = '\\'; 109 break; 110 case '0': 111 case '1': 112 case '2': 113 case '3': 114 case '4': 115 case '5': 116 case '6': 117 case '7': 118 c = 0; 119 for (int j = 0; j < 3; j++, i++) { 120 if (i >= str.length()) { 121 break; 122 } 123 int k = Character.digit(str.charAt(i), 8); 124 if (k < 0) { 125 break; 126 } 127 c = (char) (c * 8 + k); 128 } 129 i--; 130 c &= 0xFF; 131 break; 132 case 'x': 133 i++; 134 c = 0; 135 for (int j = 0; j < 2; j++, i++) { 136 if (i >= str.length()) { 137 break; 138 } 139 int k = Character.digit(str.charAt(i), 16); 140 if (k < 0) { 141 break; 142 } 143 c = (char) (c * 16 + k); 144 } 145 i--; 146 c &= 0xFF; 147 break; 148 case 'u': 149 i++; 150 c = 0; 151 for (int j = 0; j < 4; j++, i++) { 152 if (i >= str.length()) { 153 break; 154 } 155 int k = Character.digit(str.charAt(i), 16); 156 if (k < 0) { 157 break; 158 } 159 c = (char) (c * 16 + k); 160 } 161 break; 162 case 'C': 163 if (++i >= str.length()) { 164 break; 165 } 166 c = str.charAt(i); 167 if (c == '-') { 168 if (++i >= str.length()) { 169 break; 170 } 171 c = str.charAt(i); 172 } 173 c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f); 174 break; 175 } 176 } else if (c == '^') { 177 if (++i >= str.length()) { 178 break; 179 } 180 c = str.charAt(i); 181 if (c != '^') { 182 c = c == '?' ? 0x7f : (char) (Character.toUpperCase(c) & 0x1f); 183 } 184 } 185 keySeq.append(c); 186 } 187 return keySeq.toString(); 188 } 189 range(String range)190 public static Collection<String> range(String range) { 191 String[] keys = range.split("-"); 192 if (keys.length != 2) { 193 return null; 194 } 195 keys[0] = translate(keys[0]); 196 keys[1] = translate(keys[1]); 197 if (keys[0].length() != keys[1].length()) { 198 return null; 199 } 200 String pfx; 201 if (keys[0].length() > 1) { 202 pfx = keys[0].substring(0, keys[0].length() - 1); 203 if (!keys[1].startsWith(pfx)) { 204 return null; 205 } 206 } else { 207 pfx = ""; 208 } 209 char c0 = keys[0].charAt(keys[0].length() - 1); 210 char c1 = keys[1].charAt(keys[1].length() - 1); 211 if (c0 > c1) { 212 return null; 213 } 214 Collection<String> seqs = new ArrayList<>(); 215 for (char c = c0; c <= c1; c++) { 216 seqs.add(pfx + c); 217 } 218 return seqs; 219 } 220 221 esc()222 public static String esc() { 223 return "\033"; 224 } 225 alt(char c)226 public static String alt(char c) { 227 return "\033" + c; 228 } 229 alt(String c)230 public static String alt(String c) { 231 return "\033" + c; 232 } 233 del()234 public static String del() { 235 return "\177"; 236 } 237 ctrl(char key)238 public static String ctrl(char key) { 239 return key == '?' ? del() : Character.toString((char) (Character.toUpperCase(key) & 0x1f)); 240 } 241 key(Terminal terminal, Capability capability)242 public static String key(Terminal terminal, Capability capability) { 243 return Curses.tputs(terminal.getStringCapability(capability)); 244 } 245 246 public static final Comparator<String> KEYSEQ_COMPARATOR = (s1, s2) -> { 247 int len1 = s1.length(); 248 int len2 = s2.length(); 249 int lim = Math.min(len1, len2); 250 int k = 0; 251 while (k < lim) { 252 char c1 = s1.charAt(k); 253 char c2 = s2.charAt(k); 254 if (c1 != c2) { 255 int l = len1 - len2; 256 return l != 0 ? l : c1 - c2; 257 } 258 k++; 259 } 260 return len1 - len2; 261 }; 262 263 // 264 // Methods 265 // 266 267 getUnicode()268 public T getUnicode() { 269 return unicode; 270 } 271 setUnicode(T unicode)272 public void setUnicode(T unicode) { 273 this.unicode = unicode; 274 } 275 getNomatch()276 public T getNomatch() { 277 return nomatch; 278 } 279 setNomatch(T nomatch)280 public void setNomatch(T nomatch) { 281 this.nomatch = nomatch; 282 } 283 getAmbiguousTimeout()284 public long getAmbiguousTimeout() { 285 return ambiguousTimeout; 286 } 287 setAmbiguousTimeout(long ambiguousTimeout)288 public void setAmbiguousTimeout(long ambiguousTimeout) { 289 this.ambiguousTimeout = ambiguousTimeout; 290 } 291 getAnotherKey()292 public T getAnotherKey() { 293 return anotherKey; 294 } 295 getBoundKeys()296 public Map<String, T> getBoundKeys() { 297 Map<String, T> bound = new TreeMap<>(KEYSEQ_COMPARATOR); 298 doGetBoundKeys(this, "", bound); 299 return bound; 300 } 301 302 @SuppressWarnings("unchecked") doGetBoundKeys(KeyMap<T> keyMap, String prefix, Map<String, T> bound)303 private static <T> void doGetBoundKeys(KeyMap<T> keyMap, String prefix, Map<String, T> bound) { 304 if (keyMap.anotherKey != null) { 305 bound.put(prefix, keyMap.anotherKey); 306 } 307 for (int c = 0; c < keyMap.mapping.length; c++) { 308 if (keyMap.mapping[c] instanceof KeyMap) { 309 doGetBoundKeys((KeyMap<T>) keyMap.mapping[c], 310 prefix + (char) (c), 311 bound); 312 } else if (keyMap.mapping[c] != null) { 313 bound.put(prefix + (char) (c), (T) keyMap.mapping[c]); 314 } 315 } 316 } 317 318 @SuppressWarnings("unchecked") getBound(CharSequence keySeq, int[] remaining)319 public T getBound(CharSequence keySeq, int[] remaining) { 320 remaining[0] = -1; 321 if (keySeq != null && keySeq.length() > 0) { 322 char c = keySeq.charAt(0); 323 if (c >= mapping.length) { 324 remaining[0] = Character.codePointCount(keySeq, 0, keySeq.length()); 325 return null; 326 } else { 327 if (mapping[c] instanceof KeyMap) { 328 CharSequence sub = keySeq.subSequence(1, keySeq.length()); 329 return ((KeyMap<T>) mapping[c]).getBound(sub, remaining); 330 } else if (mapping[c] != null) { 331 remaining[0] = keySeq.length() - 1; 332 return (T) mapping[c]; 333 } else { 334 remaining[0] = keySeq.length(); 335 return anotherKey; 336 } 337 } 338 } else { 339 return anotherKey; 340 } 341 } 342 getBound(CharSequence keySeq)343 public T getBound(CharSequence keySeq) { 344 int[] remaining = new int[1]; 345 T res = getBound(keySeq, remaining); 346 return remaining[0] <= 0 ? res : null; 347 } 348 bindIfNotBound(T function, CharSequence keySeq)349 public void bindIfNotBound(T function, CharSequence keySeq) { 350 if (function != null && keySeq != null) { 351 bind(this, keySeq, function, true); 352 } 353 } 354 bind(T function, CharSequence... keySeqs)355 public void bind(T function, CharSequence... keySeqs) { 356 for (CharSequence keySeq : keySeqs) { 357 bind(function, keySeq); 358 } 359 } 360 bind(T function, Iterable<? extends CharSequence> keySeqs)361 public void bind(T function, Iterable<? extends CharSequence> keySeqs) { 362 for (CharSequence keySeq : keySeqs) { 363 bind(function, keySeq); 364 } 365 } 366 bind(T function, CharSequence keySeq)367 public void bind(T function, CharSequence keySeq) { 368 if (keySeq != null) { 369 if (function == null) { 370 unbind(keySeq); 371 } else { 372 bind(this, keySeq, function, false); 373 } 374 } 375 } 376 unbind(CharSequence... keySeqs)377 public void unbind(CharSequence... keySeqs) { 378 for (CharSequence keySeq : keySeqs) { 379 unbind(keySeq); 380 } 381 } 382 unbind(CharSequence keySeq)383 public void unbind(CharSequence keySeq) { 384 if (keySeq != null) { 385 unbind(this, keySeq); 386 } 387 } 388 389 @SuppressWarnings("unchecked") unbind(KeyMap<T> map, CharSequence keySeq)390 private static <T> T unbind(KeyMap<T> map, CharSequence keySeq) { 391 KeyMap<T> prev = null; 392 if (keySeq != null && keySeq.length() > 0) { 393 for (int i = 0; i < keySeq.length() - 1; i++) { 394 char c = keySeq.charAt(i); 395 if (c > map.mapping.length) { 396 return null; 397 } 398 if (!(map.mapping[c] instanceof KeyMap)) { 399 return null; 400 } 401 prev = map; 402 map = (KeyMap<T>) map.mapping[c]; 403 } 404 char c = keySeq.charAt(keySeq.length() - 1); 405 if (c > map.mapping.length) { 406 return null; 407 } 408 if (map.mapping[c] instanceof KeyMap) { 409 KeyMap<?> sub = (KeyMap) map.mapping[c]; 410 Object res = sub.anotherKey; 411 sub.anotherKey = null; 412 return (T) res; 413 } else { 414 Object res = map.mapping[c]; 415 map.mapping[c] = null; 416 int nb = 0; 417 for (int i = 0; i < map.mapping.length; i++) { 418 if (map.mapping[i] != null) { 419 nb++; 420 } 421 } 422 if (nb == 0 && prev != null) { 423 prev.mapping[keySeq.charAt(keySeq.length() - 2)] = map.anotherKey; 424 } 425 return (T) res; 426 } 427 } 428 return null; 429 } 430 431 @SuppressWarnings("unchecked") bind(KeyMap<T> map, CharSequence keySeq, T function, boolean onlyIfNotBound)432 private static <T> void bind(KeyMap<T> map, CharSequence keySeq, T function, boolean onlyIfNotBound) { 433 if (keySeq != null && keySeq.length() > 0) { 434 for (int i = 0; i < keySeq.length(); i++) { 435 char c = keySeq.charAt(i); 436 if (c >= map.mapping.length) { 437 return; 438 } 439 if (i < keySeq.length() - 1) { 440 if (!(map.mapping[c] instanceof KeyMap)) { 441 KeyMap<T> m = new KeyMap<>(); 442 m.anotherKey = (T) map.mapping[c]; 443 map.mapping[c] = m; 444 } 445 map = (KeyMap) map.mapping[c]; 446 } else { 447 if (map.mapping[c] instanceof KeyMap) { 448 ((KeyMap) map.mapping[c]).anotherKey = function; 449 } else { 450 Object op = map.mapping[c]; 451 if (!onlyIfNotBound || op == null) { 452 map.mapping[c] = function; 453 } 454 } 455 } 456 } 457 } 458 } 459 460 } 461