/*
- * Copyright Neil Brown ©2015-2020 <neil@brown.name>
+ * Copyright Neil Brown ©2015-2023 <neil@brown.name>
* May be distributed under terms of GPLv2 - see file:COPYING
*
* Keymaps for edlib.
* Keys are ordered for fast binary-search lookup.
* A "key" is an arbitrary string which typically contains
* some 'mode' prefix and some specific tail.
- * e.g emacs:M:C-X is Meta-Control-X in emacs mode.
+ * e.g emacs:A:C-X is Alt-Control-X in emacs mode.
* As far as the map is concerned, it is just a lexically order string.
*
* A 'command' is a struct provided by any of various
*
*/
+#define _GNU_SOURCE /* for asprintf */
#include <unistd.h>
#include <stdlib.h>
#include <memory.h>
+#include <stdio.h>
#include "core.h"
#include "misc.h"
struct map {
unsigned long bloom[256 / (sizeof(unsigned long)*8) ];
- short changed;
+ bool changed;
+ bool check_all;
short size;
struct map *chain;
char * safe *keys safe;
if (ci->comm == &keymap_list)
/* should be impossible */
- return 0;
+ return Efallthrough;
/* ci->comm MUST be the keymap */
m = (struct map* safe)ci->comm;
free(m);
}
-static void hash_str(const char *key safe, int len, unsigned int *hashes safe)
+static bool hash_str(const char *key safe, int len, unsigned int *hashes safe)
{
int i;
int h = 0;
+ bool prefix = False;
for (i = 0; (len < 0 || i < len) && key[i]; i++) {
h = qhash(key[i], h);
- if (key[i] == '-' || key[i] == ':')
+ if (key[i] == '-' || key[i] == ':') {
+ prefix = True;
break;
+ }
}
hashes[1] = h;
for (; (len < 0 || i < len) && key[i]; i++)
h = qhash(key[i], h);
hashes[0] = h;
+ return prefix;
}
inline static void set_bit(unsigned long *set safe, int bit)
(1UL << (bit % (sizeof(unsigned long)*8))));
}
-
-static int key_present(struct map *map safe, unsigned int *hashes safe)
+static bool key_present(struct map *map safe, unsigned int *hashes safe)
{
if (map->changed) {
int i;
+ map->check_all = False;
for (i = 0; i < map->size; i++) {
unsigned int h[2];
- hash_str(map->keys[i], -1, h);
+ bool prefix = hash_str(map->keys[i], -1, h);
if (IS_RANGE(map->comms[i])) {
+ if (!prefix)
+ map->check_all = True;
set_bit(map->bloom, h[1]&0xff);
set_bit(map->bloom, (h[1]>>8)&0xff);
set_bit(map->bloom, (h[1]>>16)&0xff);
set_bit(map->bloom, (h[0]>>16)&0xff);
}
}
- map->changed = 0;
+ map->changed = False;
}
+ if (map->check_all)
+ return True;
if (test_bit(map->bloom, hashes[0]&0xff) &&
test_bit(map->bloom, (hashes[0]>>8)&0xff) &&
test_bit(map->bloom, (hashes[0]>>16)&0xff))
- return 1;
+ return True;
if (test_bit(map->bloom, hashes[1]&0xff) &&
test_bit(map->bloom, (hashes[1]>>8)&0xff) &&
test_bit(map->bloom, (hashes[1]>>16)&0xff))
- return 1;
- return 0;
+ return True;
+ return False;
}
/* Find first entry >= k */
if (!comm)
return;
+ if (strcmp(k, "Close") == 0 &&
+ !comm->closed_ok) {
+ LOG("WARNING: Command %s registered for \"Close\" but not marked closed_ok",
+ comm->name);
+ }
pos = key_find(map, k);
/* cases:
map->comms[pos+1] = SET_RANGE(command_get(GETCOMM(comm2)));
}
map->size += ins_cnt;
- map->changed = 1;
+ map->changed = True;
}
void key_add_range(struct map *map safe,
map->keys[pos+1] = strdup(last);
map->comms[pos+1] = command_get(comm);
map->size += move_size;
- map->changed = 1;
+ map->changed = True;
}
void key_add_chain(struct map *map safe, struct map *chain)
map->chain = chain;
}
-
#if 0
void key_del(struct map *map, wint_t k)
{
memmove(map->comms+pos, map->comms+pos+1,
(map->size-pos-1) * sizeof(struct command *));
map->size -= 1;
- map->changed = 1;
+ map->changed = True;
}
#endif
return NULL;
}
+/* FIXME this makes lots of things non re-entrant */
+static struct backtrace {
+ struct command *comm safe;
+ const struct cmd_info *ci safe;
+ struct backtrace *prev;
+} *backtrace;
+static int backtrace_depth;
+
+static char *mark_info(struct mark *m)
+{
+ char *ret = NULL;
+
+ if (!m) {
+ asprintf(&ret, "M-");
+ return ret;
+ }
+ if (!mark_valid(m)) {
+ asprintf(&ret, "M-FREED");
+ return ret;
+ }
+ ret = pane_call_ret(str, m->owner, "doc:debug:mark",
+ m->owner, 0, m);
+ if (ret)
+ return ret;
+
+ asprintf(&ret, "M:%d<%p>%d", m->seq, m, m->ref.i);
+ return ret;
+}
+
+void LOG_BT(void)
+{
+ struct backtrace *bt;
+ LOG("Start Backtrace:");
+ for (bt = backtrace; bt; bt = bt->prev) {
+ const struct cmd_info *ci = bt->ci;
+ struct command *h = ci->home->handle;
+ struct command *f = ci->focus->handle;
+ char *m1 = mark_info(ci->mark);
+ char *m2 = mark_info(ci->mark2);
+
+ LOG(" H:%s \"%s\" F:%s: %d %s \"%s\" %d %s \"%s\" (%d,%d) %s",
+ h ? h->name : "?",
+ ci->key,
+ f ? f->name : "?",
+ ci->num, m1, ci->str,
+ ci->num2, m2, ci->str2,
+ ci->x, ci->y,
+ ci->comm2 ? ci->comm2->name : "");
+ free(m1);
+ free(m2);
+ }
+ LOG("End Backtrace");
+}
+
+int do_comm_call(struct command *comm safe, const struct cmd_info *ci safe)
+{
+ struct backtrace bt;
+ int ret;
+
+ if (ci->home->damaged & DAMAGED_DEAD)
+ return Efail;
+ if (times_up_fast(ci->home))
+ return Efail;
+ if ((ci->home->damaged & DAMAGED_CLOSED) &&
+ !comm->closed_ok)
+ return Efallthrough;
+
+ if (backtrace_depth > 100) {
+ backtrace_depth = 0;
+ LOG("Recursion limit of 100 reached");
+ LOG_BT();
+ backtrace_depth = 100;
+ pane_root(ci->home)->timestamp = 1;
+ return Efail;
+ }
+ bt.comm = comm;
+ bt.ci = ci;
+ bt.prev = backtrace;
+ backtrace = &bt;
+ backtrace_depth += 1;
+ ret = comm->func(ci);
+ backtrace = bt.prev;
+ backtrace_depth -= 1;
+ return ret;
+}
+
int key_lookup(struct map *m safe, const struct cmd_info *ci safe)
{
struct command *comm;
if (comm->func == keymap_list_func)
((struct cmd_info*)ci)->comm = (struct command *safe)m;
- return comm->func(ci);
+ return do_comm_call(comm, ci);
}
}
-int key_lookup_prefix(struct map *m safe, const struct cmd_info *ci safe)
+int key_lookup_prefix(struct map *m safe, const struct cmd_info *ci safe,
+ bool simple)
{
- int pos = key_find(m, ci->key);
- struct command *comm, *prev = NULL;
- int len = strlen(ci->key);
+ /* A "Simple" lookup avoids the backtrace. It is used in
+ * signal handlers.
+ */
const char *k = ci->key;
- int ret = 0;
+ int len = strlen(k);
+ int pos = key_find(m, k);
+ struct command *comm, *prev = NULL;
+ int ret = Efallthrough;
+ int i;
- while (ret == 0 && pos < m->size &&
- strncmp(m->keys[pos], k, len) == 0) {
- comm = GETCOMM(m->comms[pos]);
+ for (i = 0;
+ ret == Efallthrough && pos+i < m->size &&
+ strncmp(m->keys[pos+i], k, len) == 0;
+ i++) {
+ comm = GETCOMM(m->comms[pos+i]);
if (comm && comm != prev) {
((struct cmd_info*)ci)->comm = comm;
- ((struct cmd_info*)ci)->key = m->keys[pos];
- ret = comm->func(ci);
- ASSERT(ret >= 0 || ret < Eunused);
+ ((struct cmd_info*)ci)->key = m->keys[pos+i];
+ if (simple)
+ ret = comm->func(ci);
+ else
+ ret = do_comm_call(comm, ci);
+ ASSERT(ret >= Efallthrough || ret < Eunused);
prev = comm;
+ /* something might have been added, recalc
+ * start pos.
+ */
+ pos = key_find(m, k);
}
- pos += 1;
}
((struct cmd_info*)ci)->key = k;
return ret;
struct pane *p;
unsigned int hash[2];
+ if (ci->mark && !mark_valid(ci->mark))
+ return Einval;
+ if (ci->mark2 && !mark_valid(ci->mark2))
+ return Einval;
+
+ if (times_up(ci->home))
+ return Efail;
time_start_key(ci->key);
if ((void*) ci->comm) {
- int ret = ci->comm->func(ci);
+ int ret = do_comm_call(ci->comm, ci);
time_stop_key(ci->key);
return ret;
}
p = ci->focus;
while (p) {
- int ret = 0;
- if (p->handle && !(p->damaged & DAMAGED_DEAD)) {
+ int ret = Efallthrough;
+ if (p->handle &&
+ (p->handle->closed_ok ||
+ !(p->damaged & DAMAGED_CLOSED))) {
vci->home = p;
vci->comm = p->handle;
+ /* Don't add this to the call stack as it
+ * should simply call the desired function and
+ * that will appear on the call stack.
+ */
ret = p->handle->func(ci);
}
- if (ret) {
+ if (ret != Efallthrough) {
time_stop_key(ci->key);
/* 'p' might have been destroyed */
return ret;
p = p->parent;
}
time_stop_key(ci->key);
- return 0;
+ return Efallthrough;
}