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