]> git.neil.brown.name Git - edlib.git/blobdiff - doc-multipart.c
TODO: clean out done items.
[edlib.git] / doc-multipart.c
index 53de4ac52c74719af588229e1a1887261e6173dc..8edcdfd9e17bc026a676eb8fcaf5bb49d23e60da 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright Neil Brown ©2016-2020 <neil@brown.name>
+ * Copyright Neil Brown ©2016-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * doc-multipart: Present a sequence of documents as though it were
@@ -25,11 +25,20 @@ struct doc_ref {
        int docnum; /* may be 'nparts', in which case 'm' == NULL */
 };
 
-/* mark->refs in marks we create on individual component documents
+/* mark->mdata in marks we create on individual component documents
  * is used to track if the mark is shared among multiple marks in the
  * multipart document.
  */
+#define GET_REFS(_mark) ((unsigned long)((_mark)->mdata))
+#define SET_REFS(_mark, val) ((_mark)->mdata = (void*)(unsigned long)(val))
+#define ADD_REFS(_mark, inc) SET_REFS(_mark, GET_REFS(_mark) + (inc))
 
+#define DOC_DATA_TYPE struct mp_info
+
+#define DOC_NEXT(p,m,r,b) multipart_next_prev(p, m, r, 1, b, ci->str)
+#define DOC_PREV(p,m,r,b) multipart_next_prev(p, m, r, 0, b, ci->str)
+#define DOC_NEXT_DECL(p,m,r,b) multipart_next_prev(p, m, r, int forward, b, const char *str)
+#define DOC_PREV_DECL(p,m,r,b) multipart_next_prev(p, m, r, int forward, b, const char *str)
 #include "core.h"
 
 struct mp_info {
@@ -37,9 +46,10 @@ struct mp_info {
        int             nparts;
        int             parts_size;
        struct part {
-               struct pane     *pane safe;
-       } *parts safe;
+               struct pane     *pane;
+       } *parts;
 };
+#include "core-pane.h"
 
 static struct map *mp_map safe;
 
@@ -51,12 +61,12 @@ static void pre_move(struct mark *m safe)
 {
        struct mark *m2;
 
-       if (!m->ref.m || m->ref.m->refs == 1)
+       if (!m->ref.m || GET_REFS(m->ref.m) == 1)
                return;
        /* Mark is shared, make it unshared */
        m2 = mark_dup(m->ref.m);
-       m->ref.m->refs -= 1;
-       m2->refs = 1;
+       ADD_REFS(m->ref.m, -1);
+       SET_REFS(m2, 1);
        m->ref.m = m2;
 }
 
@@ -67,40 +77,45 @@ static void post_move(struct mark *m)
         * Then ensure that if neighbouring marks are at same location,
         * they use same marks.
         */
-       struct mark *m2;
+       struct mark *m2, *mtarget;
 
        if (!m || hlist_unhashed(&m->all))
                return;
-       ASSERT(m->ref.m == NULL || m->ref.m->refs == 1);
-       while ((m2 = mark_next(m)) != NULL &&
+       ASSERT(m->ref.m == NULL || GET_REFS(m->ref.m) == 1);
+       mtarget = m;
+       while ((m2 = mark_next(mtarget)) != NULL &&
               (m2->ref.docnum < m->ref.docnum ||
                (m2->ref.docnum == m->ref.docnum &&
                 m2->ref.m && m->ref.m &&
-                m2->ref.m->seq < m->ref.m->seq))) {
-               /* m should be after m2 */
-               mark_to_mark_noref(m, m2);
-       }
-
-       while ((m2 = mark_prev(m)) != NULL &&
+                m2->ref.m->seq < m->ref.m->seq)))
+               mtarget = m2;
+       if (mtarget != m)
+               /* m should be after mtarget */
+               mark_to_mark_noref(m, mtarget);
+
+       mtarget = m;
+       while ((m2 = mark_prev(mtarget)) != NULL &&
               (m2->ref.docnum > m->ref.docnum||
                (m2->ref.docnum == m->ref.docnum &&
                 m2->ref.m && m->ref.m &&
-                m2->ref.m->seq > m->ref.m->seq))) {
-               /* m should be before m2 */
-               mark_to_mark_noref(m, m2);
-       }
+                m2->ref.m->seq > m->ref.m->seq)))
+               mtarget = m2;
+       if (mtarget != m)
+               /* m should be before mtarget */
+               mark_to_mark_noref(m, mtarget);
+
        if (!m->ref.m)
                return;
