2 * Copyright Neil Brown ©2015-2019 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
7 * A keymap maps a key to a command.
8 * Keys are ordered for fast binary-search lookup.
9 * A "key" is an arbitrary string which typically contains
10 * some 'mode' prefix and some specific tail.
11 * e.g emacs:M:C-x is Meta-Control-X in emacs mode.
12 * As far as the map is concerned, it is just a lexically order string.
14 * A 'command' is a struct provided by any of various
17 * A range can be stored by setting the lsb of the command pointer at
18 * the start of the range.
19 * When searching for a key we find the first entry that is not less
20 * than the target. If it is an exact match, use it. If previous entry
21 * exists and has the lsb set, then use that command.
23 * So to add a range, the start is entered with lsb set, and the end it
24 * entered with lsb clear.
26 * If a key is registered a second time, the new over-rides the old.
27 * This is particularly useful for registering a range, and then some
29 * To delete a key from a range we need to make two ranges, one that ends
30 * just before the new key, one that starts just after.
31 * The 'ends just before' is easy - we just add the new key or range.
32 * The 'starts just after' is managed by entering the same key twice.
33 * The first instance of the key has a 'lsb clear' command and is used for
34 * exact matches. The second instance has 'lsb set' and is used for everything
37 * A 'prefix' can be registered which creates a command which temporarily
38 * enabled the given prefix. It is applied to the next command, but is
39 * discarded after that. This is just a convenience function.
50 inline static int qhash(char key, unsigned int start)
52 return (start ^ key) * 0x61C88647U;
56 unsigned long bloom[256 / (sizeof(long)*8) ];
61 char * safe *keys safe;
62 struct command * safe *comms safe;
65 static inline struct command * safe GETCOMM(struct command *c safe)
67 return (struct command * safe)(((unsigned long)c) & ~1UL);
70 static inline int IS_RANGE(struct command *c)
72 return ((unsigned long)c) & 1;
75 static inline struct command *SET_RANGE(struct command *c)
77 return (struct command *)(((unsigned long)c) | 1UL);
80 static int size2alloc(int size)
82 /* Alway multiple of 8. */
83 return ((size-1) | 7) + 1;
86 struct map *safe key_alloc(void)
88 struct map *m = calloc(1, sizeof(*m));
94 void key_free(struct map *m safe)
97 for (i = 0; i < m->size; i++) {
99 command_put(GETCOMM(m->comms[i]));
106 static int hash_str(char *key safe, int len)
110 for (i = 0; (len < 0 || i < len) && key[i]; i++)
111 h = qhash(key[i], h);
115 inline static void set_bit(unsigned long *set safe, int bit)
117 set[bit/(sizeof(unsigned long)*8)] |=
118 1UL << (bit % (sizeof(unsigned long)*8));
121 inline static int test_bit(unsigned long *set safe, int bit)
123 return !! (set[bit/(sizeof(unsigned long)*8)] &
124 (1UL << (bit % (sizeof(unsigned long)*8))));
128 static int key_present(struct map *map safe, const char *key, int klen,
129 unsigned int *hashp safe)
135 for (i = 0; i < map->size; i++) {
136 hash = hash_str(map->keys[i], map->prefix_len);
137 set_bit(map->bloom, hash&0xff);
138 set_bit(map->bloom, (hash>>8)&0xff);
139 set_bit(map->bloom, (hash>>16)&0xff);
143 if (map->prefix_len < 0 || klen <= map->prefix_len)
146 hash = hashp[map->prefix_len];
147 return (test_bit(map->bloom, hash&0xff) &&
148 test_bit(map->bloom, (hash>>8)&0xff) &&
149 test_bit(map->bloom, (hash>>16)&0xff));
152 /* Find first entry >= k */
153 static int key_find_len(struct map *map safe, const char *k safe, int len)
158 /* all entries before 'lo' are < k.
159 * all entries at 'hi' or later are >= k.
160 * So when lo==hi, hi is the answer.
163 int mid = (hi + lo)/ 2;
164 int cmp = strncmp(map->keys[mid], k, len);
173 static int key_find(struct map *map safe, const char *k safe)
175 return key_find_len(map, k, strlen(k));
178 void key_add(struct map *map safe, const char *k safe, struct command *comm)
182 struct command *comm2 = NULL;
188 pos = key_find(map, k);
190 * 1/ match start of range: insert before
191 * 2/ match non-range start: replace
192 * 3/ not in range: insert before like 1
193 * 4/ in a range: insert match and range start.
195 if (pos >= map->size) {
196 /* Insert k,comm - default action */
197 } else if (strcmp(k, map->keys[pos]) == 0) {
198 /* match: need to check if range-start */
199 if (IS_RANGE(map->comms[pos])) {
200 /* Changing the start of a range,
201 * insert an exact match */
203 /* replace a non-range */
204 command_put(map->comms[pos]);
205 map->comms[pos] = command_get(comm);
208 } else if (pos > 0 && IS_RANGE(map->comms[pos-1])) {
209 /* insert within a range.
210 * Add given command as non-range match, and old command
213 comm2 = map->comms[pos-1];
215 /* Not in a range, simple insert */
218 ins_cnt = comm2 ? 2 : 1;
219 size = size2alloc(map->size + ins_cnt);
221 if (size2alloc(map->size) != size) {
222 map->keys = realloc(map->keys, size * sizeof(map->keys[0]));
223 map->comms = realloc(map->comms,
224 size * sizeof(struct command *));
227 memmove(map->keys+pos+ins_cnt, map->keys+pos,
228 (map->size - pos) * sizeof(map->keys[0]));
229 memmove(map->comms+pos+ins_cnt, map->comms+pos,
230 (map->size - pos) * sizeof(struct command *));
231 map->keys[pos] = strdup(k);
232 map->comms[pos] = command_get(comm);
234 map->keys[pos+1] = strdup(k);
235 map->comms[pos+1] = SET_RANGE(command_get(GETCOMM(comm2)));
237 map->size += ins_cnt;
241 void key_add_range(struct map *map safe,
242 const char *first safe, const char *last safe,
243 struct command *comm)
250 if (!comm || strcmp(first, last) >= 0)
253 /* Add the first entry using key_add */
254 key_add(map, first, comm);
255 pos = key_find(map, first);
256 pos2 = key_find(map, last);
258 /* Now 'pos' is a stand-alone entry for 'first'.
259 * If the entry before pos2 is a range start, update to start at 'last',
260 * else discard it, and discard everything else between pos and pos2.
261 * Then insert a stand-alone for 'last' and update 'pos' to be a
264 if (pos2 - 1 > pos && IS_RANGE(map->comms[pos2-1])) {
265 free(map->keys[pos2-1]);
266 map->keys[pos2-1] = strdup(last);
269 /* Need to insert 'last', and remove extras. so +1 and -(pos2-pos-1); */
270 move_size = 1 - (pos2 - pos - 1);
271 size = size2alloc(map->size + move_size);
272 if (size2alloc(map->size) < size) {
273 map->keys = realloc(map->keys, size * sizeof(map->keys[0]));
274 map->comms = realloc(map->comms,
275 size * sizeof(struct command *));
277 for (i = pos + 1; i < pos2; i++) {
279 command_put(GETCOMM(map->comms[i]));
281 memmove(map->keys+pos2 + move_size, map->keys+pos2,
282 (map->size - pos2) * sizeof(map->keys[0]));
283 memmove(map->comms+pos2+ move_size, map->comms+pos2,
284 (map->size - pos2) * sizeof(struct command *));
286 map->comms[pos] = SET_RANGE(comm);
287 map->keys[pos+1] = strdup(last);
288 map->comms[pos+1] = command_get(comm);
289 map->size += move_size;
292 first[prefix] && first[prefix+1] == last[prefix+1];
295 if (map->prefix_len < 0 || map->prefix_len > prefix)
296 map->prefix_len = prefix;
299 void key_add_chain(struct map *map safe, struct map *chain)
308 void key_del(struct map *map, wint_t k)
312 pos = key_find(map, k, -1);
313 if (pos >= map->size || strcmp(map->keys[pos], k) == 0)
316 memmove(map->keys+pos, map->keys+pos+1,
317 (map->size-pos-1) * sizeof(map->keys[0]));
318 memmove(map->comms+pos, map->comms+pos+1,
319 (map->size-pos-1) * sizeof(struct command *));
325 int key_pfx_func(const struct cmd_info *ci safe)
327 struct pfx_cmd *m = container_of(ci->comm, struct pfx_cmd, c);
329 call("Mode:set-all", ci->focus, ci->num, NULL, m->pfx, ci->num2);
333 struct command *key_lookup_cmd(struct map *m safe, const char *c safe,
334 const char **cret, unsigned int *lenret)
336 /* If 'k' contains an ASCII US (Unit Separator, 0o37 0x1f 31),
337 * it represents multiple keys.
338 * Call key_find() on each of them until success.
341 const char *end = strchr(c, '\037');
346 pos = key_find_len(m, c, end - c);
350 else if (strncmp(m->keys[pos], c, end - c) == 0 &&
351 m->keys[pos][end - c] == '\0') {
352 /* Exact match - use this entry */
357 return GETCOMM(m->comms[pos]);
358 } else if (pos > 0 && IS_RANGE(m->comms[pos-1])) {
359 /* In a range, use previous */
364 return GETCOMM(m->comms[pos-1]);
373 int key_lookup(struct map *m safe, const struct cmd_info *ci safe)
375 struct command *comm;
380 if (ci->hash && !key_present(m, ci->key, strlen(ci->key), ci->hash)) {
381 stat_count("bloom-miss");
385 comm = key_lookup_cmd(m, ci->key, &key, &len);
386 if (comm == NULL || key == NULL) {
387 stat_count("bloom-hit-bad");
390 /* This is message, but when there are multiple
391 * keys, we need to pass down the one that was matched.
394 const char *oldkey = ci->key;
395 char ktmp[40], *k2 = NULL;
397 stat_count("bloom-hit-good");
399 ((struct cmd_info*)ci)->key = key;
400 } else if (len >= sizeof(ktmp)) {
401 k2 = strndup(key, len);
402 ((struct cmd_info*)ci)->key = k2;
404 strncpy(ktmp, key, len);
406 ((struct cmd_info*)ci)->key = ktmp;
408 ((struct cmd_info*)ci)->comm = comm;
409 ret = comm->func(ci);
410 ((struct cmd_info*)ci)->key = oldkey;
416 int key_lookup_prefix(struct map *m safe, const struct cmd_info *ci safe)
418 int pos = key_find(m, ci->key);
419 struct command *comm, *prev = NULL;
420 int len = strlen(ci->key);
421 const char *k = ci->key;
423 while (pos < m->size && strncmp(m->keys[pos], k, len) == 0) {
424 comm = GETCOMM(m->comms[pos]);
425 if (comm && comm != prev) {
427 ((struct cmd_info*)ci)->comm = comm;
428 ((struct cmd_info*)ci)->key = m->keys[pos];
429 ret = comm->func(ci);
430 ASSERT(ret >= 0 || ret < Eunused);
437 ((struct cmd_info*)ci)->key = k;
441 int key_lookup_cmd_func(const struct cmd_info *ci safe)
443 struct lookup_cmd *l = container_of(ci->comm, struct lookup_cmd, c);
444 struct map *m = safe_cast *l->m;
445 int ret = key_lookup(m, ci);
447 while (!ret && m->chain) {
449 ret = key_lookup(m, ci);
454 /* key_handle. Search towards root for the pane which handles the command.
456 int key_handle(const struct cmd_info *ci safe)
458 struct cmd_info *vci = (struct cmd_info*)ci;
460 unsigned int hash[30];
464 time_start_key(ci->key);
465 if ((void*) ci->comm) {
466 int ret = ci->comm->func(ci);
467 time_stop_key(ci->key);
471 for (i = 0; i < 30 && ci->key[i]; i++) {
472 h = qhash(ci->key[i], h);
475 if (ci->key[i] == '\037')
476 // Disable hash for multi-keys
483 /* If 'home' is set, search from there, else search
492 if (p->handle && !(p->damaged & DAMAGED_DEAD)) {
494 vci->comm = p->handle;
495 ret = p->handle->func(ci);
498 time_stop_key(ci->key);
499 /* 'p' might have been destroyed */
507 time_stop_key(ci->key);