2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
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.
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
20 #define _GNU_SOURCE for strchrnul
29 #define PRIVATE_DOC_REF
35 #define PANE_DATA_TYPE struct doc_data
36 #define DOC_DATA_TYPE struct doc
41 /* this is ->data for a document reference pane.
44 struct pane *doc safe;
45 struct mark *point safe;
46 struct mark *old_point; /* location at last refresh */
47 struct mark *marks[4];
49 #include "core-pane.h"
51 static struct pane *doc_attach_assign(struct pane *parent safe, struct pane *doc safe);
54 static void doc_init(struct doc *d safe)
56 INIT_HLIST_HEAD(&d->marks);
57 INIT_TLIST_HEAD(&d->points, 0);
61 memset(d->recent_points, 0, sizeof(d->recent_points));
67 struct pane *do_doc_register(struct pane *parent safe,
68 struct command *handle safe,
69 unsigned short data_size)
73 if (data_size < sizeof(struct doc))
74 /* Not enough room for the doc ! */
77 /* Documents are always registered against the root */
78 parent = pane_root(parent);
79 p = do_pane_register(parent, 0, handle, NULL, data_size);
86 /* For these 'default commands', home->data is struct doc */
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);
98 if (doc_next(f,m) == WEOF)
103 if (doc_prev(f,m) == WEOF)
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.
117 struct pane *f = ci->focus;
118 struct mark *m = ci->mark;
119 int rpt = RPT_NUM(ci);
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.
134 while (rpt > 0 && wi != WEOF) {
135 while ((wi = doc_following(f, m)) != WEOF &&
138 while ((wi = doc_following(f, m)) != WEOF &&
139 !iswspace(wi) && !iswalnum(wi))
141 while ((wi = doc_following(f, m)) != WEOF &&
146 while (rpt < 0 && wi != WEOF) {
147 while ((wi = doc_prior(f, m)) != WEOF &&
150 while ((wi = doc_prior(f, m)) != WEOF &&
151 !iswspace(wi) && !iswalnum(wi))
153 while ((wi = doc_prior(f, m)) != WEOF &&
158 return rpt == 0 ? 1 : Efalse;
161 static bool check_slosh(struct pane *p safe, struct mark *m safe)
164 /* Check is preceded by exactly 1 '\' */
165 if (doc_prior(p, m) != '\\')
168 ch = doc_prior(p, m);
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)
185 * 'str' can be set to extra chars that should be included in words.
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;
194 const char *wordchars = ci->str ?: "";
195 const char *special safe = "[](){}'\"";
200 open = "([{"; close = ")]}";
202 open = ")]}"; close = "([{";
207 while ((wi = doc_pending(f, m, dir)) != WEOF
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)))
217 if (strchr(close, wi)) {
218 if (dir < 0 && enter_leave) {
224 } else if (strchr(open, wi)) {
225 /* skip bracketed expression */
230 if (enter_leave && dir > 0)
231 /* Just entered the expression */
233 else while (depth > 0 &&
234 (wi = doc_move(f, m, dir)) != WEOF) {
238 if ((!check_slosh(f, m) && wi == q) ||
243 } else if (strchr(open, wi))
245 else if (strchr(close, wi))
247 else if (wi == '"' || wi == '\'') {
250 if (!check_slosh(f, m))
256 } else if (wi == '"' || wi == '\'') {
257 /* skip quoted or to EOL */
261 slosh = check_slosh(f, m);
265 slosh = check_slosh(f, m);
268 while (((wi = doc_pending(f, m, dir))
272 slosh = check_slosh(f, m);
276 slosh = check_slosh(f, m);
278 if (wi == q && !slosh)
282 } else while (((wi=doc_pending(f, m, dir)) != WEOF && iswalnum(wi)) ||
283 (wi > 0 && wi <= 255 &&
284 strchr(wordchars, wi) != NULL))
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.
303 struct pane *f = ci->focus;
304 struct mark *m = ci->mark;
305 int rpt = RPT_NUM(ci);
310 /* We skip spaces, then non-spaces */
314 while ((wi = doc_following(f, m)) != WEOF &&
318 while ((wi = doc_following(f, m)) != WEOF &&
326 while ((wi = doc_prior(f, m)) != WEOF &&
329 while ((wi = doc_prior(f, m)) != WEOF &&
335 return rpt == 0 ? 1 : Efalse;
340 struct pane *f = ci->focus;
341 struct mark *m = ci->mark;
343 int rpt = RPT_NUM(ci);
344 bool one_more = ci->num2 > 0;
349 while (rpt > 0 && ch != WEOF) {
350 while ((ch = doc_next(f, m)) != WEOF &&
356 while (rpt < 0 && ch != WEOF) {
357 while ((ch = doc_prev(f, m)) != WEOF &&
367 else if (RPT_NUM(ci) < 0)
373 else if (RPT_NUM(ci) < 0)
377 return rpt == 0 ? 1 : Efalse;
382 int rpt = RPT_NUM(ci);
383 struct mark *m = ci->mark;
388 call("doc:set-ref", ci->focus, (rpt < 0), m);
395 struct pane *p = ci->focus;
396 struct doc_data *dd = ci->home->data;
397 struct mark *m = ci->mark;
399 int rpt = RPT_NUM(ci);
404 while (rpt > 0 && ch != WEOF) {
405 while ((ch = doc_next(p, m)) != WEOF &&
410 while (rpt < 0 && ch != WEOF) {
411 while ((ch = doc_prev(p, m)) != WEOF &&
421 /* Default paragraph move - find blank line - two or more
423 * If moving forward, skip over all those chars.
424 * If moving backward, stop before the first one.
426 struct pane *p = ci->focus;
427 struct mark *m = ci->mark;
428 int rpt = RPT_NUM(ci);
431 int dir = rpt > 0 ? 1 : -1;
436 while (dir < 0 && is_eol(doc_prior(p, m)))
439 while (rpt && ch != WEOF) {
442 ch = doc_move(p, m, dir);
448 doc_move(p, m, -dir);
455 while (dir < 0 && nlcnt-- > 0)
462 struct pane *p = ci->focus;
463 struct doc_data *dd = ci->home->data;
464 struct mark *m = ci->mark, *old;
466 int rpt = RPT_NUM(ci);
473 /* repeat count is in 1000th of the pane */
475 while (rpt > 0 && ch != WEOF) {
476 while ((ch = doc_next(p, m)) != WEOF &&
481 while (rpt < 0 && ch != WEOF) {
482 while ((ch = doc_prev(p, m)) != WEOF &&
487 if (mark_same(m, old)) {
497 struct doc *d = &ci->home->doc;
498 const char *val = ksuffix(ci, "doc:set:");
505 if (strcmp(val, "autoclose") == 0) {
506 d->autoclose = ci->num;
509 if (strcmp(val, "readonly") == 0) {
510 d->readonly = ci->num;
511 call("doc:notify:doc:status-changed", ci->home);
515 attr_set_str(&ci->home->attrs, val, ci->str);
522 struct pane *p = ci->home;
523 const char *attr = ksuffix(ci, "doc:append:");
524 const char *val = ci->str;
534 /* Append the string to the attr. It attr doesn't
535 * exists, strip first char of val and use that.
537 old = attr_find(p->attrs, attr);
539 attr_set_str(&p->attrs, attr, val+1);
541 const char *pos = strstr(old, val+1);
542 int len = strlen(val+1);
544 (pos == old || pos[-1] == val[0]) &&
545 (pos[len] == 0 || pos[len] == val[0]))
546 ; /* val already present */
548 attr_set_str(&p->attrs, attr, strconcat(p, old, val));
553 DEF_CMD(doc_get_attr)
555 struct doc *d = &ci->home->doc;
556 char pathbuf[PATH_MAX];
562 if ((a = attr_find(ci->home->attrs, ci->str)) != NULL)
564 else if (strcmp(ci->str, "doc-name") == 0)
566 else if (strcmp(ci->str, "doc-modified") == 0)
568 else if (strcmp(ci->str, "doc-readonly") == 0) {
569 a = d->readonly ? "yes":"no";
570 } else if (strcmp(ci->str, "dirname") == 0) {
572 a = pane_attr_get(ci->home, "filename");
574 a = realpath(".", pathbuf);
575 if (a != pathbuf && a)
578 strcat(pathbuf, "/");
579 a = strsave(ci->focus, pathbuf);
581 sl = strrchr(a, '/');
584 a = strnsave(ci->focus, a, (sl-a)+1);
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");
591 a = realpath(a, pathbuf);
592 if (a && a != pathbuf)
595 strcat(pathbuf, "/");
598 attr_set_str(&ci->home->attrs, "realdir", a);
601 return comm_call(ci->comm2, "callback:get_attr", ci->focus, 0,
606 DEF_CMD(doc_doc_get_attr)
608 /* If the document doesn't provide the attribute for
609 * this location, see if there is a pane-attribute for
616 a = pane_attr_get(ci->home, ci->str);
618 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, a);
622 DEF_CMD(doc_set_name)
624 struct doc *d = &ci->home->doc;
629 d->name = strdup(ci->str);
630 return call("doc:notify:doc:revisit", ci->home, ci->num) ?: 1;
633 DEF_CMD(doc_request_notify)
635 pane_add_notify(ci->focus, ci->home, ksuffix(ci, "doc:request:"));
639 DEF_CMD_CLOSED(doc_notify)
641 /* Key is "doc:notify:..." */
642 int ret = pane_notify(ksuffix(ci, "doc:notify:"),
644 ci->num, ci->mark, ci->str,
645 ci->num2, ci->mark2, ci->str2, ci->comm2);
649 static int do_del_view(struct doc *d safe, int v,
653 /* This view should only have points on the list, not typed
654 * marks. Just delete everything and clear the 'notify' pointer
656 if (v < 0 || v >= d->nviews || d->views == NULL ||
657 !owner || d->views[v].owner != owner)
660 d->views[v].owner = NULL;
661 while (!tlist_empty(&d->views[v].head)) {
663 struct tlist_head *tl = d->views[v].head.next;
665 switch (TLIST_TYPE(tl)) {
666 case GRP_LIST: /* A point */
669 case GRP_MARK: /* a vmark */
670 m = container_of(tl, struct mark, view);
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",
678 "WARNING mark not freed by Close:mark");
679 LOG("WARNING Mark on %s not freed by Close:mark",
687 default: /* impossible */
696 struct doc *d = &ci->home->doc;
698 return do_del_view(d, ci->num, ci->focus);
703 struct doc *d = &ci->home->doc;
708 for (ret = 0; d->views && ret < d->nviews; ret++)
709 if (d->views[ret].owner == NULL)
711 if (!d->views || ret == d->nviews) {
712 /* Resize the view list */
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;
720 for (; i < d->nviews; i++) {
721 INIT_TLIST_HEAD(&g[i].head, GRP_HEAD);
724 unalloc_buf(d->views, sizeof(*g)*(d->nviews - 4), pane);
726 /* now resize all the points */
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");
737 DEF_CMD_CLOSED(doc_close_doc)
739 struct doc *d = &ci->home->doc;
740 doc_free(d, ci->home);
744 DEF_CMD_CLOSED(doc_view_close)
746 /* A pane which once held a view is closing. We must discard
747 * that view if it still exists.
749 struct doc *d = &ci->home->doc;
752 for (v = 0 ; d->views && v < d->nviews; v++)
753 do_del_view(d, v, ci->focus);
757 DEF_CMD(doc_vmarkget)
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;
766 DEF_CMD(doc_vmarkprev)
768 struct mark *m = NULL;
770 m = do_vmark_at_or_before(&ci->home->doc, ci->mark,
772 comm_call(ci->comm2, "callback:vmark", ci->focus, 0, m);
776 DEF_CMD(doc_vmarknew)
780 m = doc_new_mark(ci->home, ci->num, ci->focus);
781 comm_call(ci->comm2, "callback:vmark", ci->focus, 0, m);
785 DEF_CMD(doc_drop_cache)
787 struct pane *p = ci->home;
788 struct doc *d = &p->doc;
795 DEF_CMD(doc_delayed_close)
797 struct pane *p = ci->home;
800 /* If there are any doc-displays open, then will return '1' and
801 * we will know not to destroy document yet.
803 ret = pane_notify("doc:notify-viewers", p);
805 call("doc:drop-cache", p);
809 DEF_CMD(doc_do_closed)
811 struct pane *p = ci->home;
814 /* Close the path of filters from doc to focus */
815 child = pane_my_child(p, ci->focus);
819 call_comm("event:on-idle", p, &doc_delayed_close, 1);
823 DEF_CMD(doc_do_destroy)
825 pane_close(ci->home);
829 DEF_CMD(doc_get_point)
831 struct doc_data *dd = ci->home->data;
834 if (ci->num >= 1 && ci->num <= 4)
837 comm_call(ci->comm2, "callback", ci->focus, 0, dd->point, NULL,
842 DEF_CMD(doc_default_content)
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
849 * If called as doc:content-bytes: return bytes, not chars
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.
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
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.
867 * comm2 should return 1 if the main char was consumed,
868 * 1+n if n bytes (not chars) from str were consumed
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
876 struct mark *m = ci->mark;
878 char *cmd = "doc:char";
880 if (!m || !ci->comm2)
883 if (strcmp(ci->key, "doc:content-bytes") == 0)
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);
893 return nxt < 0 ? nxt : 1;
896 DEF_CMD(doc_insert_char)
898 const char *str = ksuffix(ci, "doc:char-");
900 return call("doc:replace", ci->focus, 1, NULL, str, ci->num2,
911 DEF_CB(get_str_callback)
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.
919 wint_t wch = ci->num & 0x1FFFFF;
920 struct getstr *g = container_of(ci->comm, struct getstr, c);
925 buf_resize(&g->b, ci->x);
927 buf_append_byte(&g->b, ci->num & 0xff);
929 buf_append(&g->b, wch);
930 if (g->end && (ci->mark->seq >= g->end->seq ||
931 mark_same(ci->mark, g->end)))
933 if (!ci->str || ci->num2 <= 0)
936 /* This could over-run ->end, but we assume it doesn't */
937 buf_concat_len(&g->b, ci->str, ci->num2);
944 * uses doc:content to collect the content
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.
950 int bytes = strcmp(ci->key, "doc:get-bytes") == 0;
952 struct mark *from = NULL, *to = NULL;
954 if (ci->mark && ci->mark2) {
955 if (ci->mark2->seq < ci->mark->seq) {
964 g.c = get_str_callback;
969 from = mark_new(ci->focus);
971 call("doc:set-ref", ci->focus, 1, from);
976 to = mark_new(ci->focus);
978 call("doc:set-ref", ci->focus, 0, to);
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)
984 if (to != ci->mark && to != ci->mark2)
986 comm_call(ci->comm2, "callback:get-str", ci->focus, g.b.len, NULL,
992 DEF_CMD(doc_notify_viewers)
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
999 struct doc_data *dd = ci->home->data;
1002 mark_to_mark(ci->mark, dd->point);
1006 DEF_CMD(doc_notify_moving)
1008 struct doc_data *dd = ci->home->data;
1010 if (ci->mark == dd->point)
1011 pane_damaged(ci->home, DAMAGED_VIEW);
1012 return Efallthrough;
1015 DEF_CMD(doc_refresh_view)
1017 struct doc_data *dd = ci->home->data;
1018 int active = attr_find_int(dd->point->attrs, "selection:active");
1021 call("view:changed", ci->focus, 0, dd->point, NULL,
1024 call("view:changed", ci->focus, 0, dd->point);
1026 call("view:changed", ci->focus, 0, dd->old_point);
1029 dd->old_point = mark_dup(dd->point);
1031 mark_to_mark(dd->old_point, dd->point);
1032 mark_watch(dd->point);
1036 DEF_CMD(doc_notify_close)
1038 /* This pane has to go away */
1039 struct doc_data *dd = ci->home->data;
1041 mark_free(dd->point);
1042 dd->point = safe_cast NULL;
1043 pane_close(ci->home);
1049 struct doc_data *dd = ci->home->data;
1050 struct pane *p = doc_attach_assign(ci->focus, dd->doc);
1054 call("Move-to", p, 0, dd->point);
1055 pane_clone_children(ci->home, p);
1059 DEF_CMD_CLOSED(doc_close)
1061 struct doc_data *dd = ci->home->data;
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);
1072 DEF_CMD(doc_dup_point)
1074 struct doc_data *dd = ci->home->data;
1075 struct mark *pt = dd->point;
1077 if (ci->mark && ci->mark->viewnum == MARK_POINT)
1080 if (!pt || !ci->comm2)
1083 if (ci->num2 == MARK_POINT)
1085 else if (ci->num2 == MARK_UNGROUPED)
1088 m = do_mark_at_point(pt, ci->num2);
1090 comm_call(ci->comm2, "callback:dup-point", ci->focus,
1095 DEF_CMD(doc_replace)
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);
1103 DEF_CMD(doc_handle_get_attr)
1105 struct doc_data *dd = ci->home->data;
1109 a = pane_attr_get(dd->doc, ci->str);
1111 return Efallthrough;
1112 return comm_call(ci->comm2, "callback", ci->focus, 0, NULL, a) ?: 1;
1115 DEF_CMD(doc_move_to)
1117 struct doc_data *dd = ci->home->data;
1122 mark_to_mark(dd->point, ci->mark);
1123 } else if (ci->num > 0 && ci->num <= 4) {
1124 int mnum = ci->num - 1;
1126 if (!dd->marks[mnum]) {
1127 dd->marks[mnum] = mark_dup(dd->point);
1128 if (!dd->marks[mnum])
1131 attr_set_str(&dd->marks[mnum]->attrs,
1132 "render:interactive-mark", "yes");
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;
1142 mark_free(dd->marks[mnum]);
1143 dd->marks[mnum] = NULL;
1151 struct doc_data *dd = ci->home->data;
1154 mark_clip(dd->point, ci->mark, ci->mark2, !!ci->num);
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);
1163 DEF_CMD_CLOSED(doc_pass_on)
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);
1173 DEF_CMD(doc_push_point)
1175 struct doc *d = &ci->home->doc;
1176 int n = ARRAY_SIZE(d->recent_points);
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;
1190 DEF_CMD(doc_pop_point)
1192 struct doc *d = &ci->home->doc;
1193 int n = ARRAY_SIZE(d->recent_points);
1197 if (!d->recent_points[0])
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;
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;
1212 DEF_CMD(doc_attach_view)
1214 struct pane *focus = ci->focus;
1215 struct pane *doc = ci->home;
1216 struct pane *p, *p2;
1218 const char *type = ci->str ?: "default";
1220 if (strcmp(type, "invisible") != 0) {
1222 /* caller is confused. */
1224 /* Double check the focus can display things */
1225 if (call("Draw:text", focus) == Efallthrough)
1229 p = doc_attach_assign(focus, doc);
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);
1241 s = strconcat(p, "render-", type);
1243 s = pane_attr_get(doc, s);
1245 s = pane_attr_get(doc, "render-default");
1248 s = strconcat(p, "attach-render-", s);
1249 p2 = call_ret(pane, s, p);
1254 s = strconcat(p, "view-", type);
1256 s = pane_attr_get(doc, s);
1258 s = pane_attr_get(doc, "view-default");
1261 while ((s2 = strchr(s, ',')) != NULL) {
1262 char *s3 = strndup(s, s2-s);
1263 p2 = call_ret(pane, strconcat(p, "attach-", s3)
1270 s = strconcat(p, "attach-", s);
1271 p2 = call_ret(pane, s, p);
1277 comm_call(ci->comm2, "callback:doc", p);
1281 DEF_CMD(doc_get_doc)
1286 comm_call(ci->comm2, "attach", ci->home, ci->num, NULL, ci->str,
1287 ci->num2, NULL, ci->str2);
1293 struct doc_data *dd = ci->home->data;
1295 call("doc:notify:Abort", dd->doc);
1296 return Efallthrough;
1299 struct map *doc_default_cmd safe;
1300 static struct map *doc_handle_cmd safe;
1302 DEF_LOOKUP_CMD(doc_handle, doc_handle_cmd);
1304 static void init_doc_cmds(void)
1306 doc_default_cmd = key_alloc();
1307 doc_handle_cmd = key_alloc();
1309 key_add_prefix(doc_handle_cmd, "doc:", &doc_pass_on);
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);
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);
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);
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);
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);
1366 static struct pane *doc_attach_assign(struct pane *parent safe, struct pane *doc safe)
1369 struct doc_data *dd;
1372 p = pane_register(parent, 0, &doc_handle.c);
1376 pane_damaged(p, DAMAGED_VIEW);
1383 if (call("doc:pop-point", doc, 0, m) <= 0)
1384 pane_notify("doc:notify-viewers", doc, 0, m);
1387 attr_set_str(&m->attrs, "render:interactive-point", "yes");
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);
1397 static void simplify_path(const char *path safe, char *buf safe)
1399 /* Like readpath, but doesn't process symlinks,
1400 * so only "..", "." and extra '/' are handled
1401 * Assumes that 'path' starts with a '/'.
1407 for (p = path; *p; p = end) {
1409 end = strchrnul(p+1, '/');
1413 /* Extra '/' at end or in the middle, ignore */
1415 if (len == 2 && strstarts(p, "/.") )
1416 /* Ignore the dot */
1418 if (len == 3 && strstarts(p, "/..")) {
1419 /* strip last component of buf */
1420 while (b > buf && b[-1] != '/')
1426 /* Append component to buf */
1431 /* This is the only case where we allow a trailing '/' */
1438 struct pane *ed = ci->home;
1441 char *realname = NULL;
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];
1456 /* fd < -1 mean a non-filesystem name */
1458 char *sl = NULL, *rp, *restore = NULL;
1460 /* First, make sure we have an absolute path, and
1463 if (ci->str[0] != '/') {
1464 char *c = getcwd(pathbuf, sizeof(pathbuf));
1466 name = strconcat(ed, c, "/", ci->str);
1467 simplify_path(name, pathbuf);
1468 name = strsave(ed, pathbuf);
1470 name = strsave(ed, ci->str);
1472 simplify_path(ci->str, pathbuf);
1473 name = strsave(ed, pathbuf);
1477 /* Now try to canonicalize directory part of name as realname */
1480 sl = strrchr(dir, '/');
1481 if (sl && sl[1] && strcmp(sl, "/.") != 0 && strcmp(sl, "/..") != 0) {
1482 /* Found a real basename */
1486 /* Cannot preserve basename in relative path */
1492 rp = realpath(dir, pathbuf);
1494 realname = strconcat(ed, rp, "/", sl);
1502 name = (char*)ci->str;
1506 fd = open(name, O_RDONLY);
1511 stb.st_mode = S_IFREG;
1515 p = call_ret(pane, "docs:byfd", ed, 0, NULL, name, fd);
1518 if (only_existing) {
1523 p = call_ret(pane, "global-multicall-open-doc-", ed,
1525 stb.st_mode & S_IFMT);
1533 call("doc:set:autoclose", p, 1);
1534 call("doc:load-file", p, 0, NULL, name, fd);
1535 call("global-multicall-doc:appeared-", p);
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 */
1542 realname = strconcat(ed, realname, "/");
1543 name = strconcat(ed, name, "/");
1545 if (!quiet && n && realname && strcmp(n, realname) != 0)
1546 call("Message", ci->focus, 0, NULL,
1547 strconcat(ci->focus, "File ", realname, " and ", n,
1549 else if (!quiet && n && strcmp(n, name) != 0)
1550 call("Message", ci->focus, 0, NULL,
1551 strconcat(ci->focus, "File ", name, " and ", n,
1554 if (reload || force_reload)
1555 call("doc:load-file", p, force_reload?0:1, NULL, name,
1560 return comm_call(ci->comm2, "callback", p) ?: 1;
1563 DEF_CMD(doc_from_text)
1565 const char *name = ci->str;
1566 const char *text = ci->str2;
1569 p = call_ret(pane, "attach-doc-text", ci->focus);
1573 call("doc:set-name", p, 0, NULL, name);
1574 call("global-multicall-doc:appeared-", p);
1576 call("doc:replace", p, 1, NULL, text);
1577 return comm_call(ci->comm2, "callback", p) ?: 1;
1580 void doc_free(struct doc *d safe, struct pane *root safe)
1582 /* NOTE: this must be idempotent as both it can be called
1583 * twice, once from doc_default_cmd, once deliberately by the
1587 bool warned = False;
1589 for (i = 0; i < ARRAY_SIZE(d->recent_points); i++) {
1590 mark_free(d->recent_points[i]);
1591 d->recent_points[i] = NULL;
1593 for (i = 0; i < (unsigned int)d->nviews; i++)
1595 do_del_view(d, i, d->views[i].owner);
1596 unalloc_buf(d->views, sizeof(d->views[0]) * d->nviews, pane);
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 */
1604 call("editor:notify:Message:broadcast",
1606 "WARNING mark with data not freed");
1607 LOG("WARNING Mark with data no freed");
1612 if (m->viewnum == MARK_POINT || m->viewnum == MARK_UNGROUPED)
1615 /* vmarks should have gone already */
1620 void doc_setup(struct pane *ed safe)
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,
1625 if (!(void*)doc_default_cmd)