-       ASSERT(m->ref.m->refs == 1);
+       ASSERT(GET_REFS(m->ref.m) == 1);
        /* Check if it should be shared */
        m2 = mark_next(m);
        if (m2 && m2->ref.docnum == m->ref.docnum && m2->ref.m) {
                if (m->ref.m != m2->ref.m &&
                    mark_same(m->ref.m, m2->ref.m)) {
-                       m->ref.m->refs = 0;
+                       SET_REFS(m->ref.m, 0);
                        mark_free(m->ref.m);
                        m->ref.m = m2->ref.m;
-                       m->ref.m->refs += 1;
+                       ADD_REFS(m->ref.m, 1);
                        return;
                }
        }
@@ -108,10 +123,10 @@ static void post_move(struct mark *m)
        if (m2 && m2->ref.docnum == m->ref.docnum && m2->ref.m) {
                if (m->ref.m != m2->ref.m &&
                    mark_same(m->ref.m, m2->ref.m)) {
-                       m->ref.m->refs = 0;
+                       SET_REFS(m->ref.m, 0);
                        mark_free(m->ref.m);
                        m->ref.m = m2->ref.m;
-                       m->ref.m->refs += 1;
+                       ADD_REFS(m->ref.m, 1);
                        return;
                }
        }
@@ -124,12 +139,12 @@ static void mp_mark_refcnt(struct mark *m safe, int inc)
 
        if (inc > 0)
                /* Duplicate being created of this mark */
-               m->ref.m->refs += 1;
+               ADD_REFS(m->ref.m, 1);
 
        if (inc < 0) {
                /* mark is being discarded, or ref over-written */
-               m->ref.m->refs -= 1;
-               if (m->ref.m->refs == 0)
+               ADD_REFS(m->ref.m, -1);
+               if (GET_REFS(m->ref.m) == 0)
                        mark_free(m->ref.m);
                m->ref.m = NULL;
        }
