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