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