2 * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * doc-multipart: Present a sequence of documents as though it were
7 * This is used for stitching together the parts of a MIME email message.
9 * The document is created empty, and then given subordinate documents
10 * using a "multipart-add" command which causes the "focus" to be added
12 * If more sophisticated edits are needed, they can come later.
21 #define PRIVATE_DOC_REF
25 int docnum; /* may be 'nparts', in which case 'm' == NULL */
28 /* mark->mdata in marks we create on individual component documents
29 * is used to track if the mark is shared among multiple marks in the
32 #define GET_REFS(_mark) ((unsigned long)((_mark)->mdata))
33 #define SET_REFS(_mark, val) ((_mark)->mdata = (void*)(unsigned long)(val))
34 #define ADD_REFS(_mark, inc) SET_REFS(_mark, GET_REFS(_mark) + (inc))
36 #define DOC_DATA_TYPE struct mp_info
38 #define DOC_NEXT(p,m,r,b) multipart_next_prev(p, m, r, 1, b, ci->str)
39 #define DOC_PREV(p,m,r,b) multipart_next_prev(p, m, r, 0, b, ci->str)
40 #define DOC_NEXT_DECL(p,m,r,b) multipart_next_prev(p, m, r, int forward, b, const char *str)
41 #define DOC_PREV_DECL(p,m,r,b) multipart_next_prev(p, m, r, int forward, b, const char *str)
52 #include "core-pane.h"
54 static struct map *mp_map safe;
56 /* Before moving a mark, we make sure m->ref.m is not shared.
57 * After moving, we make sure the mark is correctly ordered among
58 * siblings, and then share if m->ref.m should be shared.
60 static void pre_move(struct mark *m safe)
64 if (!m->ref.m || GET_REFS(m->ref.m) == 1)
66 /* Mark is shared, make it unshared */
67 m2 = mark_dup(m->ref.m);
68 ADD_REFS(m->ref.m, -1);
73 static void post_move(struct mark *m)
75 /* m->ref.m might have moved. If so, move m in the list of
76 * marks so marks in this document are still properly ordered
77 * Then ensure that if neighbouring marks are at same location,
78 * they use same marks.
80 struct mark *m2, *mtarget;
82 if (!m || hlist_unhashed(&m->all))
84 ASSERT(m->ref.m == NULL || GET_REFS(m->ref.m) == 1);
86 while ((m2 = mark_next(mtarget)) != NULL &&
87 (m2->ref.docnum < m->ref.docnum ||
88 (m2->ref.docnum == m->ref.docnum &&
89 m2->ref.m && m->ref.m &&
90 m2->ref.m->seq < m->ref.m->seq)))
93 /* m should be after mtarget */
94 mark_to_mark_noref(m, mtarget);
97 while ((m2 = mark_prev(mtarget)) != NULL &&
98 (m2->ref.docnum > m->ref.docnum||
99 (m2->ref.docnum == m->ref.docnum &&
100 m2->ref.m && m->ref.m &&
101 m2->ref.m->seq > m->ref.m->seq)))
104 /* m should be before mtarget */
105 mark_to_mark_noref(m, mtarget);
109 ASSERT(GET_REFS(m->ref.m) == 1);
110 /* Check if it should be shared */
112 if (m2 && m2->ref.docnum == m->ref.docnum && m2->ref.m) {
113 if (m->ref.m != m2->ref.m &&
114 mark_same(m->ref.m, m2->ref.m)) {
115 SET_REFS(m->ref.m, 0);
117 m->ref.m = m2->ref.m;
118 ADD_REFS(m->ref.m, 1);
123 if (m2 && m2->ref.docnum == m->ref.docnum && m2->ref.m) {
124 if (m->ref.m != m2->ref.m &&
125 mark_same(m->ref.m, m2->ref.m)) {
126 SET_REFS(m->ref.m, 0);
128 m->ref.m = m2->ref.m;
129 ADD_REFS(m->ref.m, 1);
135 static void mp_mark_refcnt(struct mark *m safe, int inc)
141 /* Duplicate being created of this mark */
142 ADD_REFS(m->ref.m, 1);
145 /* mark is being discarded, or ref over-written */
146 ADD_REFS(m->ref.m, -1);
147 if (GET_REFS(m->ref.m) == 0)
153 static void mp_check_consistent(struct mp_info *mpi safe)
155 struct doc *d = &mpi->doc;
161 for (m = mark_first(d); m; m = mark_next(m)) {
162 if (!m->ref.m || m->ref.m->seq <= s) {
163 for (m = mark_first(d); m;
166 printf("%p %d %d\n", m, m->seq,
176 doc_check_consistent(d);
179 static void change_part(struct mp_info *mpi safe, struct mark *m safe,
185 if (part < 0 || part > mpi->nparts || !mpi->parts)
188 ASSERT(GET_REFS(m->ref.m) == 1);
189 SET_REFS(m->ref.m, 0);
193 if (part < mpi->nparts && (p = &mpi->parts[part]) && p->pane) {
194 m1 = mark_new(p->pane);
196 call("doc:set-ref", p->pane, !end, m1);
202 m->ref.docnum = part;
205 static void mp_normalize(struct mp_info *mpi safe, struct mark *m safe,
208 /* If points the end of a document, point to the start
209 * of the next instead.
214 while (m->ref.m && (p = &mpi->parts[m->ref.docnum]) && p->pane &&
215 doc_following(p->pane, m->ref.m) == WEOF) {
216 int n = m->ref.docnum + 1;
217 while (n < mpi->nparts && vis && vis[n] == 'i')
219 change_part(mpi, m, n, 0);
223 DEF_CMD_CLOSED(mp_close)
225 struct mp_info *mpi = ci->home->doc_data;
229 for (m = mark_first(&mpi->doc); m ; m = mark_next(m))
231 struct mark *m2 = m->ref.m;
234 if (GET_REFS(m2) == 0)
239 for (i = 0; i < mpi->nparts; i++) {
240 struct pane *p = mpi->parts[i].pane;
242 call("doc:closed", p);
251 struct mp_info *mpi = ci->home->doc_data;
252 const char *vis = ci->str && (int)strlen(ci->str) >= mpi->nparts ?
259 /* Need to trigger a point:moved notification. FIXME I wonder
260 * if this can be simpler
262 mark_step(ci->mark, 0);
264 if (!ci->mark->ref.m && !ci->mark->ref.docnum) {
265 /* First time set-ref was called */
267 change_part(mpi, ci->mark, 0, 0);
268 mark_to_end(ci->home, ci->mark, 0);
276 while (n < mpi->nparts && vis && vis[n] == 'i')
278 change_part(mpi, ci->mark, n, 0);
279 mp_normalize(mpi, ci->mark, vis);
281 change_part(mpi, ci->mark, mpi->nparts, 1);
284 mp_check_consistent(mpi);
288 static inline wint_t multipart_next_prev(struct pane *home safe, struct mark *mark safe,
289 struct doc_ref *r safe,
290 int forward, bool bytes, const char *str)
292 int move = r == &mark->ref;
293 struct mp_info *mpi = home->doc_data;
294 struct mark *m1 = NULL;
295 struct mark *m = mark;
296 const char *vis = str && (int)strlen(str) >= mpi->nparts ?
301 /* Document access commands are handled by the 'cropper'. First
302 * we need to substitute the marks, then call the cropper which
303 * calls the document. Then make sure the marks are still in
307 mp_check_consistent(mpi);
310 mark_step(m, forward);
316 if (m->ref.docnum >= mpi->nparts || !mpi->parts)
319 ret = home_call(mpi->parts[m->ref.docnum].pane,
321 move ? (forward ? 1 : -1) : 0,
323 move ? 0 : (forward ? 1 : -1),
325 while (ret == CHAR_RET(WEOF) || ret == -1) {
326 if (!move && m == mark) {
327 /* don't change mark when not moving */
332 if (m->ref.docnum >= mpi->nparts)
334 n = m->ref.docnum + 1;
335 while (n < mpi->nparts && vis && vis[n] == 'i')
337 change_part(mpi, m, n, 0);
339 n = m->ref.docnum - 1;
340 while (n >= 0 && vis && vis[n] == 'i')
344 change_part(mpi, m, n, 1);
347 if (m->ref.docnum >= mpi->nparts || !mpi->parts)
350 ret = home_call(mpi->parts[m->ref.docnum].pane,
352 move ? (forward ? 1 : -1) : 0,
354 move ? 0 : (forward ? 1 : -1));
357 mp_normalize(mpi, mark, vis);
364 mp_check_consistent(mpi);
365 return ret == -1 ? (int)CHAR_RET(WEOF) : ret;
370 return do_char_byte(ci);
373 DEF_CMD(mp_step_part)
375 /* Step forward or backward to part boundary.
376 * Stepping forward takes us to start of next part.
377 * Stepping backward takes us to start of this
378 * part - we might not move.
379 * if ->num is -1, step to start of previous part
380 * Return part number plus 1.
381 * If ->str is given, only consider visible parts.
383 struct mp_info *mpi = ci->home->doc_data;
384 struct mark *m = ci->mark;
385 const char *vis = ci->str && (int)strlen(ci->str) >= mpi->nparts ?
394 start = m->ref.docnum;
397 /* Forward - start of next part */
399 while (n < mpi->nparts && vis && vis[n] == 'i')
401 } else if (ci->num < 0) {
402 /* Backward - start of prev part */
404 while (n >= 0 && vis && vis[n] == 'i')
409 /* otherwise start of this part */
410 change_part(mpi, m, n, 0);
412 /* If this part is empty, need to move to next visible part */
413 mp_normalize(mpi, m, vis);
415 while (vis && vis[first_vis] == 'i')
417 while (ci->num < 0 && m->ref.docnum == start && n > first_vis) {
418 /* didn't move - must have an empty part, try further */
420 change_part(mpi, m, n, 0);
421 mp_normalize(mpi, m, vis);
424 if (ci->num && start == m->ref.docnum)
426 return m->ref.docnum + 1;
429 DEF_CMD(mp_get_boundary)
431 /* return a mark past which rendering must not go. */
432 struct mark *m = ci->mark;
434 if (!m || !ci->comm2)
437 call("doc:step-part", ci->home, ci->num, m, ci->str);
438 comm_call(ci->comm2, "cb", ci->focus, 0, m);
450 DEF_CB(mp_content_cb)
452 struct mp_cb *c = container_of(ci->comm, struct mp_cb, c);
453 struct mark *m1 = NULL;
459 mark_to_mark(m1->ref.m, ci->mark);
463 c->last_ret = comm_call(c->cb, ci->key, c->p,
464 ci->num, m1, ci->str,
465 ci->num2, NULL, ci->str2,
472 /* Call doc:content on any visible docs in the range.
473 * Callback must re-wrap any marks
475 struct mp_info *mpi = ci->home->doc_data;
478 const char *invis = ci->str;
481 if (!ci->mark || !ci->comm2)
483 m = mark_dup(ci->mark);
486 while (cb.last_ret > 0 && m->ref.docnum < mpi->nparts &&
488 (!m2 || m->ref.docnum <= m2->ref.docnum)) {
489 /* Need to call doc:content on this document */
490 int n = m->ref.docnum;
491 if ((!invis || invis[n] != 'i') && m->ref.m) {
492 struct mark *m2a = NULL;
493 struct mark *mtmp = NULL;
494 cb.c = mp_content_cb;
500 mtmp = mark_dup(m->ref.m);
501 if (m2 && m2->ref.docnum == n && m2->ref.m)
502 m2a = mark_dup(m2->ref.m);
504 ret = home_call_comm(mpi->parts[n].pane,
505 ci->key, ci->home, &cb.c,
513 if (cb.last_ret > 0) {
515 change_part(mpi, m, n+1, 0);
525 struct mp_info *mpi = ci->home->doc_data;
526 struct mark *m1 = NULL;
528 int ret = Efallthrough;
530 const char *attr = ci->str;
532 if (!ci->mark || !attr)
535 m1 = ci->mark->ref.m;
536 d = ci->mark->ref.docnum;
538 if (d < mpi->nparts && m1 && mpi->parts && (p = &mpi->parts[d]) &&
539 p->pane && doc_following(p->pane, m1) == WEOF)
540 /* at the wrong end of a part */
543 if (strstarts(attr, "multipart-next:")) {
546 if (d >= mpi->nparts)
548 } else if (strstarts(attr, "multipart-prev:")) {
553 } else if (strstarts(attr, "multipart-this:"))
556 if (strcmp(attr, "multipart:part-num") == 0) {
558 snprintf(n, sizeof(n), "%d", d);
559 comm_call(ci->comm2, "callback:get_attr", ci->focus,
560 0, ci->mark, n, 0, NULL, attr);
564 if (d >= mpi->nparts || d < 0 || !mpi->parts)
567 if (attr != ci->str) {
568 /* Get a pane attribute, not char attribute */
569 char *s = pane_attr_get(mpi->parts[d].pane, attr);
571 return comm_call(ci->comm2, "callback", ci->focus,
572 0, ci->mark, s, 0, NULL, ci->str);
577 if (d != ci->mark->ref.docnum && p->pane) {
578 m1 = mark_new(p->pane);
579 call("doc:set-ref", p->pane,
580 (d > ci->mark->ref.docnum), m1);
584 ret = home_call(p->pane,
585 ci->key, ci->focus, ci->num, m1, ci->str,
586 ci->num2, NULL, ci->str2, 0,0, ci->comm2);
587 if (d != ci->mark->ref.docnum)
594 struct mp_info *mpi = ci->home->doc_data;
596 struct mark *m = ci->mark;
599 const char *attr = ci->str;
610 if (strstarts(attr, "multipart-")) {
611 /* Set an attribute on a part */
612 if (strstarts(attr, "multipart-prev:") &&
613 dn > 0 && (p = &mpi->parts[dn-1]) && p->pane)
614 attr_set_str(&p->pane->attrs,
616 else if (strstarts(attr, "multipart-next:") &&
617 dn < mpi->nparts && (p = &mpi->parts[dn+1]) && p->pane)
618 attr_set_str(&p->pane->attrs,
620 else if (strstarts(attr, "multipart-this:") &&
621 (p = &mpi->parts[dn]) && p->pane)
622 attr_set_str(&p->pane->attrs,
628 /* Send the request to a sub-document */
631 return call(ci->key, p->pane, ci->num, m1, ci->str,
636 DEF_CMD(mp_notify_close)
638 /* sub-document has been closed.
639 * Can we survive? or should we just shut down?
641 struct mp_info *mpi = ci->home->doc_data;
644 for (i = 0; i < mpi->nparts && mpi->parts; i++)
645 if (mpi->parts[i].pane == ci->focus) {
646 /* sub-document has been closed.
647 * Can we survive? or should we just shut down?
649 mpi->parts[i].pane = NULL;
650 pane_close(ci->home);
653 /* Not a sub-pane, maybe an owner for vmarks */
657 DEF_CMD(mp_notify_viewers)
659 /* The autoclose document wants to know if it should close.
664 DEF_CMD(mp_doc_replaced)
666 /* Something changed in a component, report that the
667 * whole doc changed - simplest for now.
669 pane_notify("doc:replaced", ci->home);
673 static void mp_resize(struct mp_info *mpi safe, int size)
675 if (mpi->parts_size >= size)
678 mpi->parts = realloc(mpi->parts, size * sizeof(struct part));
679 mpi->parts_size = size;
684 struct mp_info *mpi = ci->home->doc_data;
688 mp_resize(mpi, mpi->nparts+1);
689 if (ci->mark == NULL)
692 n = ci->mark->ref.docnum;
693 memmove(&mpi->parts[n+1], &mpi->parts[n],
694 (mpi->nparts - n)*sizeof(mpi->parts[0]));
696 mpi->parts[n].pane = ci->focus;
697 hlist_for_each_entry(m, &mpi->doc.marks, all)
698 if (m->ref.docnum >= n)
701 /* move mark to start of new part */
702 change_part(mpi, ci->mark, n, 0);
704 pane_add_notify(ci->home, ci->focus, "Notify:Close");
705 home_call(ci->focus, "doc:request:doc:notify-viewers", ci->home);
706 home_call(ci->focus, "doc:request:doc:replaced", ci->home);
711 DEF_CMD(mp_forward_by_num)
713 struct mp_info *mpi = ci->home->doc_data;
714 struct mark *m1 = NULL, *m2 = NULL;
720 key = ksuffix(ci, "doc:multipart-");
722 key = strchr(key, '-');
727 if (d >= mpi->nparts || d < 0 || !mpi->parts)
730 if (ci->mark && ci->mark->ref.docnum == d)
731 m1 = ci->mark->ref.m;
732 if (ci->mark2 && ci->mark2->ref.docnum == d)
733 m2 = ci->mark2->ref.m;
737 ret = call(key, p->pane, ci->num, m1, ci->str,
738 ci->num2, m2, ci->str2, ci->x, ci->y, ci->comm2);
746 struct mp_info *mpi = ci->home->doc_data;
750 if (d < 0 || d >= mpi->nparts || !mpi->parts)
754 comm_call(ci->comm2, "cb", p->pane);
760 /* forward this command to this/next/prev document based on
762 * ci->mark is forwarded if it is in same document
764 struct mp_info *mpi = ci->home->doc_data;
766 struct mark *m1, *m2;
774 m2 = ci->mark2->ref.m;
775 d = ci->mark2->ref.docnum;
777 if (d < mpi->nparts && m2 && (p = &mpi->parts[d]) && p->pane &&
778 doc_following(p->pane, m2) == WEOF)
779 /* at the wrong end of a part */
782 if ((key = ksuffix(ci, "multipart-next:"))[0]) {
784 if (d >= mpi->nparts)
786 } else if ((key = ksuffix(ci, "multipart-prev:"))[0]) {
790 } else if ((key = ksuffix(ci, "multipart-this:"))[0]) {
792 } else return Einval;
794 if (d >= mpi->nparts || d < 0)
798 if (ci->mark && ci->mark->ref.docnum == d)
799 m1 = ci->mark->ref.m;
802 return call(key, p->pane, ci->num, m1, ci->str,
803 ci->num2, NULL, ci->str2, 0,0, ci->comm2);
807 DEF_CMD(mp_val_marks)
809 struct mark *m1, *m2;
811 if (!ci->mark || !ci->mark2)
814 if (ci->mark->ref.docnum < ci->mark2->ref.docnum)
816 if (ci->mark->ref.docnum > ci->mark2->ref.docnum) {
817 LOG("mp_val_marks: docs not in order");
821 m1 = ci->mark->ref.m;
822 m2 = ci->mark->ref.m;
825 if (m1 && m2 && m1->seq > m2->seq) {
826 LOG("mp_val_marks: subordinate marks out of order!");
830 LOG("mp_val_marks: m1 is NULL");
831 else if (!m2 || marks_validate(m1, m2))
836 static void mp_init_map(void)
838 mp_map = key_alloc();
839 key_add_chain(mp_map, doc_default_cmd);
840 key_add(mp_map, "doc:set-ref", &mp_set_ref);
841 key_add(mp_map, "doc:char", &mp_char);
842 key_add(mp_map, "doc:content", &mp_content);
843 key_add(mp_map, "doc:content-bytes", &mp_content);
844 key_add(mp_map, "doc:get-attr", &mp_attr);
845 key_add(mp_map, "doc:set-attr", &mp_set_attr);
846 key_add(mp_map, "doc:step-part", &mp_step_part);
847 key_add(mp_map, "doc:get-boundary", &mp_get_boundary);
848 key_add(mp_map, "Close", &mp_close);
849 key_add(mp_map, "Notify:Close", &mp_notify_close);
850 key_add(mp_map, "doc:notify-viewers", &mp_notify_viewers);
851 key_add(mp_map, "doc:replaced", &mp_doc_replaced);
852 key_add(mp_map, "multipart-add", &mp_add);
853 key_add(mp_map, "debug:validate-marks", &mp_val_marks);
854 key_add(mp_map, "doc:multipart:get-part", &mp_get_part);
855 key_add_prefix(mp_map, "multipart-this:", &mp_forward);
856 key_add_prefix(mp_map, "multipart-next:", &mp_forward);
857 key_add_prefix(mp_map, "multipart-prev:", &mp_forward);
858 key_add_prefix(mp_map, "doc:multipart-", &mp_forward_by_num);
860 DEF_LOOKUP_CMD(mp_handle, mp_map);
867 h = doc_register(ci->home, &mp_handle.c);
872 mpi->doc.refcnt = mp_mark_refcnt;
873 attr_set_str(&h->attrs, "render-default", "text");
874 return comm_call(ci->comm2, "callback:doc", h);
877 void edlib_init(struct pane *ed safe)
880 call_comm("global-set-command", ed, &attach_mp, 0, NULL,
881 "attach-doc-multipart");