]> git.neil.brown.name Git - edlib.git/blob - core-doc.c
display-x11: don't draw both cursors.
[edlib.git] / core-doc.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * All content managed in edlib is stored in documents.
6  * There can be multiple document handlers which export the
7  * doc_operations interface to provide access to a particular
8  * style of document storage.
9  * A document has a list of marks and points (managed in core-mark.c)
10  * and some attributes (managed in attr.c).
11  * It has a list of 'views' which are notified when the document changes.
12  * Those are managed here.
13  *
14  * Finally all documents are kept in a single list which itself is
15  * used as the basis for a document: the document-list.  The list is
16  * kept in most-recently-used order.  Each document has a unique name
17  * in this list.
18  */
19
20 #define _GNU_SOURCE for strchrnul
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <wchar.h>
25 #include <wctype.h>
26 #include <stdio.h>
27 #include <fcntl.h>
28
29 #define PRIVATE_DOC_REF
30 struct doc_ref {
31         struct pane     *p;
32         int             ignore;
33 };
34
35 #define PANE_DATA_TYPE struct doc_data
36 #include "core.h"
37 #include "misc.h"
38 #include "internal.h"
39
40 /* this is ->data for a document reference pane.
41  */
42 struct doc_data {
43         struct pane             *doc safe;
44         struct mark             *point safe;
45         struct mark             *old_point; /* location at last refresh */
46         struct mark             *marks[4];
47 };
48 #include "core-pane.h"
49
50 static struct pane *doc_attach_assign(struct pane *parent safe, struct pane *doc safe);
51
52
53 static void doc_init(struct doc *d safe)
54 {
55         d->self = d;
56         INIT_HLIST_HEAD(&d->marks);
57         INIT_TLIST_HEAD(&d->points, 0);
58         d->views = NULL;
59         d->nviews = 0;
60         d->name = NULL;
61         memset(d->recent_points, 0, sizeof(d->recent_points));
62         d->autoclose = 0;
63         d->readonly = 0;
64         d->refcnt = NULL;
65 }
66
67 struct pane *do_doc_register(struct pane *parent safe,
68                              struct command *handle safe,
69                              struct doc *doc,
70                              unsigned short data_size)
71 {
72         struct pane *p;
73
74         if (doc == NULL && data_size < sizeof(*doc))
75                 /* Not enough room for the doc ! */
76                 return NULL;
77
78         /* Documents are always registered against the root */
79         parent = pane_root(parent);
80         p = do_pane_register(parent, 0, handle, doc, data_size);
81         if (!p)
82                 return p;
83         if (!doc)
84                 doc = &p->doc;
85         doc_init(doc);
86         return p;
87 }
88
89 /* For these 'default commands', home->data is struct doc */
90 DEF_CMD(doc_char)
91 {
92         struct pane *f = ci->focus;
93         struct doc_data *dd = ci->home->data;
94         struct mark *m = ci->mark;
95         int rpt = RPT_NUM(ci);
96
97         if (!m)
98                 m = dd->point;
99
100         while (rpt > 0) {
101                 if (doc_next(f,m) == WEOF)
102                         break;
103                 rpt -= 1;
104         }
105         while (rpt < 0) {
106                 if (doc_prev(f,m) == WEOF)
107                         break;
108                 rpt += 1;
109         }
110
111         return 1;
112 }
113
114 DEF_CMD(doc_word)
115 {
116         /* Step to the Nth word boundary in appropriate
117          * direction.  If N is 0, don't move.
118          * Return 1 if succeeded before EOF, else Efalse.
119          */
120         struct pane *f = ci->focus;
121         struct mark *m = ci->mark;
122         int rpt = RPT_NUM(ci);
123         wint_t wi = 0;
124
125         if (!m)
126                 return Enoarg;
127         /* doc:word should finish at a word boundary, which usually
128          * means an alphanum (possibly including '_' depending on doc
129          * attributes?).  However it should never cross two different
130          * sets of spaces or punctuation.  So if we cross space and
131          * punct and don't find alphanum, then we treat end of punct as
132          * a word boundary.  We never stop immediately after a space.
133          * So skip spaces, then punct, then alphanum.
134          * Same in either direction.
135          */
136
137         while (rpt > 0 && wi != WEOF) {
138                 while ((wi = doc_following(f, m)) != WEOF &&
139                        iswspace(wi))
140                         doc_next(f, m);
141                 while ((wi = doc_following(f, m)) != WEOF &&
142                        !iswspace(wi) && !iswalnum(wi))
143                         doc_next(f, m);
144                 while ((wi = doc_following(f, m)) != WEOF &&
145                        iswalnum(wi))
146                         doc_next(f, m);
147                 rpt -= 1;
148         }
149         while (rpt < 0 && wi != WEOF) {
150                 while ((wi = doc_prior(f, m)) != WEOF &&
151                        iswspace(wi))
152                         doc_prev(f, m);
153                 while ((wi = doc_prior(f, m)) != WEOF &&
154                        !iswspace(wi) && !iswalnum(wi))
155                         doc_prev(f, m);
156                 while ((wi = doc_prior(f, m)) != WEOF &&
157                        iswalnum(wi))
158                         doc_prev(f, m);
159                 rpt += 1;
160         }
161         return rpt == 0 ? 1 : Efalse;
162 }
163
164 static bool check_slosh(struct pane *p safe, struct mark *m safe)
165 {
166         wint_t ch;
167         /* Check is preceded by exactly 1 '\' */
168         if (doc_prior(p, m) != '\\')
169                 return False;
170         doc_prev(p,m);
171         ch = doc_prior(p, m);
172         doc_next(p,m);
173         return ch != '\\';
174 }
175
176 DEF_CMD(doc_expr)
177 {
178         /* doc_expr skips an 'expression' which is the same as a word
179          * unless we see open '({[' or close ')}]' or quote (\'\").
180          * We ignore quotes when preceeded by a single '\'
181          * If we see close going forward, or open going backward, we stop.
182          * If we see open going forward or close going backward, or quote,
183          * we skip to matching close/open/quote, allowing for nested
184          * open/close etc. Inside quotes, we stop at EOL.
185          * If num2 is 1, then if we reach a true 'open' we continue
186          * one more character to enter (going forward) or leave (backward)
187          * the expression.
188          * 'str' can be set to extra chars that should be included in words.
189          */
190         struct pane *f = ci->focus;
191         struct mark *m = ci->mark;
192         int rpt = RPT_NUM(ci);
193         int enter_leave = ci->num2;
194         int dir = rpt > 0 ? 1 : -1;
195         char *open;
196         char *close;
197         const char *wordchars = ci->str ?: "";
198         const char *special safe = "[](){}'\"";
199
200         if (!m)
201                 return Enoarg;
202         if (dir > 0) {
203                 open = "([{"; close = ")]}";
204         } else {
205                 open = ")]}"; close = "([{";
206         }
207         while (rpt != 0) {
208                 wint_t wi;
209
210                 while ((wi = doc_pending(f, m, dir)) != WEOF
211                        && iswspace(wi))
212                         doc_move(f, m, dir);
213
214                 while ((wi = doc_pending(f, m, dir)) != WEOF &&
215                        !iswspace(wi) && !iswalnum(wi) &&
216                        (wi > 255 || (strchr(special, wi) == NULL &&
217                                      strchr(wordchars, wi) == NULL)))
218                         doc_move(f, m, dir);
219
220                 if (strchr(close, wi)) {
221                         if (dir < 0 && enter_leave) {
222                                 doc_prev(f, m);
223                                 rpt += 1;
224                         } else
225                                 /* hit a close */
226                                 break;
227                 } else if (strchr(open, wi)) {
228                         /* skip bracketed expression */
229                         int depth = 1;
230                         wint_t q = 0;
231
232                         doc_move(f, m, dir);
233                         if (enter_leave && dir > 0)
234                                 /* Just entered the expression */
235                                 rpt -= 1;
236                         else while (depth > 0 &&
237                                     (wi = doc_move(f, m, dir)) != WEOF) {
238                                         if (q) {
239                                                 if (dir > 0)
240                                                         doc_prev(f,m);
241                                                 if ((!check_slosh(f, m) && wi == q) ||
242                                                     is_eol(wi))
243                                                         q = 0;
244                                                 if (dir > 0)
245                                                         doc_next(f,m);
246                                         } else if (strchr(open, wi))
247                                                 depth += 1;
248                                         else if (strchr(close, wi))
249                                                 depth -= 1;
250                                         else if (wi == '"' || wi == '\'') {
251                                                 if (dir > 0)
252                                                         doc_prev(f,m);
253                                                 if (!check_slosh(f, m))
254                                                         q = wi;
255                                                 if (dir > 0)
256                                                         doc_next(f,m);
257                                         }
258                                 }
259                 } else if (wi == '"' || wi == '\'') {
260                         /* skip quoted or to EOL */
261                         wint_t q = wi;
262                         bool slosh = False;
263                         if (dir > 0) {
264                                 slosh = check_slosh(f, m);
265                                 doc_move(f, m, dir);
266                         } else {
267                                 doc_move(f, m, dir);
268                                 slosh = check_slosh(f, m);
269                         }
270                         if (!slosh) {
271                                 while (((wi = doc_pending(f, m, dir))
272                                         != WEOF) &&
273                                        !is_eol(wi)) {
274                                         if (dir > 0) {
275                                                 slosh = check_slosh(f, m);
276                                                 doc_next(f, m);
277                                         } else {
278                                                 doc_prev(f, m);
279                                                 slosh = check_slosh(f, m);
280                                         }
281                                         if (wi == q && !slosh)
282                                                 break;
283                                 }
284                         }
285                 } else while (((wi=doc_pending(f, m, dir)) != WEOF && iswalnum(wi)) ||
286                               (wi > 0 && wi <= 255 &&
287                                strchr(wordchars, wi) != NULL))
288                                 doc_move(f, m, dir);
289
290                 if (!enter_leave)
291                         rpt -= dir;
292                 if (wi == WEOF)
293                         break;
294         }
295         return rpt ? 2 : 1;
296 }
297
298 DEF_CMD(doc_WORD)
299 {
300         /* Step to the Nth word boundary in appropriate
301          * direction.  For this function, puctuation is treated the
302          * same as alphanum.  Only space separates words.
303          * If N is 0, don't move.
304          * Return 1 if succeeded before EOF, else Efalse.
305          */
306         struct pane *f = ci->focus;
307         struct mark *m = ci->mark;
308         int rpt = RPT_NUM(ci);
309
310         if (!m)
311                 return Enoarg;
312
313         /* We skip spaces, then non-spaces */
314         while (rpt > 0) {
315                 wint_t wi;
316
317                 while ((wi = doc_following(f, m)) != WEOF &&
318                        iswspace(wi))
319                         doc_next(f,m);
320
321                 while ((wi = doc_following(f, m)) != WEOF &&
322                        !iswspace(wi))
323                         doc_next(f,m);
324                 rpt -= 1;
325         }
326         while (rpt < 0) {
327                 wint_t wi;
328
329                 while ((wi = doc_prior(f, m)) != WEOF &&
330                        iswspace(wi))
331                         doc_prev(f,m);
332                 while ((wi = doc_prior(f, m)) != WEOF &&
333                        !iswspace(wi))
334                         doc_prev(f,m);
335                 rpt += 1;
336         }
337
338         return rpt == 0 ? 1 : Efalse;
339 }
340
341 DEF_CMD(doc_eol)
342 {
343         struct pane *f = ci->focus;
344         struct mark *m = ci->mark;
345         wint_t ch = 1;
346         int rpt = RPT_NUM(ci);
347         bool one_more = ci->num2 > 0;
348
349         if (!m)
350                 return Enoarg;
351
352         while (rpt > 0 && ch != WEOF) {
353                 while ((ch = doc_next(f, m)) != WEOF &&
354                        !is_eol(ch))
355                         ;
356                 if (ch != WEOF)
357                         rpt -= 1;
358         }
359         while (rpt < 0 && ch != WEOF) {
360                 while ((ch = doc_prev(f, m)) != WEOF &&
361                        !is_eol(ch))
362                         ;
363                 if (ch != WEOF)
364                         rpt += 1;
365         }
366         if (!one_more) {
367                 if (is_eol(ch)) {
368                         if (RPT_NUM(ci) > 0)
369                                 doc_prev(f, m);
370                         else if (RPT_NUM(ci) < 0)
371                                 doc_next(f, m);
372                 }
373                 if (ch == WEOF) {
374                         if (RPT_NUM(ci) > 0)
375                                 rpt -= 1;
376                         else if (RPT_NUM(ci) < 0)
377                                 rpt += 1;
378                 }
379         }
380         return rpt == 0 ? 1 : Efalse;
381 }
382
383 DEF_CMD(doc_file)
384 {
385         int rpt = RPT_NUM(ci);
386         struct mark *m = ci->mark;
387
388         if (!m)
389                 return Enoarg;
390
391         call("doc:set-ref", ci->focus, (rpt < 0), m);
392
393         return 1;
394 }
395
396 DEF_CMD(doc_line)
397 {
398         struct pane *p = ci->focus;
399         struct doc_data *dd = ci->home->data;
400         struct mark *m = ci->mark;
401         wint_t ch = 1;
402         int rpt = RPT_NUM(ci);
403
404         if (!m)
405                 m = dd->point;
406
407         while (rpt > 0 && ch != WEOF) {
408                 while ((ch = doc_next(p, m)) != WEOF &&
409                        !is_eol(ch))
410                         ;
411                 rpt -= 1;
412         }
413         while (rpt < 0 && ch != WEOF) {
414                 while ((ch = doc_prev(p, m)) != WEOF &&
415                        !is_eol(ch))
416                         ;
417                 rpt += 1;
418         }
419         return 1;
420 }
421
422 DEF_CMD(doc_para)
423 {
424         /* Default paragraph move - find blank line - two or more
425          * is_eol() chars.
426          * If moving forward, skip over all those chars.
427          * If moving backward, stop before the first one.
428          */
429         struct pane *p = ci->focus;
430         struct mark *m = ci->mark;
431         int rpt = RPT_NUM(ci);
432         wint_t ch = 0;
433         int nlcnt = 0;
434         int dir = rpt > 0 ? 1 : -1;
435
436         if (!m)
437                 return Enoarg;
438
439         while (dir < 0 && is_eol(doc_prior(p, m)))
440                 doc_prev(p, m);
441
442         while (rpt && ch != WEOF) {
443                 nlcnt = 0;
444                 while (ch != WEOF) {
445                         ch = doc_move(p, m, dir);
446                         if (is_eol(ch))
447                                 nlcnt += 1;
448                         else if (nlcnt < 2)
449                                 nlcnt = 0;
450                         else {
451                                 doc_move(p, m, -dir);
452                                 break;
453                         }
454                 }
455                 rpt += dir;
456         }
457
458         while (dir < 0 && nlcnt-- > 0)
459                 doc_next(p, m);
460         return 1;
461 }
462
463 DEF_CMD(doc_page)
464 {
465         struct pane *p = ci->focus;
466         struct doc_data *dd = ci->home->data;
467         struct mark *m = ci->mark, *old;
468         wint_t ch = 1;
469         int rpt = RPT_NUM(ci);
470
471         if (!m)
472                 m = dd->point;
473         old = mark_dup(m);
474
475         rpt *= p->h-2;
476         /* repeat count is in 1000th of the pane */
477         rpt /= 1000;
478         while (rpt > 0 && ch != WEOF) {
479                 while ((ch = doc_next(p, m)) != WEOF &&
480                        !is_eol(ch))
481                         ;
482                 rpt -= 1;
483         }
484         while (rpt < 0 && ch != WEOF) {
485                 while ((ch = doc_prev(p, m)) != WEOF &&
486                        !is_eol(ch))
487                         ;
488                 rpt += 1;
489         }
490         if (mark_same(m, old)) {
491                 mark_free(old);
492                 return 2;
493         }
494         mark_free(old);
495         return 1;
496 }
497
498 DEF_CMD(doc_set)
499 {
500         struct doc *d = ci->home->_data;
501         const char *val = ksuffix(ci, "doc:set:");
502
503         if (!*val)
504                 val = ci->str2;
505         if (!val)
506                 return Enoarg;
507
508         if (strcmp(val, "autoclose") == 0) {
509                 d->autoclose = ci->num;
510                 return 1;
511         }
512         if (strcmp(val, "readonly") == 0) {
513                 d->readonly = ci->num;
514                 call("doc:notify:doc:status-changed", ci->home);
515                 return 1;
516         }
517         if (ci->str)
518                 attr_set_str(&ci->home->attrs, val, ci->str);
519
520         return 1;
521 }
522
523 DEF_CMD(doc_append)
524 {
525         struct pane *p = ci->home;
526         const char *attr = ksuffix(ci, "doc:append:");
527         const char *val = ci->str;
528         const char *old;
529
530         if (!*attr)
531                 attr = ci->str2;
532         if (!attr)
533                 return Enoarg;
534
535         if (!val || !val[0])
536                 return Enoarg;
537         /* Append the string to the attr.  It attr doesn't
538          * exists, strip first char of val and use that.
539          */
540         old = attr_find(p->attrs, attr);
541         if (!old) {
542                 attr_set_str(&p->attrs, attr, val+1);
543         } else {
544                 const char *pos = strstr(old, val+1);
545                 int len = strlen(val+1);
546                 if (pos &&
547                     (pos == old || pos[-1] == val[0]) &&
548                     (pos[len] == 0 || pos[len] == val[0]))
549                         ; /* val already present */
550                 else
551                         attr_set_str(&p->attrs, attr, strconcat(p, old, val));
552         }
553         return 1;
554 }
555
556 DEF_CMD(doc_get_attr)
557 {
558         struct doc *d = ci->home->_data;
559         char pathbuf[PATH_MAX];
560         char *a;
561
562         if (!ci->str)
563                 return Enoarg;
564
565         if ((a = attr_find(ci->home->attrs, ci->str)) != NULL)
566                 ;
567         else if (strcmp(ci->str, "doc-name") == 0)
568                 a = d->name;
569         else if (strcmp(ci->str, "doc-modified") == 0)
570                 a = "no";
571         else if (strcmp(ci->str, "doc-readonly") == 0) {
572                 a = d->readonly ? "yes":"no";
573         } else if (strcmp(ci->str, "dirname") == 0) {
574                 char *sl;
575                 a = pane_attr_get(ci->home, "filename");
576                 if (!a) {
577                         a = realpath(".", pathbuf);
578                         if (a != pathbuf && a)
579                                 strcpy(pathbuf, a);
580                         if (pathbuf[1])
581                                 strcat(pathbuf, "/");
582                         a = strsave(ci->focus, pathbuf);
583                 } else {
584                         sl = strrchr(a, '/');
585                         if (!sl)
586                                 sl = a = "/";
587                         a = strnsave(ci->focus, a, (sl-a)+1);
588                 }
589                 attr_set_str(&ci->home->attrs, "dirname", a);
590         } else if (strcmp(ci->str, "realdir") == 0) {
591                 a = pane_attr_get(ci->home, "dirname");
592                 if (a) {
593                         strcpy(pathbuf,"/");
594                         a = realpath(a, pathbuf);
595                         if (a && a != pathbuf)
596                                 strcpy(pathbuf, a);
597                         if (pathbuf[1])
598                                 strcat(pathbuf, "/");
599                         a = pathbuf;
600                 }
601                 attr_set_str(&ci->home->attrs, "realdir", a);
602         }
603         if (a)
604                 return comm_call(ci->comm2, "callback:get_attr", ci->focus, 0,
605                                  NULL, a) ?: 1;
606         return 1;
607 }
608
609 DEF_CMD(doc_doc_get_attr)
610 {
611         /* If the document doesn't provide the attribute for
612          * this location, see if there is a pane-attribute for
613          * the document.
614          */
615         char *a;
616
617         if (!ci->str)
618                 return Enoarg;
619         a = pane_attr_get(ci->home, ci->str);
620         if (a)
621                 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, a);
622         return 1;
623 }
624
625 DEF_CMD(doc_set_name)
626 {
627         struct doc *d = ci->home->_data;
628
629         if (!ci->str)
630                 return Enoarg;
631         free(d->name);
632         d->name = strdup(ci->str);
633         return call("doc:notify:doc:revisit", ci->home, ci->num) ?: 1;
634 }
635
636 DEF_CMD(doc_request_notify)
637 {
638         pane_add_notify(ci->focus, ci->home, ksuffix(ci, "doc:request:"));
639         return 1;
640 }
641
642 DEF_CMD(doc_notify)
643 {
644         /* Key is "doc:notify:..." */
645         int ret = pane_notify(ksuffix(ci, "doc:notify:"),
646                               ci->home,
647                               ci->num, ci->mark, ci->str,
648                               ci->num2, ci->mark2, ci->str2, ci->comm2);
649         return ret;
650 }
651
652 static int do_del_view(struct doc *d safe, int v,
653                        struct pane *owner)
654 {
655         bool warned = False;
656         /* This view should only have points on the list, not typed
657          * marks.  Just delete everything and clear the 'notify' pointer
658          */
659         if (v < 0 || v >= d->nviews || d->views == NULL ||
660             !owner || d->views[v].owner != owner)
661                 return Einval;
662
663         d->views[v].owner = NULL;
664         while (!tlist_empty(&d->views[v].head)) {
665                 struct mark *m;
666                 struct tlist_head *tl = d->views[v].head.next;
667
668                 switch (TLIST_TYPE(tl)) {
669                 case GRP_LIST: /* A point */
670                         tlist_del_init(tl);
671                         break;
672                 case GRP_MARK: /* a vmark */
673                         m = container_of(tl, struct mark, view);
674                         if (m->mdata)
675                                 pane_call(owner, "Close:mark", owner, 0, m);
676                         if (tl == d->views[v].head.next) {
677                                 /* It hasn't been freed */
678                                 if (m->mdata && !warned) {
679                                         call("editor:notify:Message:broadcast",
680                                              owner, 0, NULL,
681                                              "WARNING mark not freed by Close:mark");
682                                         LOG("WARNING Mark not freed by Close:mark");
683                                         warned = True;
684                                 }
685                                 m->mdata = NULL;
686                                 mark_free(m);
687                         }
688                         break;
689                 default: /* impossible */
690                         abort();
691                 }
692         }
693         return 1;
694 }
695
696 DEF_CMD(doc_delview)
697 {
698         struct doc *d = ci->home->_data;
699
700         return do_del_view(d, ci->num, ci->focus);
701 }
702
703 DEF_CMD(doc_addview)
704 {
705         struct doc *d = ci->home->_data;
706         struct docview *g;
707         int ret;
708         int i;
709
710         for (ret = 0; d->views && ret < d->nviews; ret++)
711                 if (d->views[ret].owner == NULL)
712                         break;
713         if (!d->views || ret == d->nviews) {
714                 /* Resize the view list */
715                 d->nviews += 4;
716                 g = alloc_buf(sizeof(*g) * d->nviews, pane);
717                 for (i = 0; d->views && i < ret; i++) {
718                         tlist_add(&g[i].head, GRP_HEAD, &d->views[i].head);
719                         tlist_del(&d->views[i].head);
720                         g[i].owner = d->views[i].owner;
721                 }
722                 for (; i < d->nviews; i++) {
723                         INIT_TLIST_HEAD(&g[i].head, GRP_HEAD);
724                         g[i].owner = NULL;
725                 }
726                 unalloc_buf(d->views, sizeof(*g)*(d->nviews - 4), pane);
727                 d->views = g;
728                 /* now resize all the points */
729                 points_resize(d);
730         }
731         if (d->views /* FIXME always true */) {
732                 points_attach(d, ret);
733                 d->views[ret].owner = ci->focus;
734                 /* Use Close:Notify because we need this even
735                  * when the pane is closing
736                  */
737                 pane_add_notify(ci->home, ci->focus, "Close:Notify");
738         }
739         return 1 + ret;
740 }
741
742 DEF_CMD(doc_close_doc)
743 {
744         struct doc *d = ci->home->_data;
745         doc_free(d, ci->home);
746         return 1;
747 }
748
749 DEF_CMD(doc_view_close)
750 {
751         /* A pane which once held a view is closing.  We must discard
752          * that view if it still exists.
753          */
754         struct doc *d = ci->home->_data;
755         int v;
756
757         for (v = 0 ; d->views && v < d->nviews; v++)
758                 do_del_view(d, v, ci->focus);
759         return 1;
760 }
761
762 DEF_CMD(doc_vmarkget)
763 {
764         struct mark *m, *m2;
765         m = do_vmark_first(ci->home->_data, ci->num, ci->focus);
766         m2 = do_vmark_last(ci->home->_data, ci->num, ci->focus);
767         return comm_call(ci->comm2, "callback:vmark", ci->focus,
768                          0, m, NULL, 0, m2) ?: 1;
769 }
770
771 DEF_CMD(doc_vmarkprev)
772 {
773         struct mark *m = NULL;
774         if (ci->mark)
775                 m = do_vmark_at_or_before(ci->home->_data, ci->mark,
776                                            ci->num, ci->focus);
777         comm_call(ci->comm2, "callback:vmark", ci->focus, 0, m);
778         return 1;
779 }
780
781 DEF_CMD(doc_vmarknew)
782 {
783         struct mark *m;
784
785         m = doc_new_mark(ci->home, ci->num, ci->focus);
786         comm_call(ci->comm2, "callback:vmark", ci->focus, 0, m);
787         return 1;
788 }
789
790 DEF_CMD(doc_drop_cache)
791 {
792         struct pane *p = ci->home;
793         struct doc *d = p->_data;
794
795         if (d->autoclose)
796                 pane_close(p);
797         return 1;
798 }
799
800 DEF_CMD(doc_delayed_close)
801 {
802         struct pane *p = ci->home;
803         int ret;
804
805         /* If there are any doc-displays open, then will return '1' and
806          * we will know not to destroy document yet.
807          */
808         ret = pane_notify("doc:notify-viewers", p);
809         if (ret == 0)
810                 call("doc:drop-cache", p);
811         return 1;
812 }
813
814 DEF_CMD(doc_do_closed)
815 {
816         struct pane *p = ci->home;
817         struct pane *child;
818
819         /* Close the path of filters from doc to focus */
820         child = pane_my_child(p, ci->focus);
821         if (child)
822                 pane_close(child);
823
824         call_comm("event:on-idle", p, &doc_delayed_close, 1);
825         return 1;
826 }
827
828 DEF_CMD(doc_do_destroy)
829 {
830         pane_close(ci->home);
831         return 1;
832 }
833
834 DEF_CMD(doc_get_point)
835 {
836         struct doc_data *dd = ci->home->data;
837         int mnum = 0;
838
839         if (ci->num >= 1 && ci->num <= 4)
840                 mnum = ci->num - 1;
841
842         comm_call(ci->comm2, "callback", ci->focus, 0, dd->point, NULL,
843                   0, dd->marks[mnum]);
844         return 1;
845 }
846
847 DEF_CMD(doc_default_content)
848 {
849         /* doc:content delivers one char at a time to a callback.
850          * This is used for 'search' and 'copy'.
851          * This default version calls doc:char which is simple, but might
852          * be slow.
853          *
854          * If called as doc:content-bytes: return bytes, not chars
855          *
856          * .mark is 'location': to start.  This is not moved.
857          * .mark2, if set, is location to stop.
858          * .comm2 is 'consume': pass char mark and report if finished.
859          *
860          * comm2 is passed:
861          * .mark - the mark that was passed in and gets moved
862          * .num - char character just before .mark
863          * .str - num utf8 text after mark.  It may not be present
864          *       and if it is, at most .num2 bytes can be used
865          * .num2 - usable length of .str
866          *
867          * comm2 it typically embedded in another struct that can
868          * be accessed in the callback (using container_of in C code).
869          * If the caller need to know where the callback aborted, the
870          * callback need to record that somehow.
871          *
872          * comm2 should return 1 if the main char was consumed,
873          * 1+n if n bytes (not chars) from str were consumed
874          * -ve to abort.
875          *
876          * If the callback processes some of 'str', the mark will no longer
877          * be accurate.  If it needs an accurate mark, it can walk a copy
878          * forward, or return a suitable count and be called again with an
879          * accurate mark.
880          */
881         struct mark *m = ci->mark;
882         int nxt;
883         char *cmd = "doc:char";
884
885         if (!m || !ci->comm2)
886                 return Enoarg;
887         m = mark_dup(m);
888         if (strcmp(ci->key, "doc:content-bytes") == 0)
889                 cmd = "doc:byte";
890
891         nxt = call(cmd, ci->home, 1, m);
892         while (nxt > 0 && nxt != CHAR_RET(WEOF) &&
893                (!ci->mark2 || mark_ordered_or_same(m, ci->mark2)) &&
894                comm_call(ci->comm2, "consume", ci->home, (nxt & 0x1FFFF), m) > 0)
895                 nxt = call(cmd, ci->home, 1, m);
896
897         mark_free(m);
898         return nxt < 0 ? nxt : 1;
899 }
900
901 DEF_CMD(doc_insert_char)
902 {
903         const char *str = ksuffix(ci, "doc:char-");
904
905         return call("doc:replace", ci->focus, 1, NULL, str, ci->num2,
906                     ci->mark);
907 }
908
909 struct getstr {
910         struct buf b;
911         struct mark *end;
912         struct command c;
913         int bytes;
914 };
915
916 DEF_CB(get_str_callback)
917 {
918         /* First char will be in ci->num and ->mark will be *after* that char.
919          * Some more chars might be in ->str (for ->num2 bytes).
920          * If ->x, then expect that many bytes (approximately).
921          * Return Efalse to stop, 1 if char was consumed,
922          * 1+N (N <= ->num2) if N bytes from ->str were consumed.
923          */
924         wint_t wch = ci->num & 0x1FFFFF;
925         struct getstr *g = container_of(ci->comm, struct getstr, c);
926
927         if (!ci->mark)
928                 return Enoarg;
929         if (ci->x)
930                 buf_resize(&g->b, ci->x);
931         if (g->bytes)
932                 buf_append_byte(&g->b, ci->num & 0xff);
933         else
934                 buf_append(&g->b, wch);
935         if (g->end && (ci->mark->seq >= g->end->seq ||
936                        mark_same(ci->mark, g->end)))
937                 return Efalse;
938         if (!ci->str || ci->num2 <= 0)
939                 return 1;
940
941         /* This could over-run ->end, but we assume it doesn't */
942         buf_concat_len(&g->b, ci->str, ci->num2);
943         return 1 + ci->num2;
944 }
945
946 DEF_CMD(doc_get_str)
947 {
948         /* doc:get-str
949          * uses doc:content to collect the content
950          * into a buf.
951          * If mark and mark2 are both set, they are end points.
952          * If only mark is set we ignore it.  It is likely
953          * 'point' provided by default.
954          */
955         int bytes = strcmp(ci->key, "doc:get-bytes") == 0;
956         struct getstr g;
957         struct mark *from = NULL, *to = NULL;
958
959         if (ci->mark && ci->mark2) {
960                 if (ci->mark2->seq < ci->mark->seq) {
961                         from = ci->mark2;
962                         to = ci->mark;
963                 } else {
964                         from = ci->mark;
965                         to = ci->mark2;
966                 }
967         }
968
969         g.c = get_str_callback;
970         g.bytes = bytes;
971         buf_init(&g.b);
972         g.end = to;
973         if (!from) {
974                 from = mark_new(ci->focus);
975                 if (from)
976                         call("doc:set-ref", ci->focus, 1, from);
977         }
978         if (!from)
979                 return Efail;
980         if (!to) {
981                 to = mark_new(ci->focus);
982                 if (to)
983                         call("doc:set-ref", ci->focus, 0, to);
984         }
985         call_comm(bytes ? "doc:content-bytes" : "doc:content",
986                   ci->focus, &g.c, 0, from, NULL, 0, to);
987         if (from != ci->mark && from != ci->mark2)
988                 mark_free(from);
989         if (to != ci->mark && to != ci->mark2)
990                 mark_free(to);
991         comm_call(ci->comm2, "callback:get-str", ci->focus, g.b.len, NULL,
992                   buf_final(&g.b));
993         free(g.b.b);
994         return 1;
995 }
996
997 DEF_CMD(doc_notify_viewers)
998 {
999         /* The autoclose document wants to know if it should close,
1000          * or a new view wants to find an active point.
1001          * If a mark was provided, move it to point, then
1002          * report that there are active viewers by returning 1
1003          */
1004         struct doc_data *dd = ci->home->data;
1005
1006         if (ci->mark)
1007                 mark_to_mark(ci->mark, dd->point);
1008         return 1;
1009 }
1010
1011 DEF_CMD(doc_notify_moving)
1012 {
1013         struct doc_data *dd = ci->home->data;
1014
1015         if (ci->mark == dd->point)
1016                 pane_damaged(ci->home, DAMAGED_VIEW);
1017         return Efallthrough;
1018 }
1019
1020 DEF_CMD(doc_refresh_view)
1021 {
1022         struct doc_data *dd = ci->home->data;
1023         int active = attr_find_int(dd->point->attrs, "selection:active");
1024
1025         if (active > 0) {
1026                 call("view:changed", ci->focus, 0, dd->point, NULL,
1027                      0, dd->old_point);
1028         } else {
1029                 call("view:changed", ci->focus, 0, dd->point);
1030                 if (dd->old_point)
1031                         call("view:changed", ci->focus, 0, dd->old_point);
1032         }
1033         if (!dd->old_point)
1034                 dd->old_point = mark_dup(dd->point);
1035         else
1036                 mark_to_mark(dd->old_point, dd->point);
1037         mark_watch(dd->point);
1038         return 1;
1039 }
1040
1041 DEF_CMD(doc_notify_close)
1042 {
1043         /* This pane has to go away */
1044         struct doc_data *dd = ci->home->data;
1045
1046         mark_free(dd->point);
1047         dd->point = safe_cast NULL;
1048         pane_close(ci->home);
1049         return 1;
1050 }
1051
1052 DEF_CMD(doc_clone)
1053 {
1054         struct doc_data *dd = ci->home->data;
1055         struct pane *p = doc_attach_assign(ci->focus, dd->doc);
1056
1057         if (!p)
1058                 return Efail;
1059         call("Move-to", p, 0, dd->point);
1060         pane_clone_children(ci->home, p);
1061         return 1;
1062 }
1063
1064 DEF_CMD(doc_close)
1065 {
1066         struct doc_data *dd = ci->home->data;
1067         int i;
1068         call("doc:push-point", dd->doc, 0, dd->point);
1069         mark_free(dd->point);
1070         mark_free(dd->old_point);
1071         for (i = 0; i < 4; i++)
1072                 mark_free(dd->marks[i]);
1073         call("doc:closed", dd->doc);
1074         return 1;
1075 }
1076
1077 DEF_CMD(doc_dup_point)
1078 {
1079         struct doc_data *dd = ci->home->data;
1080         struct mark *pt = dd->point;
1081         struct mark *m;
1082         if (ci->mark && ci->mark->viewnum == MARK_POINT)
1083                 pt = ci->mark;
1084
1085         if (!pt || !ci->comm2)
1086                 return Enoarg;
1087
1088         if (ci->num2 == MARK_POINT)
1089                 m = point_dup(pt);
1090         else if (ci->num2 == MARK_UNGROUPED)
1091                 m = mark_dup(pt);
1092         else
1093                 m = do_mark_at_point(pt, ci->num2);
1094
1095         comm_call(ci->comm2, "callback:dup-point", ci->focus,
1096                   0, m);
1097         return 1;
1098 }
1099
1100 DEF_CMD(doc_replace)
1101 {
1102         struct doc_data *dd = ci->home->data;
1103         return call("doc:replace", ci->focus,
1104                     1, ci->mark, ci->str,
1105                     ci->num2, dd->point, ci->str2);
1106 }
1107
1108 DEF_CMD(doc_handle_get_attr)
1109 {
1110         struct doc_data *dd = ci->home->data;
1111         char *a;
1112         if (!ci->str)
1113                 return Enoarg;
1114         a = pane_attr_get(dd->doc, ci->str);
1115         if (!a)
1116                 return Efallthrough;
1117         return comm_call(ci->comm2, "callback", ci->focus, 0, NULL, a) ?: 1;
1118 }
1119
1120 DEF_CMD(doc_move_to)
1121 {
1122         struct doc_data *dd = ci->home->data;
1123         struct mark *m;
1124
1125         if (ci->num == 0) {
1126                 if (ci->mark)
1127                         mark_to_mark(dd->point, ci->mark);
1128         } else if (ci->num > 0 && ci->num <= 4) {
1129                 int mnum = ci->num - 1;
1130
1131                 if (!dd->marks[mnum]) {
1132                         dd->marks[mnum] = mark_dup(dd->point);
1133                         if (!dd->marks[mnum])
1134                                 return Efail;
1135                         if (mnum == 0)
1136                                 attr_set_str(&dd->marks[mnum]->attrs,
1137                                              "render:interactive-mark", "yes");
1138                 }
1139                 m = ci->mark ?: dd->point;
1140                 mark_to_mark(dd->marks[mnum], m);
1141                 /* Make sure mark is *before* point so insertion
1142                  * leaves mark alone */
1143                 mark_step(dd->marks[mnum], 0);
1144         } else if (ci->num < 0 && ci->num >= -4) {
1145                 int mnum = -1 - ci->num;
1146
1147                 mark_free(dd->marks[mnum]);
1148                 dd->marks[mnum] = NULL;
1149         } else
1150                 return Efail;
1151         return 1;
1152 }
1153
1154 DEF_CMD(doc_clip)
1155 {
1156         struct doc_data *dd = ci->home->data;
1157         int mnum;
1158
1159         mark_clip(dd->point, ci->mark, ci->mark2, !!ci->num);
1160         if (dd->old_point)
1161                 mark_clip(dd->old_point, ci->mark, ci->mark2, !!ci->num);
1162         for (mnum = 0; mnum < 4; mnum++)
1163                 if (dd->marks[mnum])
1164                         mark_clip(dd->marks[mnum], ci->mark, ci->mark2, !!ci->num);
1165         return 1;
1166 }
1167
1168 DEF_CMD(doc_pass_on)
1169 {
1170         struct doc_data *dd = ci->home->data;
1171         int ret = home_call(dd->doc, ci->key, ci->focus, ci->num,
1172                             ci->mark ?: dd->point, ci->str,
1173                             ci->num2, ci->mark2, ci->str2,
1174                             ci->x, ci->y, ci->comm2);
1175         return ret;
1176 }
1177
1178 DEF_CMD(doc_push_point)
1179 {
1180         struct doc *d = ci->home->_data;
1181         int n = ARRAY_SIZE(d->recent_points);
1182         struct mark *m;
1183         if (!ci->mark)
1184                 return Enoarg;
1185         mark_free(d->recent_points[n-1]);
1186         memmove(&d->recent_points[1],
1187                 &d->recent_points[0],
1188                 (n-1)*sizeof(d->recent_points[0]));
1189         m = mark_dup(ci->mark);
1190         m->attrs = attr_copy(ci->mark->attrs);
1191         d->recent_points[0] = m;
1192         return 1;
1193 }
1194
1195 DEF_CMD(doc_pop_point)
1196 {
1197         struct doc *d = ci->home->_data;
1198         int n = ARRAY_SIZE(d->recent_points);
1199
1200         if (!ci->mark)
1201                 return Enoarg;
1202         if (!d->recent_points[0])
1203                 return Efail;
1204         mark_to_mark(ci->mark, d->recent_points[0]);
1205         if (!ci->mark->attrs) {
1206                 ci->mark->attrs = d->recent_points[0]->attrs;
1207                 d->recent_points[0]->attrs = NULL;
1208         }
1209         mark_free(d->recent_points[0]);
1210         memmove(&d->recent_points[0],
1211                 &d->recent_points[1],
1212                 (n-1) * sizeof(d->recent_points[0]));
1213         d->recent_points[n-1] = NULL;
1214         return 1;
1215 }
1216
1217 DEF_CMD(doc_attach_view)
1218 {
1219         struct pane *focus = ci->focus;
1220         struct pane *doc = ci->home;
1221         struct pane *p, *p2;
1222         char *s;
1223         const char *type = ci->str ?: "default";
1224
1225         if (strcmp(type, "invisible") != 0) {
1226                 if (doc == focus)
1227                         /* caller is confused. */
1228                         return Einval;
1229                 /* Double check the focus can display things */
1230                 if (call("Draw:text", focus) == Efallthrough)
1231                         return Einval;
1232         }
1233
1234         p = doc_attach_assign(focus, doc);
1235         if (!p)
1236                 return Efail;
1237
1238         call("doc:notify:doc:revisit", p, ci->num);
1239         if (strcmp(type, "invisible") != 0) {
1240                 /* Attach renderer */
1241                 p2 = call_ret(pane, "attach-view", p);
1242                 if (!p2)
1243                         goto out;
1244                 p = p2;
1245
1246                 s = strconcat(p, "render-", type);
1247                 if (s)
1248                         s = pane_attr_get(doc, s);
1249                 if (!s)
1250                         s = pane_attr_get(doc, "render-default");
1251                 if (!s)
1252                         goto out;
1253                 s = strconcat(p, "attach-render-", s);
1254                 p2 = call_ret(pane, s, p);
1255                 if (!p2)
1256                         goto out;
1257                 p = p2;
1258
1259                 s = strconcat(p, "view-", type);
1260                 if (s)
1261                         s = pane_attr_get(doc, s);
1262                 if (!s)
1263                         s = pane_attr_get(doc, "view-default");
1264                 if (s) {
1265                         char *s2;
1266                         while ((s2 = strchr(s, ',')) != NULL) {
1267                                 char *s3 = strndup(s, s2-s);
1268                                 p2 = call_ret(pane, strconcat(p, "attach-", s3)
1269                                               , p);
1270                                 free(s3);
1271                                 if (p2)
1272                                         p = p2;
1273                                 s = s2+1;
1274                         }
1275                         s = strconcat(p, "attach-", s);
1276                         p2 = call_ret(pane, s, p);
1277                         if (p2)
1278                                 p = p2;
1279                 }
1280         }
1281 out:
1282         comm_call(ci->comm2, "callback:doc", p);
1283         return 1;
1284 }
1285
1286 DEF_CMD(doc_get_doc)
1287 {
1288         if (!ci->comm2)
1289                 return Enoarg;
1290
1291         comm_call(ci->comm2, "attach", ci->home, ci->num, NULL, ci->str,
1292                   ci->num2, NULL, ci->str2);
1293         return 1;
1294 }
1295
1296 DEF_CMD(doc_abort)
1297 {
1298         struct doc_data *dd = ci->home->data;
1299
1300         call("doc:notify:Abort", dd->doc);
1301         return Efallthrough;
1302 }
1303
1304 struct map *doc_default_cmd safe;
1305 static struct map *doc_handle_cmd safe;
1306
1307 DEF_LOOKUP_CMD(doc_handle, doc_handle_cmd);
1308
1309 static void init_doc_cmds(void)
1310 {
1311         doc_default_cmd = key_alloc();
1312         doc_handle_cmd = key_alloc();
1313
1314         key_add_prefix(doc_handle_cmd, "doc:", &doc_pass_on);
1315
1316         key_add(doc_handle_cmd, "Move-Char", &doc_char);
1317         key_add(doc_handle_cmd, "Move-Line", &doc_line);
1318         key_add(doc_handle_cmd, "Move-View", &doc_page);
1319         key_add(doc_handle_cmd, "doc:point", &doc_get_point);
1320
1321         key_add(doc_handle_cmd, "doc:notify-viewers", &doc_notify_viewers);
1322         key_add(doc_handle_cmd, "Notify:Close", &doc_notify_close);
1323         key_add(doc_handle_cmd, "mark:moving", &doc_notify_moving);
1324         key_add(doc_handle_cmd, "Refresh:view", &doc_refresh_view);
1325         key_add(doc_handle_cmd, "Clone", &doc_clone);
1326         key_add(doc_handle_cmd, "Close", &doc_close);
1327         key_add(doc_handle_cmd, "doc:dup-point", &doc_dup_point);
1328         key_add(doc_handle_cmd, "Replace", &doc_replace);
1329         key_add(doc_handle_cmd, "get-attr", &doc_handle_get_attr);
1330         key_add(doc_handle_cmd, "Move-to", &doc_move_to);
1331         key_add(doc_handle_cmd, "Notify:clip", &doc_clip);
1332         key_add(doc_handle_cmd, "Abort", &doc_abort);
1333
1334         key_add(doc_default_cmd, "doc:add-view", &doc_addview);
1335         key_add(doc_default_cmd, "doc:del-view", &doc_delview);
1336         key_add(doc_default_cmd, "Close:Notify", &doc_view_close);
1337         key_add(doc_default_cmd, "doc:vmark-get", &doc_vmarkget);
1338         key_add(doc_default_cmd, "doc:vmark-prev", &doc_vmarkprev);
1339         key_add(doc_default_cmd, "doc:vmark-new", &doc_vmarknew);
1340         key_add(doc_default_cmd, "get-attr", &doc_get_attr);
1341         key_add(doc_default_cmd, "doc:get-attr", &doc_doc_get_attr);
1342         key_add(doc_default_cmd, "doc:set-name", &doc_set_name);
1343         key_add(doc_default_cmd, "doc:destroy", &doc_do_destroy);
1344         key_add(doc_default_cmd, "doc:drop-cache", &doc_drop_cache);
1345         key_add(doc_default_cmd, "doc:closed", &doc_do_closed);
1346         key_add(doc_default_cmd, "doc:get-str", &doc_get_str);
1347         key_add(doc_default_cmd, "doc:get-bytes", &doc_get_str);
1348         key_add(doc_default_cmd, "doc:content", &doc_default_content);
1349         key_add(doc_default_cmd, "doc:content-bytes", &doc_default_content);
1350         key_add(doc_default_cmd, "doc:push-point", &doc_push_point);
1351         key_add(doc_default_cmd, "doc:pop-point", &doc_pop_point);
1352         key_add(doc_default_cmd, "doc:attach-view", &doc_attach_view);
1353         key_add(doc_default_cmd, "doc:get-doc", &doc_get_doc);
1354         key_add(doc_default_cmd, "Close", &doc_close_doc);
1355
1356         key_add(doc_default_cmd, "doc:word", &doc_word);
1357         key_add(doc_default_cmd, "doc:WORD", &doc_WORD);
1358         key_add(doc_default_cmd, "doc:EOL", &doc_eol);
1359         key_add(doc_default_cmd, "doc:file", &doc_file);
1360         key_add(doc_default_cmd, "doc:expr", &doc_expr);
1361         key_add(doc_default_cmd, "doc:paragraph", &doc_para);
1362
1363         key_add_prefix(doc_default_cmd, "doc:char-", &doc_insert_char);
1364         key_add_prefix(doc_default_cmd, "doc:request:",
1365                        &doc_request_notify);
1366         key_add_prefix(doc_default_cmd, "doc:notify:", &doc_notify);
1367         key_add_prefix(doc_default_cmd, "doc:set:", &doc_set);
1368         key_add_prefix(doc_default_cmd, "doc:append:", &doc_append);
1369 }
1370
1371 static struct pane *doc_attach_assign(struct pane *parent safe, struct pane *doc safe)
1372 {
1373         struct pane *p;
1374         struct doc_data *dd;
1375         struct mark *m;
1376
1377         p = pane_register(parent, 0, &doc_handle.c);
1378         if (!p)
1379                 return NULL;
1380         dd = p->data;
1381         pane_damaged(p, DAMAGED_VIEW);
1382
1383         m = point_new(doc);
1384         if (!m) {
1385                 pane_close(p);
1386                 return NULL;
1387         }
1388         if (call("doc:pop-point", doc, 0, m) <= 0)
1389                 pane_notify("doc:notify-viewers", doc, 0, m);
1390         dd->doc = doc;
1391         dd->point = m;
1392         attr_set_str(&m->attrs, "render:interactive-point", "yes");
1393
1394         pane_add_notify(p, doc, "Notify:Close");
1395         pane_add_notify(p, doc, "doc:notify-viewers");
1396         pane_add_notify(p, doc, "mark:moving");
1397         call("doc:notify:doc:revisit", doc, 0);
1398         mark_watch(m);
1399         return p;
1400 }
1401
1402 static void simplify_path(const char *path safe, char *buf safe)
1403 {
1404         /* Like readpath, but doesn't process symlinks,
1405          * so only "..", "." and extra '/' are handled
1406          * Assumes that 'path' starts with a '/'.
1407          */
1408         const char *p;
1409         const char *end;
1410         char *b = buf;
1411
1412         for (p = path; *p; p = end) {
1413                 int len;
1414                 end = strchrnul(p+1, '/');
1415                 len = end - p;
1416
1417                 if (len == 1)
1418                         /* Extra '/' at end or in the middle, ignore */
1419                         continue;
1420                 if (len == 2 && strstarts(p, "/.") )
1421                         /* Ignore the dot */
1422                         continue;
1423                 if (len == 3 && strstarts(p, "/..")) {
1424                         /* strip last component of buf */
1425                         while (b > buf && b[-1] != '/')
1426                                 b -= 1;
1427                         if (b > buf)
1428                                 b -= 1;
1429                         continue;
1430                 }
1431                 /* Append component to buf */
1432                 strncpy(b, p, len);
1433                 b += len;
1434         }
1435         if (b == buf)
1436                 /* This is the only case where we allow a trailing '/' */
1437                 *b++ = '/';
1438         *b = 0;
1439 }
1440
1441 DEF_CMD(doc_open)
1442 {
1443         struct pane *ed = ci->home;
1444         int fd = ci->num;
1445         char *name;
1446         char *realname = NULL;
1447         struct stat stb;
1448         struct pane *p;
1449         int autoclose = ci->num2 & 1;
1450         int create_ok = ci->num2 & 4;
1451         int reload = ci->num2 & 8;
1452         int force_reload = ci->num2 & 16;
1453         int quiet = ci->num2 & 32;
1454         int only_existing = ci->num2 & 64;
1455         char pathbuf[PATH_MAX];
1456
1457         if (!ci->str)
1458                 return Enoarg;
1459
1460         stb.st_mode = 0;
1461         /* fd < -1 mean a non-filesystem name */
1462         if (fd >= -1) {
1463                 char *sl = NULL, *rp, *restore = NULL;
1464                 char *dir;
1465                 /* First, make sure we have an absolute path, and
1466                  * simplify it.
1467                  */
1468                 if (ci->str[0] != '/') {
1469                         char *c = getcwd(pathbuf, sizeof(pathbuf));
1470                         if (c) {
1471                                 name = strconcat(ed, c, "/", ci->str);
1472                                 simplify_path(name, pathbuf);
1473                                 name = strsave(ed, pathbuf);
1474                         } else
1475                                 name = strsave(ed, ci->str);
1476                 } else {
1477                         simplify_path(ci->str, pathbuf);
1478                         name = strsave(ed, pathbuf);
1479                 }
1480                 if (!name)
1481                         return Efail;
1482                 /* Now try to canonicalize directory part of name as realname */
1483                 dir = name;
1484                 if (dir)
1485                         sl = strrchr(dir, '/');
1486                 if (sl && sl[1] && strcmp(sl, "/.") != 0 && strcmp(sl, "/..") != 0) {
1487                         /* Found a real basename */
1488                         restore = sl;
1489                         *sl++ = '\0';
1490                 } else if (sl) {
1491                         /* Cannot preserve basename in relative path */
1492                         sl = "";
1493                 } else {
1494                         sl = name;
1495                         dir = ".";
1496                 }
1497                 rp = realpath(dir, pathbuf);
1498                 if (rp && sl)
1499                         realname = strconcat(ed, rp, "/", sl);
1500                 else if (rp)
1501                         realname = rp;
1502                 else
1503                         realname = NULL;
1504                 if (restore)
1505                         *restore = '/';
1506         } else
1507                 name = (char*)ci->str;
1508
1509         if (fd == -1)
1510                 /* No open yet */
1511                 fd = open(name, O_RDONLY);
1512
1513         if (fd >= 0)
1514                 fstat(fd, &stb);
1515         else if (create_ok)
1516                 stb.st_mode = S_IFREG;
1517         else
1518                 stb.st_mode = 0;
1519
1520         p = call_ret(pane, "docs:byfd", ed, 0, NULL, name, fd);
1521
1522         if (!p) {
1523                 if (only_existing) {
1524                         if (fd != ci->num)
1525                                 close(fd);
1526                         return Efalse;
1527                 }
1528                 p = call_ret(pane, "global-multicall-open-doc-", ed,
1529                              fd, NULL, name,
1530                              stb.st_mode & S_IFMT);
1531
1532                 if (!p) {
1533                         if (fd != ci->num)
1534                                 close(fd);
1535                         return Efail;
1536                 }
1537                 if (autoclose)
1538                         call("doc:set:autoclose", p, 1);
1539                 call("doc:load-file", p, 0, NULL, name, fd);
1540                 call("global-multicall-doc:appeared-", p);
1541         } else {
1542                 char *n;
1543                 n = pane_attr_get(p, "filename");
1544                 if (n && strlen(n) > 1 && n[strlen(n)-1] == '/') {
1545                         /* Make sure both end in '/' if either do */
1546                         if (realname)
1547                                 realname = strconcat(ed, realname, "/");
1548                         name = strconcat(ed, name, "/");
1549                 }
1550                 if (!quiet && n && realname && strcmp(n, realname) != 0)
1551                         call("Message", ci->focus, 0, NULL,
1552                              strconcat(ci->focus, "File ", realname, " and ", n,
1553                                        " are the same"));
1554                 else if (!quiet && n && strcmp(n, name) != 0)
1555                         call("Message", ci->focus, 0, NULL,
1556                              strconcat(ci->focus, "File ", name, " and ", n,
1557                                        " are the same"));
1558
1559                 if (reload || force_reload)
1560                         call("doc:load-file", p, force_reload?0:1, NULL, name,
1561                              fd);
1562         }
1563         if (fd != ci->num)
1564                 close(fd);
1565         return comm_call(ci->comm2, "callback", p) ?: 1;
1566 }
1567
1568 DEF_CMD(doc_from_text)
1569 {
1570         const char *name = ci->str;
1571         const char *text = ci->str2;
1572         struct pane *p;
1573
1574         p = call_ret(pane, "attach-doc-text", ci->focus);
1575         if (!p)
1576                 return Efail;
1577         if (name) {
1578                 call("doc:set-name", p, 0, NULL, name);
1579                 call("global-multicall-doc:appeared-", p);
1580         }
1581         call("doc:replace", p, 1, NULL, text);
1582         return comm_call(ci->comm2, "callback", p) ?: 1;
1583 }
1584
1585 void doc_free(struct doc *d safe, struct pane *root safe)
1586 {
1587         /* NOTE: this must be idempotent as both it can be called
1588          * twice, once from doc_default_cmd, once deliberately by the
1589          * pane.
1590          */
1591         unsigned int i;
1592         bool warned = False;
1593
1594         for (i = 0; i < ARRAY_SIZE(d->recent_points); i++) {
1595                 mark_free(d->recent_points[i]);
1596                 d->recent_points[i] = NULL;
1597         }
1598         for (i = 0; i < (unsigned int)d->nviews; i++)
1599                 if (d->views)
1600                         do_del_view(d, i, d->views[i].owner);
1601         unalloc_buf(d->views, sizeof(d->views[0]) * d->nviews, pane);
1602         free(d->name);
1603         d->name = NULL;
1604         while (!hlist_empty(&d->marks)) {
1605                 struct mark *m = hlist_first_entry(&d->marks, struct mark, all);
1606                 if (m->viewnum == MARK_UNGROUPED && m->mdata) {
1607                         /* we cannot free this, so warn and discard */
1608                         if (!warned) {
1609                                 call("editor:notify:Message:broadcast",
1610                                      root, 0, NULL,
1611                                      "WARNING mark with data not freed");
1612                                 LOG("WARNING Mark with data no freed");
1613                         }
1614                         warned = True;
1615                         m->mdata = NULL;
1616                 }
1617                 if (m->viewnum == MARK_POINT || m->viewnum == MARK_UNGROUPED)
1618                         mark_free(m);
1619                 else
1620                         /* vmarks should have gone already */
1621                         ASSERT(0);
1622         }
1623 }
1624
1625 void doc_setup(struct pane *ed safe)
1626 {
1627         call_comm("global-set-command", ed, &doc_open, 0, NULL, "doc:open");
1628         call_comm("global-set-command", ed, &doc_from_text, 0, NULL,
1629                   "doc:from-text");
1630         if (!(void*)doc_default_cmd)
1631                 init_doc_cmds();
1632 }