]> git.neil.brown.name Git - edlib.git/blob - core-editor.c
TODO: clean out done items.
[edlib.git] / core-editor.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  */
5 #define _GNU_SOURCE
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include <signal.h>
11 #include <fcntl.h>
12 #include <stdarg.h>
13 #include <dlfcn.h>
14
15 #define PANE_DATA_TYPE struct ed_info
16 #include "core.h"
17 #include "misc.h"
18 #include "internal.h"
19
20 #define ED_MAGIC 0x4321fedcUL
21
22 static struct map *ed_map safe;
23 struct ed_info {
24         unsigned long magic;
25         struct pane *freelist;
26         struct mark *mark_free_list;
27         struct map *map safe;
28         struct lookup_cmd cmd;
29         /* These two paths contain nul-terminated strings,
30          * with a double-nul at the end.
31          */
32         char *data_path;
33         char *config_path;
34         char *bin_path;
35         char *here;
36         bool testing;
37         struct store {
38                 struct store *next;
39                 int size;
40                 char space[];
41         } *store;
42 };
43 #include "core-pane.h"
44
45 bool edlib_testing(struct pane *p safe)
46 {
47         struct ed_info *ei = pane_root(p)->data;
48         return ei->testing;
49 }
50
51 DEF_LOOKUP_CMD(ed_handle, ed_map);
52
53 DEF_CMD(global_set_attr)
54 {
55         char *v;
56         if (!ci->str)
57                 return Enoarg;
58         if (!ci->num) {
59                 attr_set_str(&ci->home->attrs, ci->str, ci->str2);
60                 return 1;
61         }
62         /* Append */
63         if (!ci->str2)
64                 return 1;
65         v = attr_find(ci->home->attrs, ci->str);
66         if (!v) {
67                 attr_set_str(&ci->home->attrs, ci->str, ci->str2);
68                 return 1;
69         }
70         v = strconcat(ci->home, v, ci->str2);
71         attr_set_str(&ci->home->attrs, ci->str, v);
72         return 1;
73 }
74
75 DEF_CMD(global_set_command)
76 {
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;
80
81         if (!ci->str)
82                 return Enoarg;
83         if (prefix) {
84                 char *e = strconcat(NULL, ci->str, "\xFF\xFF\xFF\xFF");
85                 key_add_range(map, ci->str, e, ci->comm2);
86                 free(e);
87         } else if (ci->str2)
88                 key_add_range(map, ci->str, ci->str2, ci->comm2);
89         else
90                 key_add(map, ci->str, ci->comm2);
91         return 1;
92 }
93
94 DEF_CMD(global_get_command)
95 {
96         struct ed_info *ei = ci->home->data;
97         struct map *map = ei->map;
98         struct command *cm;
99
100         if (!ci->str ||
101             !(cm = key_lookup_cmd(map, ci->str)))
102                 return Efail;
103         return comm_call(ci->comm2, "callback:comm", ci->focus,
104                          0, NULL, ci->str,
105                          0, NULL, NULL, 0,0, cm);
106 }
107
108 DEF_CMD(global_config_dir)
109 {
110         const char *var = ci->str;
111         char *dir; // ci->str2;
112         char *key, *val = NULL;
113         struct pane *p = ci->home;
114         char *end;
115
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
120          */
121         if (!var || !ci->str2 || !ci->comm2)
122                 return Enoarg;
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) {
127                 end[0] = 0;
128                 val = attr_find(p->attrs, key);
129                 if (end[-1] == '/') {
130                         while (end > dir && end[-1] == '/')
131                                 end -= 1;
132                 } else {
133                         while (end > dir && end[-1] != '/')
134                                 end -= 1;
135                 }
136         }
137         if (!val)
138                 return Efalse;
139         comm_call(ci->comm2, "cb", ci->focus, 0, NULL, val,
140                           0, NULL, dir);
141         return 1;
142 }
143
144 #ifdef edlib_init
145 #include "O/mod-list-decl.h"
146 typedef void init_func(struct pane *ed);
147 static struct builtin {
148         char *name;
149         init_func *func;
150 } builtins[]={
151         #include "O/mod-list.h"
152 };
153 #endif
154 DEF_CMD(editor_load_module)
155 {
156         struct ed_info *ei = ci->home->data;
157         struct map *map = ei->map;
158         const char *name = ci->str;
159         char buf[PATH_MAX];
160 #ifndef edlib_init
161         void *h;
162         void (*s)(struct pane *p);
163         char **path;
164         char pbuf[PATH_MAX];
165
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
169          * tries "import gtk"
170          *
171          */
172         h = dlopen(buf, RTLD_NOW | RTLD_GLOBAL);
173         if (h) {
174                 path = dlsym(h, "edlib_module_path");
175                 if (path) {
176                         if (dlinfo(h, RTLD_DI_ORIGIN, pbuf) == 0)
177                                 *path = pbuf;
178                 }
179                 s = dlsym(h, "edlib_init");
180                 if (s) {
181                         char *v = dlsym(h, "edlib_version");
182                         LOG("Loading %s - version %s", name, v ?: "not provided");
183                         s(ci->home);
184                         if (path)
185                                 *path = NULL;
186                         return 1;
187                 }
188         } else {
189                 char *err = dlerror();
190                 if (strstr(err, "No such file or directory") == NULL)
191                         LOG("dlopen %s failed %s", buf, err);
192         }
193 #else
194         unsigned int i;
195
196         strcpy(buf, name);
197         for (i = 0; buf[i]; i++)
198                 if (buf[i] == '-')
199                         buf[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);
203                         return 1;
204                 }
205 #endif
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.
209          */
210         if (key_lookup_prefix(map, ci, False) > 0)
211                 return 1;
212         LOG("Failed to load module: %s", name);
213         return Efail;
214 }
215
216 DEF_CMD(editor_auto_event)
217 {
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?)
221          */
222         struct ed_info *ei = ci->home->data;
223         struct map *map = ei->map;
224         int ret = key_lookup_prefix(map, ci, False);
225
226         if (ret)
227                 return ret;
228         if (strcmp(ci->key, "event:refresh") == 0)
229                 /* pointless to autoload for refresh */
230                 return Efallthrough;
231         call("attach-libevent", ci->home);
232         return key_lookup_prefix(map, ci, False);
233 }
234
235 DEF_CMD(editor_multicall)
236 {
237         struct ed_info *ei = ci->home->data;
238         struct map *map = ei->map;
239         int ret;
240         const char *key = ci->key;
241
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;
245         return ret;
246 }
247
248 DEF_CMD(editor_request_notify)
249 {
250         pane_add_notify(ci->focus, ci->home, ksuffix(ci, "editor:request:"));
251         return 1;
252 }
253
254 DEF_CMD(editor_send_notify)
255 {
256         /* editor:notify:... */
257         return home_pane_notify(ci->home, ksuffix(ci, "editor:notify:"),
258                                 ci->focus,
259                                 ci->num, ci->mark, ci->str,
260                                 ci->num2, ci->mark2, ci->str2, ci->comm2);
261 }
262
263 DEF_CMD(editor_free_panes)
264 {
265         struct ed_info *ei = ci->home->data;
266
267         while (ei->freelist) {
268                 struct pane *p = ei->freelist;
269                 ei->freelist = p->focus;
270                 p->focus = NULL;
271
272                 command_put(p->handle);
273                 p->handle = NULL;
274                 attr_free(&p->attrs);
275                 pane_put(p);
276         }
277         return 1;
278 }
279
280 DEF_CMD(editor_free_marks)
281 {
282         struct ed_info *ei = ci->home->data;
283
284         while (ei->mark_free_list) {
285                 struct mark *m = ei->mark_free_list;
286                 ei->mark_free_list = (struct mark*)m->all.next;
287                 do_mark_free(m);
288         }
289
290         return 1;
291 }
292
293 DEF_CMD(editor_free_store)
294 {
295         struct ed_info *ei = ci->home->data;
296
297         while (ei->store) {
298                 struct store *s = ei->store;
299                 ei->store = s->next;
300                 free(s);
301         }
302         return 1;
303 }
304
305 /* FIXME I should be able to remove things from a keymap, not
306  * replace with this.
307  */
308 DEF_EXTERN_CMD(edlib_noop)
309 {
310         return Efallthrough;
311 }
312
313 DEF_CMD_CLOSED(editor_close)
314 {
315         struct ed_info *ei = ci->home->data;
316         stat_free();
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;
320         return Efallthrough;
321 }
322
323 void * safe memsave(struct pane *p safe, const char *buf, int len)
324 {
325         struct ed_info *ei;
326
327         p = pane_root(p);
328         ei = p->data;
329         ASSERT(ei->magic==ED_MAGIC);
330         if (!ei->store)
331                 call_comm("event:on-idle", p, &editor_free_store, 2);
332         if (ei->store == NULL || ei->store->size < len) {
333                 struct store *s;
334                 int l = 4096 - sizeof(*s);
335                 while (l < len)
336                         l += 4096;
337                 s = malloc(l + sizeof(*s));
338                 s->next = ei->store;
339                 s->size = l;
340                 ei->store = s;
341         }
342         ei->store->size -= len;
343         if (buf)
344                 return memcpy(ei->store->space+ei->store->size, buf, len);
345         else
346                 return ei->store->space+ei->store->size;
347 }
348
349 char *strsave(struct pane *p safe, const char *buf)
350 {
351         if (!buf)
352                 return NULL;
353         return memsave(p, buf, strlen(buf)+1);
354 }
355
356 char *strnsave(struct pane *p safe, const char *buf, int len)
357 {
358         char *s;
359         if (!buf)
360                 return NULL;
361         s = memsave(p, buf, len+1);
362         if (s)
363                 s[len] = 0;
364         return s;
365 }
366
367 char * safe do_strconcat(struct pane *p, const char *s1 safe, ...)
368 {
369         va_list ap;
370         char *s;
371         int len = 0;
372         char *ret;
373
374         len = strlen(s1);
375         va_start(ap, s1);
376         while ((s = va_arg(ap, char*)) != NULL)
377                 len += strlen(s);
378         va_end(ap);
379
380         if (p)
381                 ret = memsave(p, NULL, len+1);
382         else
383                 ret = malloc(len+1);
384         strcpy(ret, s1);
385         va_start(ap, s1);
386         while ((s = va_arg(ap, char*)) != NULL)
387                 strcat(ret, s);
388         va_end(ap);
389         return ret;
390 }
391
392 void editor_delayed_free(struct pane *ed safe, struct pane *p safe)
393 {
394         struct ed_info *ei = ed->data;
395         if (!ei) {
396                 command_put(p->handle);
397                 p->handle = NULL;
398                 attr_free(&p->attrs);
399                 pane_free(p);
400                 return;
401         }
402         ASSERT(ei->magic==ED_MAGIC);
403         if (!ei->freelist)
404                 call_comm("event:on-idle", ed, &editor_free_panes, 2);
405         p->focus = ei->freelist;
406         ei->freelist = p;
407 }
408
409 void editor_delayed_mark_free(struct mark *m safe)
410 {
411         struct pane *ed = pane_root(m->owner);
412         struct ed_info *ei = ed->data;
413
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;
419 }
420
421 static char *set_here(struct pane *p safe)
422 {
423         struct ed_info *ei = p->data;
424         Dl_info info;
425
426         if (ei->here)
427                 ;
428         else if (dladdr(&set_here, &info) == 0)
429                 ei->here = strdup("");
430         else {
431                 char *sl;
432                 ei->here = strdup(info.dli_fname ?: "");
433                 sl = strrchr(ei->here, '/');
434                 if (sl)
435                         *sl = 0;
436         }
437         return ei->here;
438 }
439
440 static char *set_data_path(struct pane *p safe)
441 {
442         struct ed_info *ei = p->data;
443         char *dh, *dd, *here;
444         struct buf b;
445
446         if (ei->data_path)
447                 return ei->data_path;
448
449         buf_init(&b);
450         dh = getenv("XDG_DATA_HOME");
451         if (!dh) {
452                 char *h = getenv("HOME");
453                 if (h)
454                         dh = strconcat(p, h, "/.local/share");
455         }
456         if (dh && *dh == '/') {
457                 buf_concat(&b, dh);
458                 buf_concat(&b, "/edlib/");
459                 buf_append_byte(&b, 0);
460         }
461
462         here = set_here(p);
463         if (here && *here == '/') {
464                 buf_concat(&b, here);
465                 buf_concat(&b, "/edlib/");
466                 buf_append_byte(&b, 0);
467         }
468
469         dd = getenv("XDG_DATA_DIRS");
470         if (!dd)
471                 dd = "/usr/local/share:/usr/share";
472         while (*dd) {
473                 char *c = strchrnul(dd, ':');
474                 if (*dd == '/') {
475                         buf_concat_len(&b, dd, c-dd);
476                         buf_concat(&b, "/edlib/");
477                         buf_append_byte(&b, 0);
478                 }
479                 if (*c)
480                         c++;
481                 dd = c;
482         }
483         if (b.len)
484                 ei->data_path = buf_final(&b);
485         else
486                 free(buf_final(&b));
487         return ei->data_path;
488 }
489
490 static char *set_config_path(struct pane *p safe)
491 {
492         struct ed_info *ei = p->data;
493         char *ch, *cd, *here;
494         struct buf b;
495
496         if (ei->config_path)
497                 return ei->config_path;
498
499         buf_init(&b);
500         ch = getenv("XDG_CONFIG_HOME");
501         if (!ch) {
502                 char *h = getenv("HOME");
503                 if (h)
504                         ch = strconcat(p, h, "/.config");
505         }
506         if (ch && *ch == '/') {
507                 buf_concat(&b, ch);
508                 buf_concat(&b, "/edlib/");
509                 buf_append_byte(&b, 0);
510         }
511
512         here = set_here(p);
513         if (here && *here == '/') {
514                 buf_concat(&b, here);
515                 buf_concat(&b, "/edlib/");
516                 buf_append_byte(&b, 0);
517         }
518
519         cd = getenv("XDG_CONFIG_DIRS");
520         if (!cd)
521                 cd = "/etc/xdg";
522         while (*cd) {
523                 char *c = strchrnul(cd, ':');
524                 if (*cd == '/') {
525                         buf_concat_len(&b, cd, c-cd);
526                         buf_concat(&b, "/edlib/");
527                         buf_append_byte(&b, 0);
528                 }
529                 if (*c)
530                         c++;
531                 cd = c;
532         }
533         if (b.len)
534                 ei->config_path = buf_final(&b);
535         else
536                 free(buf_final(&b));
537         return ei->config_path;
538 }
539
540 static char *set_bin_path(struct pane *p safe)
541 {
542         struct ed_info *ei = p->data;
543         char *bd, *here;
544         struct buf b;
545
546         if (ei->bin_path)
547                 return ei->bin_path;
548
549         buf_init(&b);
550         here = set_here(p);
551         if (here && *here == '/') {
552                 buf_concat(&b, here);
553                 if (b.len > 4 &&
554                     strncmp(b.b + b.len-4, "/lib", 4) == 0)
555                         b.len -= 3;
556                 else
557                         buf_concat(&b, "/../");
558                 buf_concat(&b, "bin/");
559                 buf_append_byte(&b, 0);
560         }
561         bd = getenv("PATH");
562         if (!bd)
563                 bd = "/usr/bin:/usr/local/bin";
564         while (*bd) {
565                 char *c = strchrnul(bd, ':');
566                 if (*bd == '/') {
567                         buf_concat_len(&b, bd, c-bd);
568                         buf_append_byte(&b, 0);
569                 }
570                 if (*c)
571                         c++;
572                 bd = c;
573         }
574         if (b.len)
575                 ei->bin_path = buf_final(&b);
576         else
577                 free(buf_final(&b));
578         return ei->bin_path;
579 }
580
581 DEF_CMD(global_find_file)
582 {
583         /*
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
594          * - $HERE
595          * - $XDG_DATA_DIRS, or /usr/local/share:/usr/share
596          *
597          * For config we look in a "edlib" under:
598          * - $XDG_CONFIG_HOME, or $HOME/.config
599          * - $HERE
600          * - $XDG_CONFIG_DIRS, or /etc/xdg
601          *
602          * For bin we look in $HERE/../bin and $PATH
603          */
604         char *path = NULL;
605         const char *base[2] = {ci->str, NULL};
606         int i;
607         char *cn;
608
609         if (base[0] == NULL || ci->str2 == NULL)
610                 return -Enoarg;
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);
617
618         if (!path)
619                 return Einval;
620         cn = strstr(base[0], "{COMM}");
621         if (cn) {
622                 char *p = strndup(base[0], cn - base[0]);
623                 char *comm = attr_find(ci->home->attrs, "command-name");
624                 if (!comm)
625                         comm = "edlib";
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);
629         }
630         for (i = 0; i < 2 && base[i] ; i++) {
631                 char *pth;
632                 for (pth = path; pth && *pth; pth += strlen(pth)+1) {
633                         char *p = strconcat(NULL, pth, base[i]);
634                         int fd;
635                         if (!p)
636                                 continue;
637                         fd = open(p, O_RDONLY);
638                         if (fd < 0) {
639                                 free(p);
640                                 continue;
641                         }
642                         close(fd);
643                         comm_call(ci->comm2, "cb", ci->focus, 0, NULL, p);
644                         free(p);
645                         return 1;
646                 }
647         }
648         return Efalse;
649 }
650
651 static struct pane *editor;
652 static void catch(int sig)
653 {
654         struct cmd_info ci;
655         if (!editor)
656                 return;
657         signal(sig, catch);
658         ci.home = editor;
659         ci.focus = editor;
660         ci.num = sig;
661         ci.num2 = 0;
662         ci.str = ci.str2 = NULL;
663         ci.mark = ci.mark2 = NULL;
664         ci.x = ci.y = 0;
665         switch (sig) {
666         case SIGALRM:
667                 ci.key = "fast-alarm-";
668                 key_lookup_prefix(editor->data->map, &ci, True);
669                 break;
670         case SIGINT:
671                 ci.key = "fast-interrupt-";
672                 key_lookup_prefix(editor->data->map, &ci, True);
673                 break;
674         }
675 }
676
677 struct pane *editor_new(const char *comm_name)
678 {
679         struct pane *ed;
680         struct ed_info *ei;
681
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);
698         }
699         ed = pane_register_root(&ed_handle.c, NULL, sizeof(*ei));
700         if (!ed)
701                 return NULL;
702         ei = ed->data;
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);
708         ei->cmd = ed_handle;
709         ei->cmd.m = &ei->map;
710         /* This allows the pane to see registered commands */
711         pane_update_handle(ed, &ei->cmd.c);
712
713         doc_setup(ed);
714         log_setup(ed);
715         window_setup(ed);
716
717         signal(SIGINT, catch);
718         signal(SIGALRM, catch);
719
720         editor = ed;
721         return ed;
722 }