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
40 /* this is ->data for a document reference pane.
43 struct pane *doc safe;
44 struct mark *point safe;
45 struct mark *old_point; /* location at last refresh */
46 struct mark *marks[4];
48 #include "core-pane.h"
50 static struct pane *doc_attach_assign(struct pane *parent safe, struct pane *doc safe);
53 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,
70 unsigned short data_size)
74 if (doc == NULL && data_size < sizeof(*doc))
75 /* Not enough room for the doc ! */
78 /* Documents are always registered against the root */
79 parent = pane_root(parent);
80 p = do_pane_register(parent, 0, handle, doc, data_size);
89 /* For these 'default commands', home->data is struct doc */
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);
101 if (doc_next(f,m) == WEOF)
106 if (doc_prev(f,m) == WEOF)
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.
120 struct pane *f = ci->focus;
121 struct mark *m = ci->mark;
122 int rpt = RPT_NUM(ci);
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.
137 while (rpt > 0 && wi != WEOF) {
138 while ((wi = doc_following(f, m)) != WEOF &&
141 while ((wi = doc_following(f, m)) != WEOF &&
142 !iswspace(wi) && !iswalnum(wi))
144 while ((wi = doc_following(f, m)) != WEOF &&
149 while (rpt < 0 && wi != WEOF) {
150 while ((wi = doc_prior(f, m)) != WEOF &&
153 while ((wi = doc_prior(f, m)) != WEOF &&
154 !iswspace(wi) && !iswalnum(wi))
156 while ((wi = doc_prior(f, m)) != WEOF &&
161 return rpt == 0 ? 1 : Efalse;
164 static bool check_slosh(struct pane *p safe, struct mark *m safe)
167 /* Check is preceded by exactly 1 '\' */
168 if (doc_prior(p, m) != '\\')
171 ch = doc_prior(p, m);
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)
188 * 'str' can be set to extra chars that should be included in words.
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;
197 const char *wordchars = ci->str ?: "";
198 const char *special safe = "[](){}'\"";
203 open = "([{"; close = ")]}";
205 open = ")]}"; close = "([{";
210 while ((wi = doc_pending(f, m, dir)) != WEOF
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)))
220 if (strchr(close, wi)) {
221 if (dir < 0 && enter_leave) {
227 } else if (strchr(open, wi)) {
228 /* skip bracketed expression */
233 if (enter_leave && dir > 0)
234 /* Just entered the expression */
236 else while (depth > 0 &&
237 (wi = doc_move(f, m, dir)) != WEOF) {
241 if ((!check_slosh(f, m) && wi == q) ||
246 } else if (strchr(open, wi))
248 else if (strchr(close, wi))
250 else if (wi == '"' || wi == '\'') {
253 if (!check_slosh(f, m))
259 } else if (wi == '"' || wi == '\'') {
260 /* skip quoted or to EOL */
264 slosh = check_slosh(f, m);
268 slosh = check_slosh(f, m);
271 while (((wi = doc_pending(f, m, dir))
275 slosh = check_slosh(f, m);
279 slosh = check_slosh(f, m);
281 if (wi == q && !slosh)
285 } else while (((wi=doc_pending(f, m, dir)) != WEOF && iswalnum(wi)) ||
286 (wi > 0 && wi <= 255 &&
287 strchr(wordchars, wi) != NULL))
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.
306 struct pane *f = ci->focus;
307 struct mark *m = ci->mark;
308 int rpt = RPT_NUM(ci);
313 /* We skip spaces, then non-spaces */
317 while ((wi = doc_following(f, m)) != WEOF &&
321 while ((wi = doc_following(f, m)) != WEOF &&
329 while ((wi = doc_prior(f, m)) != WEOF &&
332 while ((wi = doc_prior(f, m)) != WEOF &&
338 return rpt == 0 ? 1 : Efalse;
343 struct pane *f = ci->focus;
344 struct mark *m = ci->mark;
346 int rpt = RPT_NUM(ci);
347 bool one_more = ci->num2 > 0;
352 while (rpt > 0 && ch != WEOF) {
353 while ((ch = doc_next(f, m)) != WEOF &&
359 while (rpt < 0 && ch != WEOF) {
360 while ((ch = doc_prev(f, m)) != WEOF &&
370 else if (RPT_NUM(ci) < 0)
376 else if (RPT_NUM(ci) < 0)
380 return rpt == 0 ? 1 : Efalse;
385 int rpt = RPT_NUM(ci);
386 struct mark *m = ci->mark;
391 call("doc:set-ref", ci->focus, (rpt < 0), m);
398 struct pane *p = ci->focus;
399 struct doc_data *dd = ci->home->data;
400 struct mark *m = ci->mark;
402 int rpt = RPT_NUM(ci);
407 while (rpt > 0 && ch != WEOF) {
408 while ((ch = doc_next(p, m)) != WEOF &&
413 while (rpt < 0 && ch != WEOF) {
414 while ((ch = doc_prev(p, m)) != WEOF &&
424 /* Default paragraph move - find blank line - two or more
426 * If moving forward, skip over all those chars.
427 * If moving backward, stop before the first one.
429 struct pane *p = ci->focus;
430 struct mark *m = ci->mark;
431 int rpt = RPT_NUM(ci);
434 int dir = rpt > 0 ? 1 : -1;
439 while (dir < 0 && is_eol(doc_prior(p, m)))
442 while (rpt && ch != WEOF) {
445 ch = doc_move(p, m, dir);
451 doc_move(p, m, -dir);
458 while (dir < 0 && nlcnt-- > 0)
465 struct pane *p = ci->focus;
466 struct doc_data *dd = ci->home->data;
467 struct mark *m = ci->mark, *old;
469 int rpt = RPT_NUM(ci);
476 /* repeat count is in 1000th of the pane */
478 while (rpt > 0 && ch != WEOF) {
479 while ((ch = doc_next(p, m)) != WEOF &&
484 while (rpt < 0 && ch != WEOF) {
485 while ((ch = doc_prev(p, m)) != WEOF &&
490 if (mark_same(m, old)) {
500 struct doc *d = ci->home->_data;
501 const char *val = ksuffix(ci, "doc:set:");
508 if (strcmp(val, "autoclose") == 0) {
509 d->autoclose = ci->num;
512 if (strcmp(val, "readonly") == 0) {
513 d->readonly = ci->num;
514 call("doc:notify:doc:status-changed", ci->home);
518 attr_set_str(&ci->home->attrs, val, ci->str);
525 struct pane *p = ci->home;
526 const char *attr = ksuffix(ci, "doc:append:");
527 const char *val = ci->str;
537 /* Append the string to the attr. It attr doesn't
538 * exists, strip first char of val and use that.
540 old = attr_find(p->attrs, attr);
542 attr_set_str(&p->attrs, attr, val+1);
544 const char *pos = strstr(old, val+1);
545 int len = strlen(val+1);
547 (pos == old || pos[-1] == val[0]) &&
548 (pos[len] == 0 || pos[len] == val[0]))
549 ; /* val already present */
551 attr_set_str(&p->attrs, attr, strconcat(p, old, val));
556 DEF_CMD(doc_get_attr)
558 struct doc *d = ci->home->_data;
559 char pathbuf[PATH_MAX];
565 if ((a = attr_find(ci->home->attrs, ci->str)) != NULL)
567 else if (strcmp(ci->str, "doc-name") == 0)
569 else if (strcmp(ci->str, "doc-modified") == 0)
571 else if (strcmp(ci->str, "doc-readonly") == 0) {
572 a = d->readonly ? "yes":"no";
573 } else if (strcmp(ci->str, "dirname") == 0) {
575 a = pane_attr_get(ci->home, "filename");
577 a = realpath(".", pathbuf);
578 if (a != pathbuf && a)
581 strcat(pathbuf, "/");
582 a = strsave(ci->focus, pathbuf);
584 sl = strrchr(a, '/');
587 a = strnsave(ci->focus, a, (sl-a)+1);
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");
594 a = realpath(a, pathbuf);
595 if (a && a != pathbuf)
598 strcat(pathbuf, "/");
601 attr_set_str(&ci->home->attrs, "realdir", a);
604 return comm_call(ci->comm2, "callback:get_attr", ci->focus, 0,
609 DEF_CMD(doc_doc_get_attr)
611 /* If the document doesn't provide the attribute for
612 * this location, see if there is a pane-attribute for
619 a = pane_attr_get(ci->home, ci->str);
621 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, a);
625 DEF_CMD(doc_set_name)
627 struct doc *d = ci->home->_data;
632 d->name = strdup(ci->str);
633 return call("doc:notify:doc:revisit", ci->home, ci->num) ?: 1;
636 DEF_CMD(doc_request_notify)
638 pane_add_notify(ci->focus, ci->home, ksuffix(ci, "doc:request:"));
644 /* Key is "doc:notify:..." */
645 int ret = pane_notify(ksuffix(ci, "doc:notify:"),
647 ci->num, ci->mark, ci->str,
648 ci->num2, ci->mark2, ci->str2, ci->comm2);
652 static int do_del_view(struct doc *d safe, int v,
656 /* This view should only have points on the list, not typed
657 * marks. Just delete everything and clear the 'notify' pointer
659 if (v < 0 || v >= d->nviews || d->views == NULL ||
660 !owner || d->views[v].owner != owner)
663 d->views[v].owner = NULL;
664 while (!tlist_empty(&d->views[v].head)) {
666 struct tlist_head *tl = d->views[v].head.next;
668 switch (TLIST_TYPE(tl)) {
669 case GRP_LIST: /* A point */
672 case GRP_MARK: /* a vmark */
673 m = container_of(tl, struct mark, view);
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",
681 "WARNING mark not freed by Close:mark");
682 LOG("WARNING Mark not freed by Close:mark");
689 default: /* impossible */
698 struct doc *d = ci->home->_data;
700 return do_del_view(d, ci->num, ci->focus);
705 struct doc *d = ci->home->_data;
710 for (ret = 0; d->views && ret < d->nviews; ret++)
711 if (d->views[ret].owner == NULL)
713 if (!d->views || ret == d->nviews) {
714 /* Resize the view list */
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;
722 for (; i < d->nviews; i++) {
723 INIT_TLIST_HEAD(&g[i].head, GRP_HEAD);
726 unalloc_buf(d->views, sizeof(*g)*(d->nviews - 4), pane);
728 /* now resize all the points */
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
737 pane_add_notify(ci->home, ci->focus, "Close:Notify");
742 DEF_CMD(doc_close_doc)
744 struct doc *d = ci->home->_data;
745 doc_free(d, ci->home);
749 DEF_CMD(doc_view_close)
751 /* A pane which once held a view is closing. We must discard
752 * that view if it still exists.
754 struct doc *d = ci->home->_data;
757 for (v = 0 ; d->views && v < d->nviews; v++)
758 do_del_view(d, v, ci->focus);
762 DEF_CMD(doc_vmarkget)
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;
771 DEF_CMD(doc_vmarkprev)
773 struct mark *m = NULL;
775 m = do_vmark_at_or_before(ci->home->_data, ci->mark,
777 comm_call(ci->comm2, "callback:vmark", ci->focus, 0, m);
781 DEF_CMD(doc_vmarknew)
785 m = doc_new_mark(ci->home, ci->num, ci->focus);
786 comm_call(ci->comm2, "callback:vmark", ci->focus, 0, m);
790 DEF_CMD(doc_drop_cache)
792 struct pane *p = ci->home;
793 struct doc *d = p->_data;
800 DEF_CMD(doc_delayed_close)
802 struct pane *p = ci->home;
805 /* If there are any doc-displays open, then will return '1' and
806 * we will know not to destroy document yet.
808 ret = pane_notify("doc:notify-viewers", p);
810 call("doc:drop-cache", p);
814 DEF_CMD(doc_do_closed)
816 struct pane *p = ci->home;
819 /* Close the path of filters from doc to focus */
820 child = pane_my_child(p, ci->focus);
824 call_comm("event:on-idle", p, &doc_delayed_close, 1);
828 DEF_CMD(doc_do_destroy)
830 pane_close(ci->home);
834 DEF_CMD(doc_get_point)
836 struct doc_data *dd = ci->home->data;
839 if (ci->num >= 1 && ci->num <= 4)
842 comm_call(ci->comm2, "callback", ci->focus, 0, dd->point, NULL,
847 DEF_CMD(doc_default_content)
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
854 * If called as doc:content-bytes: return bytes, not chars
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.
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
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.
872 * comm2 should return 1 if the main char was consumed,
873 * 1+n if n bytes (not chars) from str were consumed
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
881 struct mark *m = ci->mark;
883 char *cmd = "doc:char";
885 if (!m || !ci->comm2)
888 if (strcmp(ci->key, "doc:content-bytes") == 0)
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);
898 return nxt < 0 ? nxt : 1;
901 DEF_CMD(doc_insert_char)
903 const char *str = ksuffix(ci, "doc:char-");
905 return call("doc:replace", ci->focus, 1, NULL, str, ci->num2,
916 DEF_CB(get_str_callback)
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.
924 wint_t wch = ci->num & 0x1FFFFF;
925 struct getstr *g = container_of(ci->comm, struct getstr, c);
930 buf_resize(&g->b, ci->x);
932 buf_append_byte(&g->b, ci->num & 0xff);
934 buf_append(&g->b, wch);
935 if (g->end && (ci->mark->seq >= g->end->seq ||
936 mark_same(ci->mark, g->end)))
938 if (!ci->str || ci->num2 <= 0)
941 /* This could over-run ->end, but we assume it doesn't */
942 buf_concat_len(&g->b, ci->str, ci->num2);
949 * uses doc:content to collect the content
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.
955 int bytes = strcmp(ci->key, "doc:get-bytes") == 0;
957 struct mark *from = NULL, *to = NULL;
959 if (ci->mark && ci->mark2) {
960 if (ci->mark2->seq < ci->mark->seq) {
969 g.c = get_str_callback;
974 from = mark_new(ci->focus);
976 call("doc:set-ref", ci->focus, 1, from);
981 to = mark_new(ci->focus);
983 call("doc:set-ref", ci->focus, 0, to);
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)
989 if (to != ci->mark && to != ci->mark2)
991 comm_call(ci->comm2, "callback:get-str", ci->focus, g.b.len, NULL,
997 DEF_CMD(doc_notify_viewers)
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
1004 struct doc_data *dd = ci->home->data;
1007 mark_to_mark(ci->mark, dd->point);
1011 DEF_CMD(doc_notify_moving)
1013 struct doc_data *dd = ci->home->data;
1015 if (ci->mark == dd->point)
1016 pane_damaged(ci->home, DAMAGED_VIEW);
1017 return Efallthrough;
1020 DEF_CMD(doc_refresh_view)
1022 struct doc_data *dd = ci->home->data;
1023 int active = attr_find_int(dd->point->attrs, "selection:active");
1026 call("view:changed", ci->focus, 0, dd->point, NULL,
1029 call("view:changed", ci->focus, 0, dd->point);
1031 call("view:changed", ci->focus, 0, dd->old_point);
1034 dd->old_point = mark_dup(dd->point);
1036 mark_to_mark(dd->old_point, dd->point);
1037 mark_watch(dd->point);
1041 DEF_CMD(doc_notify_close)
1043 /* This pane has to go away */
1044 struct doc_data *dd = ci->home->data;
1046 mark_free(dd->point);
1047 dd->point = safe_cast NULL;
1048 pane_close(ci->home);
1054 struct doc_data *dd = ci->home->data;
1055 struct pane *p = doc_attach_assign(ci->focus, dd->doc);
1059 call("Move-to", p, 0, dd->point);
1060 pane_clone_children(ci->home, p);
1066 struct doc_data *dd = ci->home->data;
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);
1077 DEF_CMD(doc_dup_point)
1079 struct doc_data *dd = ci->home->data;
1080 struct mark *pt = dd->point;
1082 if (ci->mark && ci->mark->viewnum == MARK_POINT)
1085 if (!pt || !ci->comm2)
1088 if (ci->num2 == MARK_POINT)
1090 else if (ci->num2 == MARK_UNGROUPED)
1093 m = do_mark_at_point(pt, ci->num2);
1095 comm_call(ci->comm2, "callback:dup-point", ci->focus,
1100 DEF_CMD(doc_replace)
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);
1108 DEF_CMD(doc_handle_get_attr)
1110 struct doc_data *dd = ci->home->data;
1114 a = pane_attr_get(dd->doc, ci->str);
1116 return Efallthrough;
1117 return comm_call(ci->comm2, "callback", ci->focus, 0, NULL, a) ?: 1;
1120 DEF_CMD(doc_move_to)
1122 struct doc_data *dd = ci->home->data;
1127 mark_to_mark(dd->point, ci->mark);
1128 } else if (ci->num > 0 && ci->num <= 4) {
1129 int mnum = ci->num - 1;
1131 if (!dd->marks[mnum]) {
1132 dd->marks[mnum] = mark_dup(dd->point);
1133 if (!dd->marks[mnum])
1136 attr_set_str(&dd->marks[mnum]->attrs,
1137 "render:interactive-mark", "yes");
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;
1147 mark_free(dd->marks[mnum]);
1148 dd->marks[mnum] = NULL;
1156 struct doc_data *dd = ci->home->data;
1159 mark_clip(dd->point, ci->mark, ci->mark2, !!ci->num);
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);
1168 DEF_CMD(doc_pass_on)
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);
1178 DEF_CMD(doc_push_point)
1180 struct doc *d = ci->home->_data;
1181 int n = ARRAY_SIZE(d->recent_points);
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;
1195 DEF_CMD(doc_pop_point)
1197 struct doc *d = ci->home->_data;
1198 int n = ARRAY_SIZE(d->recent_points);
1202 if (!d->recent_points[0])
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;
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;
1217 DEF_CMD(doc_attach_view)
1219 struct pane *focus = ci->focus;
1220 struct pane *doc = ci->home;
1221 struct pane *p, *p2;
1223 const char *type = ci->str ?: "default";
1225 if (strcmp(type, "invisible") != 0) {
1227 /* caller is confused. */
1229 /* Double check the focus can display things */
1230 if (call("Draw:text", focus) == Efallthrough)
1234 p = doc_attach_assign(focus, doc);
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);
1246 s = strconcat(p, "render-", type);
1248 s = pane_attr_get(doc, s);
1250 s = pane_attr_get(doc, "render-default");
1253 s = strconcat(p, "attach-render-", s);
1254 p2 = call_ret(pane, s, p);
1259 s = strconcat(p, "view-", type);
1261 s = pane_attr_get(doc, s);
1263 s = pane_attr_get(doc, "view-default");
1266 while ((s2 = strchr(s, ',')) != NULL) {
1267 char *s3 = strndup(s, s2-s);
1268 p2 = call_ret(pane, strconcat(p, "attach-", s3)
1275 s = strconcat(p, "attach-", s);
1276 p2 = call_ret(pane, s, p);
1282 comm_call(ci->comm2, "callback:doc", p);
1286 DEF_CMD(doc_get_doc)
1291 comm_call(ci->comm2, "attach", ci->home, ci->num, NULL, ci->str,
1292 ci->num2, NULL, ci->str2);
1298 struct doc_data *dd = ci->home->data;
1300 call("doc:notify:Abort", dd->doc);
1301 return Efallthrough;
1304 struct map *doc_default_cmd safe;
1305 static struct map *doc_handle_cmd safe;
1307 DEF_LOOKUP_CMD(doc_handle, doc_handle_cmd);
1309 static void init_doc_cmds(void)
1311 doc_default_cmd = key_alloc();
1312 doc_handle_cmd = key_alloc();
1314 key_add_prefix(doc_handle_cmd, "doc:", &doc_pass_on);
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);
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);
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);
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);
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);
1371 static struct pane *doc_attach_assign(struct pane *parent safe, struct pane *doc safe)
1374 struct doc_data *dd;
1377 p = pane_register(parent, 0, &doc_handle.c);
1381 pane_damaged(p, DAMAGED_VIEW);
1388 if (call("doc:pop-point", doc, 0, m) <= 0)
1389 pane_notify("doc:notify-viewers", doc, 0, m);
1392 attr_set_str(&m->attrs, "render:interactive-point", "yes");
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);
1402 static void simplify_path(const char *path safe, char *buf safe)
1404 /* Like readpath, but doesn't process symlinks,
1405 * so only "..", "." and extra '/' are handled
1406 * Assumes that 'path' starts with a '/'.
1412 for (p = path; *p; p = end) {
1414 end = strchrnul(p+1, '/');
1418 /* Extra '/' at end or in the middle, ignore */
1420 if (len == 2 && strstarts(p, "/.") )
1421 /* Ignore the dot */
1423 if (len == 3 && strstarts(p, "/..")) {
1424 /* strip last component of buf */
1425 while (b > buf && b[-1] != '/')
1431 /* Append component to buf */
1436 /* This is the only case where we allow a trailing '/' */
1443 struct pane *ed = ci->home;
1446 char *realname = NULL;
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];
1461 /* fd < -1 mean a non-filesystem name */
1463 char *sl = NULL, *rp, *restore = NULL;
1465 /* First, make sure we have an absolute path, and
1468 if (ci->str[0] != '/') {
1469 char *c = getcwd(pathbuf, sizeof(pathbuf));
1471 name = strconcat(ed, c, "/", ci->str);
1472 simplify_path(name, pathbuf);
1473 name = strsave(ed, pathbuf);
1475 name = strsave(ed, ci->str);
1477 simplify_path(ci->str, pathbuf);
1478 name = strsave(ed, pathbuf);
1482 /* Now try to canonicalize directory part of name as realname */
1485 sl = strrchr(dir, '/');
1486 if (sl && sl[1] && strcmp(sl, "/.") != 0 && strcmp(sl, "/..") != 0) {
1487 /* Found a real basename */
1491 /* Cannot preserve basename in relative path */
1497 rp = realpath(dir, pathbuf);
1499 realname = strconcat(ed, rp, "/", sl);
1507 name = (char*)ci->str;
1511 fd = open(name, O_RDONLY);
1516 stb.st_mode = S_IFREG;
1520 p = call_ret(pane, "docs:byfd", ed, 0, NULL, name, fd);
1523 if (only_existing) {
1528 p = call_ret(pane, "global-multicall-open-doc-", ed,
1530 stb.st_mode & S_IFMT);
1538 call("doc:set:autoclose", p, 1);
1539 call("doc:load-file", p, 0, NULL, name, fd);
1540 call("global-multicall-doc:appeared-", p);
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 */
1547 realname = strconcat(ed, realname, "/");
1548 name = strconcat(ed, name, "/");
1550 if (!quiet && n && realname && strcmp(n, realname) != 0)
1551 call("Message", ci->focus, 0, NULL,
1552 strconcat(ci->focus, "File ", realname, " and ", n,
1554 else if (!quiet && n && strcmp(n, name) != 0)
1555 call("Message", ci->focus, 0, NULL,
1556 strconcat(ci->focus, "File ", name, " and ", n,
1559 if (reload || force_reload)
1560 call("doc:load-file", p, force_reload?0:1, NULL, name,
1565 return comm_call(ci->comm2, "callback", p) ?: 1;
1568 DEF_CMD(doc_from_text)
1570 const char *name = ci->str;
1571 const char *text = ci->str2;
1574 p = call_ret(pane, "attach-doc-text", ci->focus);
1578 call("doc:set-name", p, 0, NULL, name);
1579 call("global-multicall-doc:appeared-", p);
1581 call("doc:replace", p, 1, NULL, text);
1582 return comm_call(ci->comm2, "callback", p) ?: 1;
1585 void doc_free(struct doc *d safe, struct pane *root safe)
1587 /* NOTE: this must be idempotent as both it can be called
1588 * twice, once from doc_default_cmd, once deliberately by the
1592 bool warned = False;
1594 for (i = 0; i < ARRAY_SIZE(d->recent_points); i++) {
1595 mark_free(d->recent_points[i]);
1596 d->recent_points[i] = NULL;
1598 for (i = 0; i < (unsigned int)d->nviews; i++)
1600 do_del_view(d, i, d->views[i].owner);
1601 unalloc_buf(d->views, sizeof(d->views[0]) * d->nviews, pane);
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 */
1609 call("editor:notify:Message:broadcast",
1611 "WARNING mark with data not freed");
1612 LOG("WARNING Mark with data no freed");
1617 if (m->viewnum == MARK_POINT || m->viewnum == MARK_UNGROUPED)
1620 /* vmarks should have gone already */
1625 void doc_setup(struct pane *ed safe)
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,
1630 if (!(void*)doc_default_cmd)