]> git.neil.brown.name Git - edlib.git/blob - core-editor.c
Draw:image: support scale and crop.
[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_activate_display)
236 {
237         /* Given a display attached to the root, integrate it
238          * into a full initial stack of panes.
239          */
240         struct pane *disp = ci->focus;
241         struct pane *p, *p2;
242         char *ip = attr_find(ci->home->attrs, "editor-initial-panes");
243         char *save, *t, *m;
244
245         if (!ip)
246                 return Efail;
247         ip = strsave(ci->home, ip);
248         p = pane_root(ci->focus);
249
250         p2 = call_ret(pane, "attach-window-core", p);
251         if (!p2)
252                 return Efail;
253         p = p2;
254
255         for (t = strtok_r(ip, " \t\n", &save);
256              t;
257              t = strtok_r(NULL, " \t\n", &save)) {
258                 if (!*t)
259                         continue;
260                 if (strcmp(t, "DISPLAY") == 0) {
261                         if (disp) {
262                                 pane_reparent(disp, p);
263                                 p = disp;
264                                 disp = NULL;
265                         }
266                         continue;
267                 }
268                 m = strconcat(NULL, "attach-", t);
269                 p2 = call_ret(pane, m, p);
270                 free(m);
271                 if (p2)
272                         p = p2;
273         }
274         comm_call(ci->comm2, "cb", p);
275         return 1;
276 }
277
278 DEF_CMD(editor_multicall)
279 {
280         struct ed_info *ei = ci->home->data;
281         struct map *map = ei->map;
282         int ret;
283         const char *key = ci->key;
284
285         ((struct cmd_info*)ci)->key = ksuffix(ci, "global-multicall-");
286         ret = key_lookup_prefix(map, ci, False);
287         ((struct cmd_info*)ci)->key = key;
288         return ret;
289 }
290
291 DEF_CMD(editor_request_notify)
292 {
293         pane_add_notify(ci->focus, ci->home, ksuffix(ci, "editor:request:"));
294         return 1;
295 }
296
297 DEF_CMD(editor_send_notify)
298 {
299         /* editor:notify:... */
300         return home_pane_notify(ci->home, ksuffix(ci, "editor:notify:"),
301                                 ci->focus,
302                                 ci->num, ci->mark, ci->str,
303                                 ci->num2, ci->mark2, ci->str2, ci->comm2);
304 }
305
306 DEF_CMD(editor_free_panes)
307 {
308         struct ed_info *ei = ci->home->data;
309
310         while (ei->freelist) {
311                 struct pane *p = ei->freelist;
312                 ei->freelist = p->focus;
313                 p->focus = NULL;
314
315                 command_put(p->handle);
316                 p->handle = NULL;
317                 attr_free(&p->attrs);
318                 pane_put(p);
319         }
320         return 1;
321 }
322
323 DEF_CMD(editor_free_marks)
324 {
325         struct ed_info *ei = ci->home->data;
326
327         while (ei->mark_free_list) {
328                 struct mark *m = ei->mark_free_list;
329                 ei->mark_free_list = (struct mark*)m->all.next;
330                 do_mark_free(m);
331         }
332
333         return 1;
334 }
335
336 DEF_CMD(editor_free_store)
337 {
338         struct ed_info *ei = ci->home->data;
339
340         while (ei->store) {
341                 struct store *s = ei->store;
342                 ei->store = s->next;
343                 free(s);
344         }
345         return 1;
346 }
347
348 /* FIXME I should be able to remove things from a keymap, not
349  * replace with this.
350  */
351 DEF_EXTERN_CMD(edlib_noop)
352 {
353         return Efallthrough;
354 }
355
356 DEF_CMD_CLOSED(editor_close)
357 {
358         struct ed_info *ei = ci->home->data;
359         stat_free();
360         free(ei->here); ei->here = NULL;
361         free(ei->data_path); ei->data_path = NULL;
362         free(ei->config_path); ei->config_path = NULL;
363         return Efallthrough;
364 }
365
366 void * safe memsave(struct pane *p safe, const char *buf, int len)
367 {
368         struct ed_info *ei;
369
370         p = pane_root(p);
371         ei = p->data;
372         ASSERT(ei->magic==ED_MAGIC);
373         if (!ei->store)
374                 call_comm("event:on-idle", p, &editor_free_store, 2);
375         if (ei->store == NULL || ei->store->size < len) {
376                 struct store *s;
377                 int l = 4096 - sizeof(*s);
378                 while (l < len)
379                         l += 4096;
380                 s = malloc(l + sizeof(*s));
381                 s->next = ei->store;
382                 s->size = l;
383                 ei->store = s;
384         }
385         ei->store->size -= len;
386         if (buf)
387                 return memcpy(ei->store->space+ei->store->size, buf, len);
388         else
389                 return ei->store->space+ei->store->size;
390 }
391
392 char *strsave(struct pane *p safe, const char *buf)
393 {
394         if (!buf)
395                 return NULL;
396         return memsave(p, buf, strlen(buf)+1);
397 }
398
399 char *strnsave(struct pane *p safe, const char *buf, int len)
400 {
401         char *s;
402         if (!buf)
403                 return NULL;
404         s = memsave(p, buf, len+1);
405         if (s)
406                 s[len] = 0;
407         return s;
408 }
409
410 char * safe do_strconcat(struct pane *p, const char *s1 safe, ...)
411 {
412         va_list ap;
413         char *s;
414         int len = 0;
415         char *ret;
416
417         len = strlen(s1);
418         va_start(ap, s1);
419         while ((s = va_arg(ap, char*)) != NULL)
420                 len += strlen(s);
421         va_end(ap);
422
423         if (p)
424                 ret = memsave(p, NULL, len+1);
425         else
426                 ret = malloc(len+1);
427         strcpy(ret, s1);
428         va_start(ap, s1);
429         while ((s = va_arg(ap, char*)) != NULL)
430                 strcat(ret, s);
431         va_end(ap);
432         return ret;
433 }
434
435 void editor_delayed_free(struct pane *ed safe, struct pane *p safe)
436 {
437         struct ed_info *ei = ed->data;
438         if (!ei) {
439                 command_put(p->handle);
440                 p->handle = NULL;
441                 attr_free(&p->attrs);
442                 pane_free(p);
443                 return;
444         }
445         ASSERT(ei->magic==ED_MAGIC);
446         if (!ei->freelist)
447                 call_comm("event:on-idle", ed, &editor_free_panes, 2);
448         p->focus = ei->freelist;
449         ei->freelist = p;
450 }
451
452 void editor_delayed_mark_free(struct mark *m safe)
453 {
454         struct pane *ed = pane_root(m->owner);
455         struct ed_info *ei = ed->data;
456
457         ASSERT(ei->magic==ED_MAGIC);
458         if (!ei->mark_free_list)
459                 call_comm("event:on-idle", ed, &editor_free_marks, 2);
460         m->all.next = (void*)ei->mark_free_list;
461         ei->mark_free_list = m;
462 }
463
464 static char *set_here(struct pane *p safe)
465 {
466         struct ed_info *ei = p->data;
467         Dl_info info;
468
469         if (ei->here)
470                 ;
471         else if (dladdr(&set_here, &info) == 0)
472                 ei->here = strdup("");
473         else {
474                 char *sl;
475                 ei->here = strdup(info.dli_fname ?: "");
476                 sl = strrchr(ei->here, '/');
477                 if (sl)
478                         *sl = 0;
479         }
480         return ei->here;
481 }
482
483 static char *set_data_path(struct pane *p safe)
484 {
485         struct ed_info *ei = p->data;
486         char *dh, *dd, *here;
487         struct buf b;
488
489         if (ei->data_path)
490                 return ei->data_path;
491
492         buf_init(&b);
493         dh = getenv("XDG_DATA_HOME");
494         if (!dh) {
495                 char *h = getenv("HOME");
496                 if (h)
497                         dh = strconcat(p, h, "/.local/share");
498         }
499         if (dh && *dh == '/') {
500                 buf_concat(&b, dh);
501                 buf_concat(&b, "/edlib/");
502                 buf_append_byte(&b, 0);
503         }
504
505         here = set_here(p);
506         if (here && *here == '/') {
507                 buf_concat(&b, here);
508                 buf_concat(&b, "/edlib/");
509                 buf_append_byte(&b, 0);
510         }
511
512         dd = getenv("XDG_DATA_DIRS");
513         if (!dd)
514                 dd = "/usr/local/share:/usr/share";
515         while (*dd) {
516                 char *c = strchrnul(dd, ':');
517                 if (*dd == '/') {
518                         buf_concat_len(&b, dd, c-dd);
519                         buf_concat(&b, "/edlib/");
520                         buf_append_byte(&b, 0);
521                 }
522                 if (*c)
523                         c++;
524                 dd = c;
525         }
526         if (b.len)
527                 ei->data_path = buf_final(&b);
528         else
529                 free(buf_final(&b));
530         return ei->data_path;
531 }
532
533 static char *set_config_path(struct pane *p safe)
534 {
535         struct ed_info *ei = p->data;
536         char *ch, *cd, *here;
537         struct buf b;
538
539         if (ei->config_path)
540                 return ei->config_path;
541
542         buf_init(&b);
543         ch = getenv("XDG_CONFIG_HOME");
544         if (!ch) {
545                 char *h = getenv("HOME");
546                 if (h)
547                         ch = strconcat(p, h, "/.config");
548         }
549         if (ch && *ch == '/') {
550                 buf_concat(&b, ch);
551                 buf_concat(&b, "/edlib/");
552                 buf_append_byte(&b, 0);
553         }
554
555         here = set_here(p);
556         if (here && *here == '/') {
557                 buf_concat(&b, here);
558                 buf_concat(&b, "/edlib/");
559                 buf_append_byte(&b, 0);
560         }
561
562         cd = getenv("XDG_CONFIG_DIRS");
563         if (!cd)
564                 cd = "/etc/xdg";
565         while (*cd) {
566                 char *c = strchrnul(cd, ':');
567                 if (*cd == '/') {
568                         buf_concat_len(&b, cd, c-cd);
569                         buf_concat(&b, "/edlib/");
570                         buf_append_byte(&b, 0);
571                 }
572                 if (*c)
573                         c++;
574                 cd = c;
575         }
576         if (b.len)
577                 ei->config_path = buf_final(&b);
578         else
579                 free(buf_final(&b));
580         return ei->config_path;
581 }
582
583 static char *set_bin_path(struct pane *p safe)
584 {
585         struct ed_info *ei = p->data;
586         char *bd, *here;
587         struct buf b;
588
589         if (ei->bin_path)
590                 return ei->bin_path;
591
592         buf_init(&b);
593         here = set_here(p);
594         if (here && *here == '/') {
595                 buf_concat(&b, here);
596                 if (b.len > 4 &&
597                     strncmp(b.b + b.len-4, "/lib", 4) == 0)
598                         b.len -= 3;
599                 else
600                         buf_concat(&b, "/../");
601                 buf_concat(&b, "bin/");
602                 buf_append_byte(&b, 0);
603         }
604         bd = getenv("PATH");
605         if (!bd)
606                 bd = "/usr/bin:/usr/local/bin";
607         while (*bd) {
608                 char *c = strchrnul(bd, ':');
609                 if (*bd == '/') {
610                         buf_concat_len(&b, bd, c-bd);
611                         buf_append_byte(&b, 0);
612                 }
613                 if (*c)
614                         c++;
615                 bd = c;
616         }
617         if (b.len)
618                 ei->bin_path = buf_final(&b);
619         else
620                 free(buf_final(&b));
621         return ei->bin_path;
622 }
623
624 DEF_CMD(global_find_file)
625 {
626         /*
627          * ->str is a file basename.  If it contains {COMM}, that will
628          * be replaced with the "command-name" attr from root, or
629          * "edlib" if nothing can be found.
630          * ->str2 is one of "data", "config", "bin"
631          * We find a file with basename in a known location following
632          * the XDG Base Directory Specificaton.
633          * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
634          * but also look in the directory containing this library $HERE
635          * For "data" we look in a directory "edlib" under:
636          * - $XDG_DATA_HOME, or $HOME/.local/share
637          * - $HERE
638          * - $XDG_DATA_DIRS, or /usr/local/share:/usr/share
639          *
640          * For config we look in a "edlib" under:
641          * - $XDG_CONFIG_HOME, or $HOME/.config
642          * - $HERE
643          * - $XDG_CONFIG_DIRS, or /etc/xdg
644          *
645          * For bin we look in $HERE/../bin and $PATH
646          */
647         char *path = NULL;
648         const char *base[2] = {ci->str, NULL};
649         int i;
650         char *cn;
651
652         if (base[0] == NULL || ci->str2 == NULL)
653                 return -Enoarg;
654         if (strcmp(ci->str2, "data") == 0)
655                 path = set_data_path(ci->home);
656         else if (strcmp(ci->str2, "config") == 0)
657                 path = set_config_path(ci->home);
658         else if (strcmp(ci->str2, "bin") == 0)
659                 path = set_bin_path(ci->home);
660
661         if (!path)
662                 return Einval;
663         cn = strstr(base[0], "{COMM}");
664         if (cn) {
665                 char *p = strndup(base[0], cn - base[0]);
666                 char *comm = attr_find(ci->home->attrs, "command-name");
667                 if (!comm)
668                         comm = "edlib";
669                 base[0] = strconcat(ci->home, p, comm, cn+6);
670                 if (strcmp(comm, "edlib") != 0)
671                         base[1] = strconcat(ci->home, p, "edlib", cn+6);
672         }
673         for (i = 0; i < 2 && base[i] ; i++) {
674                 char *pth;
675                 for (pth = path; pth && *pth; pth += strlen(pth)+1) {
676                         char *p = strconcat(NULL, pth, base[i]);
677                         int fd;
678                         if (!p)
679                                 continue;
680                         fd = open(p, O_RDONLY);
681                         if (fd < 0) {
682                                 free(p);
683                                 continue;
684                         }
685                         close(fd);
686                         comm_call(ci->comm2, "cb", ci->focus, 0, NULL, p);
687                         free(p);
688                         return 1;
689                 }
690         }
691         return Efalse;
692 }
693
694 static struct pane *editor;
695 static void catch(int sig)
696 {
697         struct cmd_info ci;
698         if (!editor)
699                 return;
700         signal(sig, catch);
701         ci.home = editor;
702         ci.focus = editor;
703         ci.num = sig;
704         ci.num2 = 0;
705         ci.str = ci.str2 = NULL;
706         ci.mark = ci.mark2 = NULL;
707         ci.x = ci.y = 0;
708         switch (sig) {
709         case SIGALRM:
710                 ci.key = "fast-alarm-";
711                 key_lookup_prefix(editor->data->map, &ci, True);
712                 break;
713         case SIGINT:
714                 ci.key = "fast-interrupt-";
715                 key_lookup_prefix(editor->data->map, &ci, True);
716                 break;
717         }
718 }
719
720 struct pane *editor_new(const char *comm_name)
721 {
722         struct pane *ed;
723         struct ed_info *ei;
724
725         if (! (void*) ed_map) {
726                 ed_map = key_alloc();
727                 key_add(ed_map, "global-set-attr", &global_set_attr);
728                 key_add(ed_map, "global-set-command", &global_set_command);
729                 key_add(ed_map, "global-set-command-prefix", &global_set_command);
730                 key_add(ed_map, "global-get-command", &global_get_command);
731                 key_add(ed_map, "global-load-module", &editor_load_module);
732                 key_add(ed_map, "global-config-dir", &global_config_dir);
733                 key_add(ed_map, "xdg-find-edlib-file", &global_find_file);
734                 key_add_prefix(ed_map, "event:", &editor_auto_event);
735                 key_add_prefix(ed_map, "global-multicall-", &editor_multicall);
736                 key_add_prefix(ed_map, "editor:request:",
737                                &editor_request_notify);
738                 key_add_prefix(ed_map, "editor:notify:",
739                                &editor_send_notify);
740                 key_add(ed_map, "editor:activate-display",
741                         &editor_activate_display);
742                 key_add(ed_map, "Close", &editor_close);
743         }
744         ed = pane_register_root(&ed_handle.c, NULL, sizeof(*ei));
745         if (!ed)
746                 return NULL;
747         ei = ed->data;
748         ei->magic = ED_MAGIC;
749         attr_set_str(&ed->attrs, "command-name", comm_name ?: "edlib");
750         ei->testing = (getenv("EDLIB_TESTING") != NULL);
751         ei->map = key_alloc();
752         key_add_chain(ei->map, ed_map);
753         ei->cmd = ed_handle;
754         ei->cmd.m = &ei->map;
755         /* This allows the pane to see registered commands */
756         pane_update_handle(ed, &ei->cmd.c);
757
758         doc_setup(ed);
759         log_setup(ed);
760         window_setup(ed);
761
762         signal(SIGINT, catch);
763         signal(SIGALRM, catch);
764
765         editor = ed;
766         return ed;
767 }