2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
14 #define PANE_DATA_TYPE struct ed_info
19 #define ED_MAGIC 0x4321fedcUL
21 static struct map *ed_map safe;
24 struct pane *freelist;
25 struct mark *mark_free_list;
27 struct lookup_cmd cmd;
28 /* These two paths contain nul-terminated strings,
29 * with a double-nul at the end.
42 #include "core-pane.h"
44 bool edlib_testing(struct pane *p safe)
46 struct ed_info *ei = pane_root(p)->data;
50 DEF_LOOKUP_CMD(ed_handle, ed_map);
52 DEF_CMD(global_set_attr)
58 attr_set_str(&ci->home->attrs, ci->str, ci->str2);
64 v = attr_find(ci->home->attrs, ci->str);
66 attr_set_str(&ci->home->attrs, ci->str, ci->str2);
69 v = strconcat(ci->home, v, ci->str2);
70 attr_set_str(&ci->home->attrs, ci->str, v);
74 DEF_CMD(global_set_command)
76 struct ed_info *ei = ci->home->data;
77 struct map *map = ei->map;
78 bool prefix = strcmp(ci->key, "global-set-command-prefix") == 0;
83 char *e = strconcat(NULL, ci->str, "\xFF\xFF\xFF\xFF");
84 key_add_range(map, ci->str, e, ci->comm2);
87 key_add_range(map, ci->str, ci->str2, ci->comm2);
89 key_add(map, ci->str, ci->comm2);
93 DEF_CMD(global_get_command)
95 struct ed_info *ei = ci->home->data;
96 struct map *map = ei->map;
100 !(cm = key_lookup_cmd(map, ci->str)))
102 return comm_call(ci->comm2, "callback:comm", ci->focus,
104 0, NULL, NULL, 0,0, cm);
107 DEF_CMD(global_config_dir)
109 const char *var = ci->str;
110 char *dir; // ci->str2;
111 char *key, *val = NULL;
112 struct pane *p = ci->home;
115 /* var might be different in different directories.
116 * Config setting are attributes stored on root that
117 * look like "config:var:dir".
118 * We find the best and return that with the dir
120 if (!var || !ci->str2 || !ci->comm2)
122 key = strconcat(p, "config:", var, ":", ci->str2);
123 dir = key + 7 + strlen(var) + 1;
124 end = dir + strlen(dir);
125 while (!val && end > dir) {
127 val = attr_find(p->attrs, key);
128 if (end[-1] == '/') {
129 while (end > dir && end[-1] == '/')
132 while (end > dir && end[-1] != '/')
138 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, val,
144 #include "O/mod-list-decl.h"
145 typedef void init_func(struct pane *ed);
146 static struct builtin {
150 #include "O/mod-list.h"
153 DEF_CMD(editor_load_module)
155 struct ed_info *ei = ci->home->data;
156 struct map *map = ei->map;
157 const char *name = ci->str;
161 void (*s)(struct pane *p);
165 sprintf(buf, "edlib-%s.so", name);
166 /* RTLD_GLOBAL is needed for python, else we get
167 * errors about _Py_ZeroStruct which a python script
171 h = dlopen(buf, RTLD_NOW | RTLD_GLOBAL);
173 path = dlsym(h, "edlib_module_path");
175 if (dlinfo(h, RTLD_DI_ORIGIN, pbuf) == 0)
178 s = dlsym(h, "edlib_init");
180 char *v = dlsym(h, "edlib_version");
181 LOG("Loading %s - version %s", name, v ?: "not provided");
188 char *err = dlerror();
189 if (strstr(err, "No such file or directory") == NULL)
190 LOG("dlopen %s failed %s", buf, err);
196 for (i = 0; buf[i]; i++)
199 for (i = 0; i < sizeof(builtins)/sizeof(builtins[0]); i++)
200 if (strcmp(builtins[i].name, buf) == 0) {
201 builtins[i].func(ci->home);
205 if (key_lookup_prefix(map, ci) > 0)
207 LOG("Failed to load module: %s", name);
211 DEF_CMD(editor_auto_event)
213 /* Event handlers register under a private name so we
214 * have to use key_lookup_prefix to find them.
215 * If nothing is found, autoload lib-libevent (hack?)
217 struct ed_info *ei = ci->home->data;
218 struct map *map = ei->map;
219 int ret = key_lookup_prefix(map, ci);
223 if (strcmp(ci->key, "event:refresh") == 0)
224 /* pointless to autoload for refresh */
226 call("attach-libevent", ci->home);
227 return key_lookup_prefix(map, ci);
230 DEF_CMD(editor_activate_display)
232 /* Given a display attached to the root, integrate it
233 * into a full initial stack of panes.
235 struct pane *disp = ci->focus;
237 char *ip = attr_find(ci->home->attrs, "editor-initial-panes");
242 ip = strsave(ci->home, ip);
243 p = pane_root(ci->focus);
245 p2 = call_ret(pane, "attach-window-core", p);
250 for (t = strtok_r(ip, " \t\n", &save);
252 t = strtok_r(NULL, " \t\n", &save)) {
255 if (strcmp(t, "DISPLAY") == 0) {
257 pane_reparent(disp, p);
263 m = strconcat(NULL, "attach-", t);
264 p2 = call_ret(pane, m, p);
269 comm_call(ci->comm2, "cb", p);
273 DEF_CMD(editor_multicall)
275 struct ed_info *ei = ci->home->data;
276 struct map *map = ei->map;
278 const char *key = ci->key;
280 ((struct cmd_info*)ci)->key = ksuffix(ci, "global-multicall-");
281 ret = key_lookup_prefix(map, ci);
282 ((struct cmd_info*)ci)->key = key;
286 DEF_CMD(editor_request_notify)
288 pane_add_notify(ci->focus, ci->home, ksuffix(ci, "editor:request:"));
292 DEF_CMD(editor_send_notify)
294 /* editor:notify:... */
295 return home_pane_notify(ci->home, ksuffix(ci, "editor:notify:"),
297 ci->num, ci->mark, ci->str,
298 ci->num2, ci->mark2, ci->str2, ci->comm2);
301 DEF_CMD(editor_free_panes)
303 struct ed_info *ei = ci->home->data;
305 while (ei->freelist) {
306 struct pane *p = ei->freelist;
307 ei->freelist = p->focus;
310 command_put(p->handle);
312 attr_free(&p->attrs);
318 DEF_CMD(editor_free_marks)
320 struct ed_info *ei = ci->home->data;
322 while (ei->mark_free_list) {
323 struct mark *m = ei->mark_free_list;
324 ei->mark_free_list = (struct mark*)m->all.next;
331 DEF_CMD(editor_free_store)
333 struct ed_info *ei = ci->home->data;
336 struct store *s = ei->store;
343 /* FIXME I should be able to remove things from a keymap, not
346 DEF_EXTERN_CMD(edlib_noop)
351 DEF_CMD(editor_close)
353 struct ed_info *ei = ci->home->data;
355 free(ei->here); ei->here = NULL;
356 free(ei->data_path); ei->data_path = NULL;
357 free(ei->config_path); ei->config_path = NULL;
361 void * safe memsave(struct pane *p safe, const char *buf, int len)
367 ASSERT(ei->magic==ED_MAGIC);
369 call_comm("event:on-idle", p, &editor_free_store, 2);
370 if (ei->store == NULL || ei->store->size < len) {
372 int l = 4096 - sizeof(*s);
375 s = malloc(l + sizeof(*s));
380 ei->store->size -= len;
382 return memcpy(ei->store->space+ei->store->size, buf, len);
384 return ei->store->space+ei->store->size;
387 char *strsave(struct pane *p safe, const char *buf)
391 return memsave(p, buf, strlen(buf)+1);
394 char *strnsave(struct pane *p safe, const char *buf, int len)
399 s = memsave(p, buf, len+1);
405 char * safe do_strconcat(struct pane *p, const char *s1 safe, ...)
414 while ((s = va_arg(ap, char*)) != NULL)
419 ret = memsave(p, NULL, len+1);
424 while ((s = va_arg(ap, char*)) != NULL)
430 void editor_delayed_free(struct pane *ed safe, struct pane *p safe)
432 struct ed_info *ei = ed->data;
434 command_put(p->handle);
436 attr_free(&p->attrs);
440 ASSERT(ei->magic==ED_MAGIC);
442 call_comm("event:on-idle", ed, &editor_free_panes, 2);
443 p->focus = ei->freelist;
447 void editor_delayed_mark_free(struct mark *m safe)
449 struct pane *ed = pane_root(m->owner);
450 struct ed_info *ei = ed->data;
452 ASSERT(ei->magic==ED_MAGIC);
453 if (!ei->mark_free_list)
454 call_comm("event:on-idle", ed, &editor_free_marks, 2);
455 m->all.next = (void*)ei->mark_free_list;
456 ei->mark_free_list = m;
459 static char *set_here(struct pane *p safe)
461 struct ed_info *ei = p->data;
466 else if (dladdr(&set_here, &info) == 0)
467 ei->here = strdup("");
470 ei->here = strdup(info.dli_fname ?: "");
471 sl = strrchr(ei->here, '/');
478 static char *set_data_path(struct pane *p safe)
480 struct ed_info *ei = p->data;
481 char *dh, *dd, *here;
485 return ei->data_path;
488 dh = getenv("XDG_DATA_HOME");
490 char *h = getenv("HOME");
492 dh = strconcat(p, h, "/.local/share");
494 if (dh && *dh == '/') {
496 buf_concat(&b, "/edlib/");
497 buf_append_byte(&b, 0);
501 if (here && *here == '/') {
502 buf_concat(&b, here);
503 buf_concat(&b, "/edlib/");
504 buf_append_byte(&b, 0);
507 dd = getenv("XDG_DATA_DIRS");
509 dd = "/usr/local/share:/usr/share";
511 char *c = strchrnul(dd, ':');
513 buf_concat_len(&b, dd, c-dd);
514 buf_concat(&b, "/edlib/");
515 buf_append_byte(&b, 0);
522 ei->data_path = buf_final(&b);
525 return ei->data_path;
528 static char *set_config_path(struct pane *p safe)
530 struct ed_info *ei = p->data;
531 char *ch, *cd, *here;
535 return ei->config_path;
538 ch = getenv("XDG_CONFIG_HOME");
540 char *h = getenv("HOME");
542 ch = strconcat(p, h, "/.config");
544 if (ch && *ch == '/') {
546 buf_concat(&b, "/edlib/");
547 buf_append_byte(&b, 0);
551 if (here && *here == '/') {
552 buf_concat(&b, here);
553 buf_concat(&b, "/edlib/");
554 buf_append_byte(&b, 0);
557 cd = getenv("XDG_CONFIG_DIRS");
561 char *c = strchrnul(cd, ':');
563 buf_concat_len(&b, cd, c-cd);
564 buf_concat(&b, "/edlib/");
565 buf_append_byte(&b, 0);
572 ei->config_path = buf_final(&b);
575 return ei->config_path;
578 static char *set_bin_path(struct pane *p safe)
580 struct ed_info *ei = p->data;
589 if (here && *here == '/') {
590 buf_concat(&b, here);
592 strncmp(b.b + b.len-4, "/lib", 4) == 0)
595 buf_concat(&b, "/../");
596 buf_concat(&b, "bin/");
597 buf_append_byte(&b, 0);
601 bd = "/usr/bin:/usr/local/bin";
603 char *c = strchrnul(bd, ':');
605 buf_concat_len(&b, bd, c-bd);
606 buf_append_byte(&b, 0);
613 ei->bin_path = buf_final(&b);
619 DEF_CMD(global_find_file)
622 * ->str is a file basename. If it contains {COMM}, that will
623 * be replaced with the "command-name" attr from root, or
624 * "edlib" if nothing can be found.
625 * ->str2 is one of "data", "config", "bin"
626 * We find a file with basename in a known location following
627 * the XDG Base Directory Specificaton.
628 * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
629 * but also look in the directory containing this library $HERE
630 * For "data" we look in a directory "edlib" under:
631 * - $XDG_DATA_HOME, or $HOME/.local/share
633 * - $XDG_DATA_DIRS, or /usr/local/share:/usr/share
635 * For config we look in a "edlib" under:
636 * - $XDG_CONFIG_HOME, or $HOME/.config
638 * - $XDG_CONFIG_DIRS, or /etc/xdg
640 * For bin we look in $HERE/../bin and $PATH
643 const char *base[2] = {ci->str, NULL};
647 if (base[0] == NULL || ci->str2 == NULL)
649 if (strcmp(ci->str2, "data") == 0)
650 path = set_data_path(ci->home);
651 else if (strcmp(ci->str2, "config") == 0)
652 path = set_config_path(ci->home);
653 else if (strcmp(ci->str2, "bin") == 0)
654 path = set_bin_path(ci->home);
658 cn = strstr(base[0], "{COMM}");
660 char *p = strndup(base[0], cn - base[0]);
661 char *comm = attr_find(ci->home->attrs, "command-name");
664 base[0] = strconcat(ci->home, p, comm, cn+6);
665 if (strcmp(comm, "edlib") != 0)
666 base[1] = strconcat(ci->home, p, "edlib", cn+6);
668 for (i = 0; i < 2 && base[i] ; i++) {
670 for (pth = path; pth && *pth; pth += strlen(pth)+1) {
671 char *p = strconcat(NULL, pth, base[i]);
675 fd = open(p, O_RDONLY);
681 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, p);
689 struct pane *editor_new(const char *comm_name)
694 if (! (void*) ed_map) {
695 ed_map = key_alloc();
696 key_add(ed_map, "global-set-attr", &global_set_attr);
697 key_add(ed_map, "global-set-command", &global_set_command);
698 key_add(ed_map, "global-set-command-prefix", &global_set_command);
699 key_add(ed_map, "global-get-command", &global_get_command);
700 key_add(ed_map, "global-load-module", &editor_load_module);
701 key_add(ed_map, "global-config-dir", &global_config_dir);
702 key_add(ed_map, "xdg-find-edlib-file", &global_find_file);
703 key_add_prefix(ed_map, "event:", &editor_auto_event);
704 key_add_prefix(ed_map, "global-multicall-", &editor_multicall);
705 key_add_prefix(ed_map, "editor:request:",
706 &editor_request_notify);
707 key_add_prefix(ed_map, "editor:notify:",
708 &editor_send_notify);
709 key_add(ed_map, "editor:activate-display",
710 &editor_activate_display);
711 key_add(ed_map, "Close", &editor_close);
713 ed = pane_register_root(&ed_handle.c, NULL, sizeof(*ei));
717 ei->magic = ED_MAGIC;
718 attr_set_str(&ed->attrs, "command-name", comm_name ?: "edlib");
719 ei->testing = (getenv("EDLIB_TESTING") != NULL);
720 ei->map = key_alloc();
721 key_add_chain(ei->map, ed_map);
723 ei->cmd.m = &ei->map;
724 /* This allows the pane to see registered commands */
725 pane_update_handle(ed, &ei->cmd.c);