@@ -137,12 +152,12 @@ static void mp_mark_refcnt(struct mark *m safe, int inc)
 
 static void mp_check_consistent(struct mp_info *mpi safe)
 {
-//     struct mark *m;
        struct doc *d = &mpi->doc;
-//     int s = -1;
-
-       doc_check_consistent(d);
 #if 0
+       struct mark *m;
+       int s = -1;
+       int max = 1000;
+
        for (m = mark_first(d); m; m = mark_next(m)) {
                if (!m->ref.m || m->ref.m->seq <= s) {
                        for (m = mark_first(d); m;
@@ -154,9 +169,11 @@ static void mp_check_consistent(struct mp_info *mpi safe)
                        abort();
                }
                s = m->ref.m->seq;
+               if (max-- < 0)
+                       break;
        }
-       doc_check_consistent(d);
 #endif
+       doc_check_consistent(d);
 }
 
 static void change_part(struct mp_info *mpi safe, struct mark *m safe,
@@ -165,20 +182,20 @@ static void change_part(struct mp_info *mpi safe, struct mark *m safe,
        struct mark *m1;
        struct part *p;
 
-       if (part < 0 || part > mpi->nparts)
+       if (part < 0 || part > mpi->nparts || !mpi->parts)
                return;
        if (m->ref.m) {
-               ASSERT(m->ref.m->refs == 1);
-               m->ref.m->refs = 0;
+               ASSERT(GET_REFS(m->ref.m) == 1);
+               SET_REFS(m->ref.m, 0);
                mark_free(m->ref.m);
+               m->ref.m = NULL;
        }
-       if (part < mpi->nparts) {
-               p = &mpi->parts[part];
-               m1 = vmark_new(p->pane, MARK_UNGROUPED, NULL);
+       if (part < mpi->nparts && (p = &mpi->parts[part]) && p->pane) {
+               m1 = mark_new(p->pane);
                if (m1) {
                        call("doc:set-ref", p->pane, !end, m1);
                        m->ref.m = m1;
-                       m1->refs = 1;
+                       SET_REFS(m1, 1);
                }
        } else
                m->ref.m = NULL;
@@ -191,9 +208,11 @@ static void mp_normalize(struct mp_info *mpi safe, struct mark *m safe,
        /* If points the end of a document, point to the start
         * of the next instead.
         */
-       while (m->ref.m &&
-              doc_following(mpi->parts[m->ref.docnum].pane,
-                                 m->ref.m) == WEOF) {
+       struct part *p;
+       if (!mpi->parts)
+               return;
+       while (m->ref.m && (p = &mpi->parts[m->ref.docnum]) && p->pane &&
+              doc_following(p->pane, m->ref.m) == WEOF) {
                int n = m->ref.docnum + 1;
                while (n < mpi->nparts && vis && vis[n] == 'i')
                        n += 1;
@@ -201,9 +220,9 @@ static void mp_normalize(struct mp_info *mpi safe, struct mark *m safe,
        }
 }
 
-DEF_CMD(mp_close)
+DEF_CMD_CLOSED(mp_close)
 {
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
        int i;
        struct mark *m;
 
@@ -211,28 +230,25 @@ DEF_CMD(mp_close)
                if (m->ref.m) {
                        struct mark *m2 = m->ref.m;
                        m->ref.m = NULL;
-                       m2->refs -= 1;
-                       if (m2->refs == 0)
+                       ADD_REFS(m2, -1);
+                       if (GET_REFS(m2) == 0)
                                mark_free(m2);
                }
-       for (i = 0; i < mpi->nparts; i++)
-               call("doc:closed", mpi->parts[i].pane);
-       doc_free(&mpi->doc);
-       return 1;
-}
-
-DEF_CMD(mp_free)
-{
-       struct mp_info *mpi = ci->home->data;
-
+       if (!mpi->parts)
+               return Efallthrough;
+       for (i = 0; i < mpi->nparts; i++) {
+               struct pane *p = mpi->parts[i].pane;
+               if (p)
+                       call("doc:closed", p);
+       }
        free(mpi->parts);
-       unalloc(mpi, pane);
-       return 1;
+       mpi->parts = NULL;
+       return Efallthrough;
 }
 
 DEF_CMD(mp_set_ref)
 {
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
        const char *vis = ci->str && (int)strlen(ci->str) >= mpi->nparts ?
                ci->str : NULL;
        int ret = 1;
@@ -269,13 +285,16 @@ DEF_CMD(mp_set_ref)
        return ret;
 }
 
-DEF_CMD(mp_step)
+static inline wint_t multipart_next_prev(struct pane *home safe, struct mark *mark safe,
+                                        struct doc_ref *r safe,
+                                        int forward, bool bytes, const char *str)
 {
-       struct mp_info *mpi = ci->home->data;
+       int move = r == &mark->ref;
+       struct mp_info *mpi = home->doc_data;
        struct mark *m1 = NULL;
-       struct mark *m = ci->mark;
-       const char *vis = ci->str && (int)strlen(ci->str) >= mpi->nparts ?
-               ci->str : NULL;
+       struct mark *m = mark;
+       const char *vis = str && (int)strlen(str) >= mpi->nparts ?
+               str : NULL;
        int n;
        int ret;
 
@@ -284,32 +303,32 @@ DEF_CMD(mp_step)
         * calls the document.  Then make sure the marks are still in
         * order.
         */
-       mp_check_consistent(mpi);
-       if (!m)
-               return Enoarg;
 
        mp_check_consistent(mpi);
 
-       if (ci->num2) {
-               mark_step(m, ci->num);
+       if (move) {
+               mark_step(m, forward);
                pre_move(m);
        }
 
        m1 = m->ref.m;
 
-       if (m->ref.docnum == mpi->nparts)
+       if (m->ref.docnum >= mpi->nparts || !mpi->parts)
                ret = -1;
        else
                ret = home_call(mpi->parts[m->ref.docnum].pane,
-                               ci->key, ci->focus, ci->num, m1, ci->str,
-                               ci->num2, NULL, ci->str2, 0,0, ci->comm2);
+                               "doc:char", home,
+                               move ? (forward ? 1 : -1) : 0,
+                               m1, str,
+                               move ? 0 : (forward ? 1 : -1),
+                               NULL, NULL);
        while (ret == CHAR_RET(WEOF) || ret == -1) {
-               if (!ci->num2 && m == ci->mark) {
-                       /* don't change ci->mark when not moving */
+               if (!move && m == mark) {
+                       /* don't change mark when not moving */
                        m = mark_dup(m);
                        pre_move(m);
                }
-               if (ci->num) {
+               if (forward) {
                        if (m->ref.docnum >= mpi->nparts)
                                break;
                        n = m->ref.docnum + 1;
@@ -317,8 +336,6 @@ DEF_CMD(mp_step)
                                n += 1;
                        change_part(mpi, m, n, 0);
                } else {
-                       if (m->ref.docnum == 0)
-                               break;
                        n = m->ref.docnum - 1;
                        while (n >= 0 && vis && vis[n] == 'i')
                                n -= 1;
@@ -327,27 +344,32 @@ DEF_CMD(mp_step)
                        change_part(mpi, m, n, 1);
                }
                m1 = m->ref.m;
-               if (m->ref.docnum == mpi->nparts)
+               if (m->ref.docnum >= mpi->nparts || !mpi->parts)
                        ret = -1;
                else
                        ret = home_call(mpi->parts[m->ref.docnum].pane,
-                                       ci->key, ci->focus,
-                                       ci->num, m1, ci->str,
-                                       ci->num2, NULL, ci->str2,
-                                       0,0, ci->comm2);
+                                       "doc:char", home,
+                                       move ? (forward ? 1 : -1) : 0,
+                                       m1, str,
+                                       move ? 0 : (forward ? 1 : -1));
        }
-       if (ci->num2) {
-               mp_normalize(mpi, ci->mark, vis);
-               post_move(ci->mark);
+       if (move) {
+               mp_normalize(mpi, mark, vis);
+               post_move(mark);
        }
 
-       if (m != ci->mark)
+       if (m != mark)
                mark_free(m);
 
        mp_check_consistent(mpi);
        return ret == -1 ? (int)CHAR_RET(WEOF) : ret;
 }
 
+DEF_CMD(mp_char)
+{
+       return do_char_byte(ci);
+}
+
 DEF_CMD(mp_step_part)
 {
        /* Step forward or backward to part boundary.
@@ -358,16 +380,19 @@ DEF_CMD(mp_step_part)
         * Return part number plus 1.
         * If ->str is given, only consider visible parts.
         */
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
        struct mark *m = ci->mark;
        const char *vis = ci->str && (int)strlen(ci->str) >= mpi->nparts ?
                ci->str : NULL;
+       int start;
+       int first_vis;
        int n;
 
        if (!m)
                return Enoarg;
        pre_move(m);
-       n = m->ref.docnum;
+       start = m->ref.docnum;
+       n = start;
        if (ci->num > 0) {
                /* Forward - start of next part */
                n += 1;
@@ -386,15 +411,121 @@ DEF_CMD(mp_step_part)
 
        /* If this part is empty, need to move to next visible part */
        mp_normalize(mpi, m, vis);
+       first_vis = 0;
+       while (vis && vis[first_vis] == 'i')
+               first_vis++;
+       while (ci->num < 0 && m->ref.docnum == start && n > first_vis) {
+               /* didn't move - must have an empty part, try further */
+               n -= 1;
+               change_part(mpi, m, n, 0);
+               mp_normalize(mpi, m, vis);
+       }
        post_move(m);
+       if (ci->num && start == m->ref.docnum)
+               return Efail;
        return m->ref.docnum + 1;
 }
 
+DEF_CMD(mp_get_boundary)
+{
+       /* return a mark past which rendering must not go. */
+       struct mark *m = ci->mark;
+
+       if (!m || !ci->comm2)
+               return Enoarg;
+       m = mark_dup(m);
+       call("doc:step-part", ci->home, ci->num, m, ci->str);
+       comm_call(ci->comm2, "cb", ci->focus, 0, m);
+       return 1;
+}
+
+struct mp_cb {
+       struct command c;
+       struct command *cb;
+       struct pane *p safe;
+       struct mark *m safe;
+       int last_ret;
+};
+
+DEF_CB(mp_content_cb)
+{
+       struct mp_cb *c = container_of(ci->comm, struct mp_cb, c);
+       struct mark *m1 = NULL;
+
+       if (ci->mark) {
+               m1 = c->m;
+               pre_move(m1);
+               if (m1->ref.m)
+                       mark_to_mark(m1->ref.m, ci->mark);
+               post_move(m1);
+       }
+
+       c->last_ret = comm_call(c->cb, ci->key, c->p,
+                               ci->num, m1, ci->str,
+                               ci->num2, NULL, ci->str2,
+                               ci->x, ci->y);
+       return c->last_ret;
+}
+
+DEF_CMD(mp_content)
+{
+       /* Call doc:content on any visible docs in the range.
+        * Callback must re-wrap any marks
+        */
+       struct mp_info *mpi = ci->home->doc_data;
+       struct mp_cb cb;
+       struct mark *m, *m2;
+       const char *invis = ci->str;
+       int ret = 1;
+
+       if (!ci->mark || !ci->comm2)
+               return Enoarg;
+       m = mark_dup(ci->mark);
+       m2 = ci->mark2;
+       cb.last_ret = 1;
+       while (cb.last_ret > 0 && m->ref.docnum < mpi->nparts &&
+              mpi->parts &&
+              (!m2 || m->ref.docnum <= m2->ref.docnum)) {
+               /* Need to call doc:content on this document */
+               int n = m->ref.docnum;
+               if ((!invis || invis[n] != 'i') && m->ref.m) {
+                       struct mark *m2a = NULL;
+                       struct mark *mtmp = NULL;
+                       cb.c = mp_content_cb;
+                       cb.cb = ci->comm2;
+                       cb.p = ci->focus;
+                       cb.m = m;
+
+                       if (m->ref.m)
+                               mtmp = mark_dup(m->ref.m);
+                       if (m2 && m2->ref.docnum == n && m2->ref.m)
+                               m2a = mark_dup(m2->ref.m);
+
+                       ret = home_call_comm(mpi->parts[n].pane,
+                                            ci->key, ci->home, &cb.c,
+                                            ci->num, mtmp, NULL,
+                                            ci->num2, m2a);
+                       mark_free(m2a);
+                       mark_free(mtmp);
+                       if (ret < 0)
+                               break;
+               }
+               if (cb.last_ret > 0) {
+                       pre_move(m);
+                       change_part(mpi, m, n+1, 0);
+                       post_move(m);
+               }
+       }
+       mark_free(m);
+       return ret;
+}
+
 DEF_CMD(mp_attr)
 {
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
        struct mark *m1 = NULL;
-       int ret;
+       struct part *p;
+       int ret = Efallthrough;
        int d;
        const char *attr = ci->str;
 
@@ -404,22 +535,22 @@ DEF_CMD(mp_attr)
        m1 = ci->mark->ref.m;
        d = ci->mark->ref.docnum;
 
-       if (d < mpi->nparts && m1 &&
-           doc_following(mpi->parts[d].pane, m1) == WEOF)
+       if (d < mpi->nparts && m1 && mpi->parts && (p = &mpi->parts[d]) &&
+           p->pane &&  doc_following(p->pane, m1) == WEOF)
                /* at the wrong end of a part */
                d += 1;
 
-       if (strncmp(attr, "multipart-next:", 15) == 0) {
+       if (strstarts(attr, "multipart-next:")) {
                d += 1;
                attr += 15;
                if (d >= mpi->nparts)
                        return 1;
-       } else if (strncmp(attr, "multipart-prev:", 15) == 0) {
+       } else if (strstarts(attr, "multipart-prev:")) {
                d -= 1;
                attr += 15;
                if (d < 0)
                        return 1;
-       } else if (strncmp(attr, "multipart-this:", 15) == 0)
+       } else if (strstarts(attr, "multipart-this:"))
                attr += 15;
 
        if (strcmp(attr, "multipart:part-num") == 0) {
@@ -430,7 +561,7 @@ DEF_CMD(mp_attr)
                return 1;
        }
 
-       if (d >= mpi->nparts || d < 0)
+       if (d >= mpi->nparts || d < 0 || !mpi->parts)
                return 1;
 
        if (attr != ci->str) {
@@ -442,15 +573,17 @@ DEF_CMD(mp_attr)
                return 1;
        }
 
-       if (d != ci->mark->ref.docnum) {
-               m1 = vmark_new(mpi->parts[d].pane, MARK_UNGROUPED, NULL);
-               call("doc:set-ref", mpi->parts[d].pane,
+       p = &mpi->parts[d];
+       if (d != ci->mark->ref.docnum && p->pane) {
+               m1 = mark_new(p->pane);
+               call("doc:set-ref", p->pane,
                     (d > ci->mark->ref.docnum), m1);
        }
 
-       ret = home_call(mpi->parts[d].pane,
-                       ci->key, ci->focus, ci->num, m1, ci->str,
-                       ci->num2, NULL, ci->str2, 0,0, ci->comm2);
+       if (p->pane)
+               ret = home_call(p->pane,
+                               ci->key, ci->focus, ci->num, m1, ci->str,
+                               ci->num2, NULL, ci->str2, 0,0, ci->comm2);
        if (d != ci->mark->ref.docnum)
                mark_free(m1);
        return ret;
@@ -458,7 +591,8 @@ DEF_CMD(mp_attr)
 
 DEF_CMD(mp_set_attr)
 {
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
+       struct part *p;
        struct mark *m = ci->mark;
        struct mark *m1;
        int dn;
@@ -468,26 +602,35 @@ DEF_CMD(mp_set_attr)
                return Enoarg;
        if (!m)
                return Efallthrough;
+       if (!mpi->parts)
+               return Efail;
        dn = m->ref.docnum;
        m1 = m->ref.m;
 
-       if (dn < mpi->nparts && m1 &&
-           doc_step(mpi->parts[dn].pane, m1, ci->num, 0) == WEOF) {
-               /* at the wrong end of a part */
-               if (ci->num)
-                       dn += 1;
-               else if (dn > 0)
-                       dn -= 1;
-       }
-
-       if (strncmp(attr, "multipart-prev:", 15) == 0) {
-               dn -= 1;
-               attr += 15;
-       } else if (strncmp(attr, "multipart-next:", 15) == 0) {
-               dn += 1;
-               attr += 15;
+       if (strstarts(attr, "multipart-")) {
+               /* Set an attribute on a part */
+               if (strstarts(attr, "multipart-prev:") &&
+                   dn > 0 && (p = &mpi->parts[dn-1]) && p->pane)
+                       attr_set_str(&p->pane->attrs,
+                                    attr+15, ci->str2);
+               else if (strstarts(attr, "multipart-next:") &&
+                        dn < mpi->nparts && (p = &mpi->parts[dn+1]) && p->pane)
+                       attr_set_str(&p->pane->attrs,
+                                    attr+15, ci->str2);
+               else if (strstarts(attr, "multipart-this:") &&
+                        (p = &mpi->parts[dn]) && p->pane)
+                       attr_set_str(&p->pane->attrs,
+                                    attr+15, ci->str2);
+               else
+                       return Efail;
+               return 1;
        }
-       return Efallthrough;
+       /* Send the request to a sub-document */
+       p = &mpi->parts[dn];
+       if (p->pane)
+               return call(ci->key, p->pane, ci->num, m1, ci->str,
+                           0, NULL, ci->str2);
+       return Efail;
 }
 
 DEF_CMD(mp_notify_close)
@@ -495,8 +638,20 @@ DEF_CMD(mp_notify_close)
        /* sub-document has been closed.
         * Can we survive? or should we just shut down?
         */
-       pane_close(ci->home);
-       return 1;
+       struct mp_info *mpi = ci->home->doc_data;
+       int i;
+
+       for (i = 0; i < mpi->nparts && mpi->parts; i++)
+               if (mpi->parts[i].pane == ci->focus) {
+                       /* sub-document has been closed.
+                        * Can we survive? or should we just shut down?
+                        */
+                       mpi->parts[i].pane = NULL;
+                       pane_close(ci->home);
+                       return 1;
+               }
+       /* Not a sub-pane, maybe an owner for vmarks */
+       return Efallthrough;
 }
 
 DEF_CMD(mp_notify_viewers)
@@ -506,6 +661,15 @@ DEF_CMD(mp_notify_viewers)
        return 1;
 }
 
+DEF_CMD(mp_doc_replaced)
+{
+       /* Something changed in a component, report that the
+        * whole doc changed - simplest for now.
+        */
+       pane_notify("doc:replaced", ci->home);
+       return 1;
+}
+
 static void mp_resize(struct mp_info *mpi safe, int size)
 {
        if (mpi->parts_size >= size)
@@ -517,7 +681,7 @@ static void mp_resize(struct mp_info *mpi safe, int size)
 
 DEF_CMD(mp_add)
 {
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
        struct mark *m;
        int n;
 
@@ -539,29 +703,79 @@ DEF_CMD(mp_add)
 
        pane_add_notify(ci->home, ci->focus, "Notify:Close");
        home_call(ci->focus, "doc:request:doc:notify-viewers", ci->home);
+       home_call(ci->focus, "doc:request:doc:replaced", ci->home);
 
        return 1;
 }
 
+DEF_CMD(mp_forward_by_num)
+{
+       struct mp_info *mpi = ci->home->doc_data;
+       struct mark *m1 = NULL, *m2 = NULL;
+       struct part *p;
+       const char *key;
+       int d;
+       int ret;
+
+       key = ksuffix(ci, "doc:multipart-");
+       d = atoi(key);
+       key = strchr(key, '-');
+       if (!key)
+               return Einval;
+       key += 1;
+
+       if (d >= mpi->nparts || d < 0 || !mpi->parts)
+               return 1;
+
+       if (ci->mark && ci->mark->ref.docnum == d)
+               m1 = ci->mark->ref.m;
+       if (ci->mark2 && ci->mark2->ref.docnum == d)
+               m2 = ci->mark2->ref.m;
+
+       p = &mpi->parts[d];
+       if (p->pane)
+               ret = call(key, p->pane, ci->num, m1, ci->str,
+                          ci->num2, m2, ci->str2, ci->x, ci->y, ci->comm2);
+       else
+               ret = Efail;
+       return ret;
+}
+
+DEF_CMD(mp_get_part)
+{
+       struct mp_info *mpi = ci->home->doc_data;
+       struct part *p;
+       int d = ci->num;
+
+       if (d < 0 || d >= mpi->nparts || !mpi->parts)
+               return Einval;
+       p = &mpi->parts[d];
+       if (p->pane)
+               comm_call(ci->comm2, "cb", p->pane);
+       return 1;
+}
+
 DEF_CMD(mp_forward)
 {
        /* forward this command to this/next/prev document based on
         * ci->mark2.
         * ci->mark is forwarded if it is in same document
         */
-       struct mp_info *mpi = ci->home->data;
+       struct mp_info *mpi = ci->home->doc_data;
+       struct part *p;
        struct mark *m1, *m2;
        const char *key;
        int d;
 
        if (!ci->mark2)
                return Enoarg;
-
+       if (!mpi->parts)
+               return Efail;
        m2 = ci->mark2->ref.m;
        d = ci->mark2->ref.docnum;
 
-       if (d < mpi->nparts && m2 &&
-           doc_following(mpi->parts[d].pane, m2) == WEOF)
+       if (d < mpi->nparts && m2 && (p = &mpi->parts[d]) && p->pane &&
+           doc_following(p->pane, m2) == WEOF)
                /* at the wrong end of a part */
                d += 1;
 
@@ -583,8 +797,40 @@ DEF_CMD(mp_forward)
        m1 = NULL;
        if (ci->mark && ci->mark->ref.docnum == d)
                m1 = ci->mark->ref.m;
-       return call(key, mpi->parts[d].pane, ci->num, m1, ci->str,
-                   ci->num2, NULL, ci->str2, 0,0, ci->comm2);
+       p = &mpi->parts[d];
+       if (p->pane)
+               return call(key, p->pane, ci->num, m1, ci->str,
+                           ci->num2, NULL, ci->str2, 0,0, ci->comm2);
+       return Efail;
+}
+
+DEF_CMD(mp_val_marks)
+{
+       struct mark *m1, *m2;
+
+       if (!ci->mark || !ci->mark2)
+               return Enoarg;
+
+       if (ci->mark->ref.docnum < ci->mark2->ref.docnum)
+               return 1;
+       if (ci->mark->ref.docnum > ci->mark2->ref.docnum) {
+               LOG("mp_val_marks: docs not in order");
+               return Efalse;
+       }
+
+       m1 = ci->mark->ref.m;
+       m2 = ci->mark->ref.m;
+       if (m1 == m2)
+               return 1;
+       if (m1 && m2 && m1->seq > m2->seq) {
+               LOG("mp_val_marks: subordinate marks out of order!");
+               return Efalse;
+       }
+       if (!m1)
+               LOG("mp_val_marks: m1 is NULL");
+       else if (!m2 || marks_validate(m1, m2))
+               return 1;
+       return Efalse;
 }
 
 static void mp_init_map(void)
@@ -592,18 +838,24 @@ static void mp_init_map(void)
        mp_map = key_alloc();
        key_add_chain(mp_map, doc_default_cmd);
        key_add(mp_map, "doc:set-ref", &mp_set_ref);
-       key_add(mp_map, "doc:step", &mp_step);
+       key_add(mp_map, "doc:char", &mp_char);
+       key_add(mp_map, "doc:content", &mp_content);
+       key_add(mp_map, "doc:content-bytes", &mp_content);
        key_add(mp_map, "doc:get-attr", &mp_attr);
        key_add(mp_map, "doc:set-attr", &mp_set_attr);
        key_add(mp_map, "doc:step-part", &mp_step_part);
+       key_add(mp_map, "doc:get-boundary", &mp_get_boundary);
        key_add(mp_map, "Close", &mp_close);
-       key_add(mp_map, "Free", &mp_free);
        key_add(mp_map, "Notify:Close", &mp_notify_close);
        key_add(mp_map, "doc:notify-viewers", &mp_notify_viewers);
+       key_add(mp_map, "doc:replaced", &mp_doc_replaced);
        key_add(mp_map, "multipart-add", &mp_add);
+       key_add(mp_map, "debug:validate-marks", &mp_val_marks);
+       key_add(mp_map, "doc:multipart:get-part", &mp_get_part);
        key_add_prefix(mp_map, "multipart-this:", &mp_forward);
        key_add_prefix(mp_map, "multipart-next:", &mp_forward);
        key_add_prefix(mp_map, "multipart-prev:", &mp_forward);
+       key_add_prefix(mp_map, "doc:multipart-", &mp_forward_by_num);
 }
 DEF_LOOKUP_CMD(mp_handle, mp_map);
 
@@ -612,17 +864,14 @@ DEF_CMD(attach_mp)
        struct mp_info *mpi;
        struct pane *h;
 
-       alloc(mpi, pane);
-
-       h = doc_register(ci->home, &mp_handle.c, mpi);
-       if (h) {
-               mpi->doc.refcnt = mp_mark_refcnt;
-               attr_set_str(&h->attrs, "render-default", "text");
-               return comm_call(ci->comm2, "callback:doc", h);
-       }
+       h = doc_register(ci->home, &mp_handle.c);
+       if (!h)
+               return Efail;
+       mpi = h->doc_data;
 
-       free(mpi);
-       return Efail;
+       mpi->doc.refcnt = mp_mark_refcnt;
+       attr_set_str(&h->attrs, "render-default", "text");
+       return comm_call(ci->comm2, "callback:doc", h);
 }
 
 void edlib_init(struct pane *ed safe)