2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
15 #define PANE_DATA_TYPE struct ed_info
20 #define ED_MAGIC 0x4321fedcUL
22 static struct map *ed_map safe;
25 struct pane *freelist;
26 struct mark *mark_free_list;
28 struct lookup_cmd cmd;
29 /* These two paths contain nul-terminated strings,
30 * with a double-nul at the end.
43 #include "core-pane.h"
45 bool edlib_testing(struct pane *p safe)
47 struct ed_info *ei = pane_root(p)->data;
51 DEF_LOOKUP_CMD(ed_handle, ed_map);
53 DEF_CMD(global_set_attr)
59 attr_set_str(&ci->home->attrs, ci->str, ci->str2);
65 v = attr_find(ci->home->attrs, ci->str);
67 attr_set_str(&ci->home->attrs, ci->str, ci->str2);
70 v = strconcat(ci->home, v, ci->str2);
71 attr_set_str(&ci->home->attrs, ci->str, v);
75 DEF_CMD(global_set_command)
77 struct ed_info *ei = ci->home->data;
78 struct map *map = ei->map;
79 bool prefix = strcmp(ci->key, "global-set-command-prefix") == 0;
84 char *e = strconcat(NULL, ci->str, "\xFF\xFF\xFF\xFF");
85 key_add_range(map, ci->str, e, ci->comm2);
88 key_add_range(map, ci->str, ci->str2, ci->comm2);
90 key_add(map, ci->str, ci->comm2);
94 DEF_CMD(global_get_command)
96 struct ed_info *ei = ci->home->data;
97 struct map *map = ei->map;
101 !(cm = key_lookup_cmd(map, ci->str)))
103 return comm_call(ci->comm2, "callback:comm", ci->focus,
105 0, NULL, NULL, 0,0, cm);
108 DEF_CMD(global_config_dir)
110 const char *var = ci->str;
111 char *dir; // ci->str2;
112 char *key, *val = NULL;
113 struct pane *p = ci->home;
116 /* var might be different in different directories.
117 * Config setting are attributes stored on root that
118 * look like "config:var:dir".
119 * We find the best and return that with the dir
121 if (!var || !ci->str2 || !ci->comm2)
123 key = strconcat(p, "config:", var, ":", ci->str2);
124 dir = key + 7 + strlen(var) + 1;
125 end = dir + strlen(dir);
126 while (!val && end > dir) {
128 val = attr_find(p->attrs, key);
129 if (end[-1] == '/') {
130 while (end > dir && end[-1] == '/')
133 while (end > dir && end[-1] != '/')
139 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, val,
145 #include "O/mod-list-decl.h"
146 typedef void init_func(struct pane *ed);
147 static struct builtin {
151 #include "O/mod-list.h"
154 DEF_CMD(editor_load_module)
156 struct ed_info *ei = ci->home->data;
157 struct map *map = ei->map;
158 const char *name = ci->str;
162 void (*s)(struct pane *p);
166 sprintf(buf, "edlib-%s.so", name);
167 /* RTLD_GLOBAL is needed for python, else we get
168 * errors about _Py_ZeroStruct which a python script
172 h = dlopen(buf, RTLD_NOW | RTLD_GLOBAL);
174 path = dlsym(h, "edlib_module_path");
176 if (dlinfo(h, RTLD_DI_ORIGIN, pbuf) == 0)
179 s = dlsym(h, "edlib_init");
181 char *v = dlsym(h, "edlib_version");
182 LOG("Loading %s - version %s", name, v ?: "not provided");
189 char *err = dlerror();
190 if (strstr(err, "No such file or directory") == NULL)
191 LOG("dlopen %s failed %s", buf, err);
197 for (i = 0; buf[i]; i++)
200 for (i = 0; i < sizeof(builtins)/sizeof(builtins[0]); i++)
201 if (strcmp(builtins[i].name, buf) == 0) {
202 builtins[i].func(ci->home);
206 /* Multiple modules can support module lookup, such as
207 * global-load-modules:python or global-load-module:lua.
208 * So do a prefix lookup.
210 if (key_lookup_prefix(map, ci, False) > 0)
212 LOG("Failed to load module: %s", name);
216 DEF_CMD(editor_auto_event)
218 /* Event handlers register under a private name so we
219 * have to use key_lookup_prefix to find them.
220 * If nothing is found, autoload lib-libevent (hack?)
222 struct ed_info *ei = ci->home->data;
223 struct map *map = ei->map;
224 int ret = key_lookup_prefix(map, ci, False);
228 if (strcmp(ci->key, "event:refresh") == 0)
229 /* pointless to autoload for refresh */
231 call("attach-libevent", ci->home);
232 return key_lookup_prefix(map, ci, False);
235 DEF_CMD(editor_multicall)
237 struct ed_info *ei = ci->home->data;
238 struct map *map = ei->map;
240 const char *key = ci->key;
242 ((struct cmd_info*)ci)->key = ksuffix(ci, "global-multicall-");
243 ret = key_lookup_prefix(map, ci, False);
244 ((struct cmd_info*)ci)->key = key;
248 DEF_CMD(editor_request_notify)
250 pane_add_notify(ci->focus, ci->home, ksuffix(ci, "editor:request:"));
254 DEF_CMD(editor_send_notify)
256 /* editor:notify:... */
257 return home_pane_notify(ci->home, ksuffix(ci, "editor:notify:"),
259 ci->num, ci->mark, ci->str,
260 ci->num2, ci->mark2, ci->str2, ci->comm2);
263 DEF_CMD(editor_free_panes)
265 struct ed_info *ei = ci->home->data;
267 while (ei->freelist) {
268 struct pane *p = ei->freelist;
269 ei->freelist = p->focus;
272 command_put(p->handle);
274 attr_free(&p->attrs);
280 DEF_CMD(editor_free_marks)
282 struct ed_info *ei = ci->home->data;
284 while (ei->mark_free_list) {
285 struct mark *m = ei->mark_free_list;
286 ei->mark_free_list = (struct mark*)m->all.next;
293 DEF_CMD(editor_free_store)
295 struct ed_info *ei = ci->home->data;
298 struct store *s = ei->store;
305 /* FIXME I should be able to remove things from a keymap, not
308 DEF_EXTERN_CMD(edlib_noop)
313 DEF_CMD_CLOSED(editor_close)
315 struct ed_info *ei = ci->home->data;
317 free(ei->here); ei->here = NULL;
318 free(ei->data_path); ei->data_path = NULL;
319 free(ei->config_path); ei->config_path = NULL;
323 void * safe memsave(struct pane *p safe, const char *buf, int len)
329 ASSERT(ei->magic==ED_MAGIC);
331 call_comm("event:on-idle", p, &editor_free_store, 2);
332 if (ei->store == NULL || ei->store->size < len) {
334 int l = 4096 - sizeof(*s);
337 s = malloc(l + sizeof(*s));
342 ei->store->size -= len;
344 return memcpy(ei->store->space+ei->store->size, buf, len);
346 return ei->store->space+ei->store->size;
349 char *strsave(struct pane *p safe, const char *buf)
353 return memsave(p, buf, strlen(buf)+1);
356 char *strnsave(struct pane *p safe, const char *buf, int len)
361 s = memsave(p, buf, len+1);
367 char * safe do_strconcat(struct pane *p, const char *s1 safe, ...)
376 while ((s = va_arg(ap, char*)) != NULL)
381 ret = memsave(p, NULL, len+1);
386 while ((s = va_arg(ap, char*)) != NULL)
392 void editor_delayed_free(struct pane *ed safe, struct pane *p safe)
394 struct ed_info *ei = ed->data;
396 command_put(p->handle);
398 attr_free(&p->attrs);
402 ASSERT(ei->magic==ED_MAGIC);
404 call_comm("event:on-idle", ed, &editor_free_panes, 2);
405 p->focus = ei->freelist;
409 void editor_delayed_mark_free(struct mark *m safe)
411 struct pane *ed = pane_root(m->owner);
412 struct ed_info *ei = ed->data;
414 ASSERT(ei->magic==ED_MAGIC);
415 if (!ei->mark_free_list)
416 call_comm("event:on-idle", ed, &editor_free_marks, 2);
417 m->all.next = (void*)ei->mark_free_list;
418 ei->mark_free_list = m;
421 static char *set_here(struct pane *p safe)
423 struct ed_info *ei = p->data;
428 else if (dladdr(&set_here, &info) == 0)
429 ei->here = strdup("");
432 ei->here = strdup(info.dli_fname ?: "");
433 sl = strrchr(ei->here, '/');
440 static char *set_data_path(struct pane *p safe)
442 struct ed_info *ei = p->data;
443 char *dh, *dd, *here;
447 return ei->data_path;
450 dh = getenv("XDG_DATA_HOME");
452 char *h = getenv("HOME");
454 dh = strconcat(p, h, "/.local/share");
456 if (dh && *dh == '/') {
458 buf_concat(&b, "/edlib/");
459 buf_append_byte(&b, 0);
463 if (here && *here == '/') {
464 buf_concat(&b, here);
465 buf_concat(&b, "/edlib/");
466 buf_append_byte(&b, 0);
469 dd = getenv("XDG_DATA_DIRS");
471 dd = "/usr/local/share:/usr/share";
473 char *c = strchrnul(dd, ':');
475 buf_concat_len(&b, dd, c-dd);
476 buf_concat(&b, "/edlib/");
477 buf_append_byte(&b, 0);
484 ei->data_path = buf_final(&b);
487 return ei->data_path;
490 static char *set_config_path(struct pane *p safe)
492 struct ed_info *ei = p->data;
493 char *ch, *cd, *here;
497 return ei->config_path;
500 ch = getenv("XDG_CONFIG_HOME");
502 char *h = getenv("HOME");
504 ch = strconcat(p, h, "/.config");
506 if (ch && *ch == '/') {
508 buf_concat(&b, "/edlib/");
509 buf_append_byte(&b, 0);
513 if (here && *here == '/') {
514 buf_concat(&b, here);
515 buf_concat(&b, "/edlib/");
516 buf_append_byte(&b, 0);
519 cd = getenv("XDG_CONFIG_DIRS");
523 char *c = strchrnul(cd, ':');
525 buf_concat_len(&b, cd, c-cd);
526 buf_concat(&b, "/edlib/");
527 buf_append_byte(&b, 0);
534 ei->config_path = buf_final(&b);
537 return ei->config_path;
540 static char *set_bin_path(struct pane *p safe)
542 struct ed_info *ei = p->data;
551 if (here && *here == '/') {
552 buf_concat(&b, here);
554 strncmp(b.b + b.len-4, "/lib", 4) == 0)
557 buf_concat(&b, "/../");
558 buf_concat(&b, "bin/");
559 buf_append_byte(&b, 0);
563 bd = "/usr/bin:/usr/local/bin";
565 char *c = strchrnul(bd, ':');
567 buf_concat_len(&b, bd, c-bd);
568 buf_append_byte(&b, 0);
575 ei->bin_path = buf_final(&b);
581 DEF_CMD(global_find_file)
584 * ->str is a file basename. If it contains {COMM}, that will
585 * be replaced with the "command-name" attr from root, or
586 * "edlib" if nothing can be found.
587 * ->str2 is one of "data", "config", "bin"
588 * We find a file with basename in a known location following
589 * the XDG Base Directory Specificaton.
590 * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
591 * but also look in the directory containing this library $HERE
592 * For "data" we look in a directory "edlib" under:
593 * - $XDG_DATA_HOME, or $HOME/.local/share
595 * - $XDG_DATA_DIRS, or /usr/local/share:/usr/share
597 * For config we look in a "edlib" under:
598 * - $XDG_CONFIG_HOME, or $HOME/.config
600 * - $XDG_CONFIG_DIRS, or /etc/xdg
602 * For bin we look in $HERE/../bin and $PATH
605 const char *base[2] = {ci->str, NULL};
609 if (base[0] == NULL || ci->str2 == NULL)
611 if (strcmp(ci->str2, "data") == 0)
612 path = set_data_path(ci->home);
613 else if (strcmp(ci->str2, "config") == 0)
614 path = set_config_path(ci->home);
615 else if (strcmp(ci->str2, "bin") == 0)
616 path = set_bin_path(ci->home);
620 cn = strstr(base[0], "{COMM}");
622 char *p = strndup(base[0], cn - base[0]);
623 char *comm = attr_find(ci->home->attrs, "command-name");
626 base[0] = strconcat(ci->home, p, comm, cn+6);
627 if (strcmp(comm, "edlib") != 0)
628 base[1] = strconcat(ci->home, p, "edlib", cn+6);
630 for (i = 0; i < 2 && base[i] ; i++) {
632 for (pth = path; pth && *pth; pth += strlen(pth)+1) {
633 char *p = strconcat(NULL, pth, base[i]);
637 fd = open(p, O_RDONLY);
643 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, p);
651 static struct pane *editor;
652 static void catch(int sig)
662 ci.str = ci.str2 = NULL;
663 ci.mark = ci.mark2 = NULL;
667 ci.key = "fast-alarm-";
668 key_lookup_prefix(editor->data->map, &ci, True);
671 ci.key = "fast-interrupt-";
672 key_lookup_prefix(editor->data->map, &ci, True);
677 struct pane *editor_new(const char *comm_name)
682 if (! (void*) ed_map) {
683 ed_map = key_alloc();
684 key_add(ed_map, "global-set-attr", &global_set_attr);
685 key_add(ed_map, "global-set-command", &global_set_command);
686 key_add(ed_map, "global-set-command-prefix", &global_set_command);
687 key_add(ed_map, "global-get-command", &global_get_command);
688 key_add(ed_map, "global-load-module", &editor_load_module);
689 key_add(ed_map, "global-config-dir", &global_config_dir);
690 key_add(ed_map, "xdg-find-edlib-file", &global_find_file);
691 key_add_prefix(ed_map, "event:", &editor_auto_event);
692 key_add_prefix(ed_map, "global-multicall-", &editor_multicall);
693 key_add_prefix(ed_map, "editor:request:",
694 &editor_request_notify);
695 key_add_prefix(ed_map, "editor:notify:",
696 &editor_send_notify);
697 key_add(ed_map, "Close", &editor_close);
699 ed = pane_register_root(&ed_handle.c, NULL, sizeof(*ei));
703 ei->magic = ED_MAGIC;
704 attr_set_str(&ed->attrs, "command-name", comm_name ?: "edlib");
705 ei->testing = (getenv("EDLIB_TESTING") != NULL);
706 ei->map = key_alloc();
707 key_add_chain(ei->map, ed_map);
709 ei->cmd.m = &ei->map;
710 /* This allows the pane to see registered commands */
711 pane_update_handle(ed, &ei->cmd.c);
717 signal(SIGINT, catch);
718 signal(SIGALRM, catch);