]> git.neil.brown.name Git - edlib.git/blob - doc-multipart.c
TODO: clean out done items.
[edlib.git] / doc-multipart.c
1 /*
2  * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * doc-multipart: Present a sequence of documents as though it were
6  *   just one.
7  *   This is used for stitching together the parts of a MIME email message.
8  *
9  * The document is created empty, and then given subordinate documents
10  * using a "multipart-add" command which causes the "focus" to be added
11  * to a list.
12  * If more sophisticated edits are needed, they can come later.
13  */
14
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <fcntl.h>
18 #include <string.h>
19 #include <stdio.h>
20
21 #define PRIVATE_DOC_REF
22
23 struct doc_ref {
24         struct mark *m;
25         int docnum; /* may be 'nparts', in which case 'm' == NULL */
26 };
27
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
30  * multipart document.
31  */
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))
35
36 #define DOC_DATA_TYPE struct mp_info
37
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)
42 #include "core.h"
43
44 struct mp_info {
45         struct doc      doc;
46         int             nparts;
47         int             parts_size;
48         struct part {
49                 struct pane     *pane;
50         } *parts;
51 };
52 #include "core-pane.h"
53
54 static struct map *mp_map safe;
55
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.
59  */
60 static void pre_move(struct mark *m safe)
61 {
62         struct mark *m2;
63
64         if (!m->ref.m || GET_REFS(m->ref.m) == 1)
65                 return;
66         /* Mark is shared, make it unshared */
67         m2 = mark_dup(m->ref.m);
68         ADD_REFS(m->ref.m, -1);
69         SET_REFS(m2, 1);
70         m->ref.m = m2;
71 }
72
73 static void post_move(struct mark *m)
74 {
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.
79          */
80         struct mark *m2, *mtarget;
81
82         if (!m || hlist_unhashed(&m->all))
83                 return;
84         ASSERT(m->ref.m == NULL || GET_REFS(m->ref.m) == 1);
85         mtarget = m;
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)))
91                 mtarget = m2;
92         if (mtarget != m)
93                 /* m should be after mtarget */
94                 mark_to_mark_noref(m, mtarget);
95
96         mtarget = m;
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)))
102                 mtarget = m2;
103         if (mtarget != m)
104                 /* m should be before mtarget */
105                 mark_to_mark_noref(m, mtarget);
106
107         if (!m->ref.m)
108                 return;
109         ASSERT(GET_REFS(m->ref.m) == 1);
110         /* Check if it should be shared */
111         m2 = mark_next(m);
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);
116                         mark_free(m->ref.m);
117                         m->ref.m = m2->ref.m;
118                         ADD_REFS(m->ref.m, 1);
119                         return;
120                 }
121         }
122         m2 = mark_prev(m);
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);
127                         mark_free(m->ref.m);
128                         m->ref.m = m2->ref.m;
129                         ADD_REFS(m->ref.m, 1);
130                         return;
131                 }
132         }
133 }
134
135 static void mp_mark_refcnt(struct mark *m safe, int inc)
136 {
137         if (!m->ref.m)
138                 return;
139
140         if (inc > 0)
141                 /* Duplicate being created of this mark */
142                 ADD_REFS(m->ref.m, 1);
143
144         if (inc < 0) {
145                 /* mark is being discarded, or ref over-written */
146                 ADD_REFS(m->ref.m, -1);
147                 if (GET_REFS(m->ref.m) == 0)
148                         mark_free(m->ref.m);
149                 m->ref.m = NULL;
150         }
151 }
152
153 static void mp_check_consistent(struct mp_info *mpi safe)
154 {
155         struct doc *d = &mpi->doc;
156 #if 0
157         struct mark *m;
158         int s = -1;
159         int max = 1000;
160
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;
164                              m = mark_next(m))
165                                 if (m && m->ref.m)
166                                         printf("%p %d %d\n", m, m->seq,
167                                                m->ref.m->seq);
168
169                         abort();
170                 }
171                 s = m->ref.m->seq;
172                 if (max-- < 0)
173                         break;
174         }
175 #endif
176         doc_check_consistent(d);
177 }
178
179 static void change_part(struct mp_info *mpi safe, struct mark *m safe,
180                         int part, int end)
181 {
182         struct mark *m1;
183         struct part *p;
184
185         if (part < 0 || part > mpi->nparts || !mpi->parts)
186                 return;
187         if (m->ref.m) {
188                 ASSERT(GET_REFS(m->ref.m) == 1);
189                 SET_REFS(m->ref.m, 0);
190                 mark_free(m->ref.m);
191                 m->ref.m = NULL;
192         }
193         if (part < mpi->nparts && (p = &mpi->parts[part]) && p->pane) {
194                 m1 = mark_new(p->pane);
195                 if (m1) {
196                         call("doc:set-ref", p->pane, !end, m1);
197                         m->ref.m = m1;
198                         SET_REFS(m1, 1);
199                 }
200         } else
201                 m->ref.m = NULL;
202         m->ref.docnum = part;
203 }
204
205 static void mp_normalize(struct mp_info *mpi safe, struct mark *m safe,
206                          const char *vis)
207 {
208         /* If points the end of a document, point to the start
209          * of the next instead.
210          */
211         struct part *p;
212         if (!mpi->parts)
213                 return;
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')
218                         n += 1;
219                 change_part(mpi, m, n, 0);
220         }
221 }
222
223 DEF_CMD_CLOSED(mp_close)
224 {
225         struct mp_info *mpi = ci->home->doc_data;
226         int i;
227         struct mark *m;
228
229         for (m = mark_first(&mpi->doc); m ; m = mark_next(m))
230                 if (m->ref.m) {
231                         struct mark *m2 = m->ref.m;
232                         m->ref.m = NULL;
233                         ADD_REFS(m2, -1);
234                         if (GET_REFS(m2) == 0)
235                                 mark_free(m2);
236                 }
237         if (!mpi->parts)
238                 return Efallthrough;
239         for (i = 0; i < mpi->nparts; i++) {
240                 struct pane *p = mpi->parts[i].pane;
241                 if (p)
242                         call("doc:closed", p);
243         }
244         free(mpi->parts);
245         mpi->parts = NULL;
246         return Efallthrough;
247 }
248
249 DEF_CMD(mp_set_ref)
250 {
251         struct mp_info *mpi = ci->home->doc_data;
252         const char *vis = ci->str && (int)strlen(ci->str) >= mpi->nparts ?
253                 ci->str : NULL;
254         int ret = 1;
255
256         if (!ci->mark)
257                 return Enoarg;
258
259         /* Need to trigger a point:moved notification.  FIXME I wonder
260          * if this can be simpler
261          */
262         mark_step(ci->mark, 0);
263
264         if (!ci->mark->ref.m && !ci->mark->ref.docnum) {
265                 /* First time set-ref was called */
266                 pre_move(ci->mark);
267                 change_part(mpi, ci->mark, 0, 0);
268                 mark_to_end(ci->home, ci->mark, 0);
269                 post_move(ci->mark);
270         }
271         pre_move(ci->mark);
272
273         if (ci->num == 1) {
274                 /* start */
275                 int n = 0;
276                 while (n < mpi->nparts && vis && vis[n] == 'i')
277                         n += 1;
278                 change_part(mpi, ci->mark, n, 0);
279                 mp_normalize(mpi, ci->mark, vis);
280         } else
281                 change_part(mpi, ci->mark, mpi->nparts, 1);
282
283         post_move(ci->mark);
284         mp_check_consistent(mpi);
285         return ret;
286 }
287
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)
291 {
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 ?
297                 str : NULL;
298         int n;
299         int ret;
300
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
304          * order.
305          */
306
307         mp_check_consistent(mpi);
308
309         if (move) {
310                 mark_step(m, forward);
311                 pre_move(m);
312         }
313
314         m1 = m->ref.m;
315
316         if (m->ref.docnum >= mpi->nparts || !mpi->parts)
317                 ret = -1;
318         else
319                 ret = home_call(mpi->parts[m->ref.docnum].pane,
320                                 "doc:char", home,
321                                 move ? (forward ? 1 : -1) : 0,
322                                 m1, str,
323                                 move ? 0 : (forward ? 1 : -1),
324                                 NULL, NULL);
325         while (ret == CHAR_RET(WEOF) || ret == -1) {
326                 if (!move && m == mark) {
327                         /* don't change mark when not moving */
328                         m = mark_dup(m);
329                         pre_move(m);
330                 }
331                 if (forward) {
332                         if (m->ref.docnum >= mpi->nparts)
333                                 break;
334                         n = m->ref.docnum + 1;
335                         while (n < mpi->nparts && vis && vis[n] == 'i')
336                                 n += 1;
337                         change_part(mpi, m, n, 0);
338                 } else {
339                         n = m->ref.docnum - 1;
340                         while (n >= 0 && vis && vis[n] == 'i')
341                                 n -= 1;
342                         if (n < 0)
343                                 break;
344                         change_part(mpi, m, n, 1);
345                 }
346                 m1 = m->ref.m;
347                 if (m->ref.docnum >= mpi->nparts || !mpi->parts)
348                         ret = -1;
349                 else
350                         ret = home_call(mpi->parts[m->ref.docnum].pane,
351                                         "doc:char", home,
352                                         move ? (forward ? 1 : -1) : 0,
353                                         m1, str,
354                                         move ? 0 : (forward ? 1 : -1));
355         }
356         if (move) {
357                 mp_normalize(mpi, mark, vis);
358                 post_move(mark);
359         }
360
361         if (m != mark)
362                 mark_free(m);
363
364         mp_check_consistent(mpi);
365         return ret == -1 ? (int)CHAR_RET(WEOF) : ret;
366 }
367
368 DEF_CMD(mp_char)
369 {
370         return do_char_byte(ci);
371 }
372
373 DEF_CMD(mp_step_part)
374 {
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.
382          */
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 ?
386                 ci->str : NULL;
387         int start;
388         int first_vis;
389         int n;
390
391         if (!m)
392                 return Enoarg;
393         pre_move(m);
394         start = m->ref.docnum;
395         n = start;
396         if (ci->num > 0) {
397                 /* Forward - start of next part */
398                 n += 1;
399                 while (n < mpi->nparts && vis && vis[n] == 'i')
400                         n += 1;
401         } else if (ci->num < 0) {
402                 /* Backward - start of prev part */
403                 n -= 1;
404                 while (n >= 0 && vis && vis[n] == 'i')
405                         n -= 1;
406                 if (n < 0)
407                         n = m->ref.docnum;
408         }
409         /* otherwise start of this part */
410         change_part(mpi, m, n, 0);
411
412         /* If this part is empty, need to move to next visible part */
413         mp_normalize(mpi, m, vis);
414         first_vis = 0;
415         while (vis && vis[first_vis] == 'i')
416                 first_vis++;
417         while (ci->num < 0 && m->ref.docnum == start && n > first_vis) {
418                 /* didn't move - must have an empty part, try further */
419                 n -= 1;
420                 change_part(mpi, m, n, 0);
421                 mp_normalize(mpi, m, vis);
422         }
423         post_move(m);
424         if (ci->num && start == m->ref.docnum)
425                 return Efail;
426         return m->ref.docnum + 1;
427 }
428
429 DEF_CMD(mp_get_boundary)
430 {
431         /* return a mark past which rendering must not go. */
432         struct mark *m = ci->mark;
433
434         if (!m || !ci->comm2)
435                 return Enoarg;
436         m = mark_dup(m);
437         call("doc:step-part", ci->home, ci->num, m, ci->str);
438         comm_call(ci->comm2, "cb", ci->focus, 0, m);
439         return 1;
440 }
441
442 struct mp_cb {
443         struct command c;
444         struct command *cb;
445         struct pane *p safe;
446         struct mark *m safe;
447         int last_ret;
448 };
449
450 DEF_CB(mp_content_cb)
451 {
452         struct mp_cb *c = container_of(ci->comm, struct mp_cb, c);
453         struct mark *m1 = NULL;
454
455         if (ci->mark) {
456                 m1 = c->m;
457                 pre_move(m1);
458                 if (m1->ref.m)
459                         mark_to_mark(m1->ref.m, ci->mark);
460                 post_move(m1);
461         }
462
463         c->last_ret = comm_call(c->cb, ci->key, c->p,
464                                 ci->num, m1, ci->str,
465                                 ci->num2, NULL, ci->str2,
466                                 ci->x, ci->y);
467         return c->last_ret;
468 }
469
470 DEF_CMD(mp_content)
471 {
472         /* Call doc:content on any visible docs in the range.
473          * Callback must re-wrap any marks
474          */
475         struct mp_info *mpi = ci->home->doc_data;
476         struct mp_cb cb;
477         struct mark *m, *m2;
478         const char *invis = ci->str;
479         int ret = 1;
480
481         if (!ci->mark || !ci->comm2)
482                 return Enoarg;
483         m = mark_dup(ci->mark);
484         m2 = ci->mark2;
485         cb.last_ret = 1;
486         while (cb.last_ret > 0 && m->ref.docnum < mpi->nparts &&
487                mpi->parts &&
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;
495                         cb.cb = ci->comm2;
496                         cb.p = ci->focus;
497                         cb.m = m;
498
499                         if (m->ref.m)
500                                 mtmp = mark_dup(m->ref.m);
501                         if (m2 && m2->ref.docnum == n && m2->ref.m)
502                                 m2a = mark_dup(m2->ref.m);
503
504                         ret = home_call_comm(mpi->parts[n].pane,
505                                              ci->key, ci->home, &cb.c,
506                                              ci->num, mtmp, NULL,
507                                              ci->num2, m2a);
508                         mark_free(m2a);
509                         mark_free(mtmp);
510                         if (ret < 0)
511                                 break;
512                 }
513                 if (cb.last_ret > 0) {
514                         pre_move(m);
515                         change_part(mpi, m, n+1, 0);
516                         post_move(m);
517                 }
518         }
519         mark_free(m);
520         return ret;
521 }
522
523 DEF_CMD(mp_attr)
524 {
525         struct mp_info *mpi = ci->home->doc_data;
526         struct mark *m1 = NULL;
527         struct part *p;
528         int ret = Efallthrough;
529         int d;
530         const char *attr = ci->str;
531
532         if (!ci->mark || !attr)
533                 return Enoarg;
534
535         m1 = ci->mark->ref.m;
536         d = ci->mark->ref.docnum;
537
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 */
541                 d += 1;
542
543         if (strstarts(attr, "multipart-next:")) {
544                 d += 1;
545                 attr += 15;
546                 if (d >= mpi->nparts)
547                         return 1;
548         } else if (strstarts(attr, "multipart-prev:")) {
549                 d -= 1;
550                 attr += 15;
551                 if (d < 0)
552                         return 1;
553         } else if (strstarts(attr, "multipart-this:"))
554                 attr += 15;
555
556         if (strcmp(attr, "multipart:part-num") == 0) {
557                 char n[11];
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);
561                 return 1;
562         }
563
564         if (d >= mpi->nparts || d < 0 || !mpi->parts)
565                 return 1;
566
567         if (attr != ci->str) {
568                 /* Get a pane attribute, not char attribute */
569                 char *s = pane_attr_get(mpi->parts[d].pane, attr);
570                 if (s)
571                         return comm_call(ci->comm2, "callback", ci->focus,
572                                          0, ci->mark, s, 0, NULL, ci->str);
573                 return 1;
574         }
575
576         p = &mpi->parts[d];
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);
581         }
582
583         if (p->pane)
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)
588                 mark_free(m1);
589         return ret;
590 }
591
592 DEF_CMD(mp_set_attr)
593 {
594         struct mp_info *mpi = ci->home->doc_data;
595         struct part *p;
596         struct mark *m = ci->mark;
597         struct mark *m1;
598         int dn;
599         const char *attr = ci->str;
600
601         if (!attr)
602                 return Enoarg;
603         if (!m)
604                 return Efallthrough;
605         if (!mpi->parts)
606                 return Efail;
607         dn = m->ref.docnum;
608         m1 = m->ref.m;
609
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,
615                                      attr+15, ci->str2);
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,
619                                      attr+15, ci->str2);
620                 else if (strstarts(attr, "multipart-this:") &&
621                          (p = &mpi->parts[dn]) && p->pane)
622                         attr_set_str(&p->pane->attrs,
623                                      attr+15, ci->str2);
624                 else
625                         return Efail;
626                 return 1;
627         }
628         /* Send the request to a sub-document */
629         p = &mpi->parts[dn];
630         if (p->pane)
631                 return call(ci->key, p->pane, ci->num, m1, ci->str,
632                             0, NULL, ci->str2);
633         return Efail;
634 }
635
636 DEF_CMD(mp_notify_close)
637 {
638         /* sub-document has been closed.
639          * Can we survive? or should we just shut down?
640          */
641         struct mp_info *mpi = ci->home->doc_data;
642         int i;
643
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?
648                          */
649                         mpi->parts[i].pane = NULL;
650                         pane_close(ci->home);
651                         return 1;
652                 }
653         /* Not a sub-pane, maybe an owner for vmarks */
654         return Efallthrough;
655 }
656
657 DEF_CMD(mp_notify_viewers)
658 {
659         /* The autoclose document wants to know if it should close.
660          * tell it "no" */
661         return 1;
662 }
663
664 DEF_CMD(mp_doc_replaced)
665 {
666         /* Something changed in a component, report that the
667          * whole doc changed - simplest for now.
668          */
669         pane_notify("doc:replaced", ci->home);
670         return 1;
671 }
672
673 static void mp_resize(struct mp_info *mpi safe, int size)
674 {
675         if (mpi->parts_size >= size)
676                 return;
677         size += 4;
678         mpi->parts = realloc(mpi->parts, size * sizeof(struct part));
679         mpi->parts_size = size;
680 }
681
682 DEF_CMD(mp_add)
683 {
684         struct mp_info *mpi = ci->home->doc_data;
685         struct mark *m;
686         int n;
687
688         mp_resize(mpi, mpi->nparts+1);
689         if (ci->mark == NULL)
690                 n = mpi->nparts;
691         else
692                 n = ci->mark->ref.docnum;
693         memmove(&mpi->parts[n+1], &mpi->parts[n],
694                 (mpi->nparts - n)*sizeof(mpi->parts[0]));
695         mpi->nparts += 1;
696         mpi->parts[n].pane = ci->focus;
697         hlist_for_each_entry(m, &mpi->doc.marks, all)
698                 if (m->ref.docnum >= n)
699                         m->ref.docnum ++;
700         if (ci->mark)
701                 /* move mark to start of new part */
702                 change_part(mpi, ci->mark, n, 0);
703
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);
707
708         return 1;
709 }
710
711 DEF_CMD(mp_forward_by_num)
712 {
713         struct mp_info *mpi = ci->home->doc_data;
714         struct mark *m1 = NULL, *m2 = NULL;
715         struct part *p;
716         const char *key;
717         int d;
718         int ret;
719
720         key = ksuffix(ci, "doc:multipart-");
721         d = atoi(key);
722         key = strchr(key, '-');
723         if (!key)
724                 return Einval;
725         key += 1;
726
727         if (d >= mpi->nparts || d < 0 || !mpi->parts)
728                 return 1;
729
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;
734
735         p = &mpi->parts[d];
736         if (p->pane)
737                 ret = call(key, p->pane, ci->num, m1, ci->str,
738                            ci->num2, m2, ci->str2, ci->x, ci->y, ci->comm2);
739         else
740                 ret = Efail;
741         return ret;
742 }
743
744 DEF_CMD(mp_get_part)
745 {
746         struct mp_info *mpi = ci->home->doc_data;
747         struct part *p;
748         int d = ci->num;
749
750         if (d < 0 || d >= mpi->nparts || !mpi->parts)
751                 return Einval;
752         p = &mpi->parts[d];
753         if (p->pane)
754                 comm_call(ci->comm2, "cb", p->pane);
755         return 1;
756 }
757
758 DEF_CMD(mp_forward)
759 {
760         /* forward this command to this/next/prev document based on
761          * ci->mark2.
762          * ci->mark is forwarded if it is in same document
763          */
764         struct mp_info *mpi = ci->home->doc_data;
765         struct part *p;
766         struct mark *m1, *m2;
767         const char *key;
768         int d;
769
770         if (!ci->mark2)
771                 return Enoarg;
772         if (!mpi->parts)
773                 return Efail;
774         m2 = ci->mark2->ref.m;
775         d = ci->mark2->ref.docnum;
776
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 */
780                 d += 1;
781
782         if ((key = ksuffix(ci, "multipart-next:"))[0]) {
783                 d += 1;
784                 if (d >= mpi->nparts)
785                         return 1;
786         } else if ((key = ksuffix(ci, "multipart-prev:"))[0]) {
787                 d -= 1;
788                 if (d < 0)
789                         return 1;
790         } else if ((key = ksuffix(ci, "multipart-this:"))[0]) {
791                 ;
792         } else return Einval;
793
794         if (d >= mpi->nparts || d < 0)
795                 return 1;
796
797         m1 = NULL;
798         if (ci->mark && ci->mark->ref.docnum == d)
799                 m1 = ci->mark->ref.m;
800         p = &mpi->parts[d];
801         if (p->pane)
802                 return call(key, p->pane, ci->num, m1, ci->str,
803                             ci->num2, NULL, ci->str2, 0,0, ci->comm2);
804         return Efail;
805 }
806
807 DEF_CMD(mp_val_marks)
808 {
809         struct mark *m1, *m2;
810
811         if (!ci->mark || !ci->mark2)
812                 return Enoarg;
813
814         if (ci->mark->ref.docnum < ci->mark2->ref.docnum)
815                 return 1;
816         if (ci->mark->ref.docnum > ci->mark2->ref.docnum) {
817                 LOG("mp_val_marks: docs not in order");
818                 return Efalse;
819         }
820
821         m1 = ci->mark->ref.m;
822         m2 = ci->mark->ref.m;
823         if (m1 == m2)
824                 return 1;
825         if (m1 && m2 && m1->seq > m2->seq) {
826                 LOG("mp_val_marks: subordinate marks out of order!");
827                 return Efalse;
828         }
829         if (!m1)
830                 LOG("mp_val_marks: m1 is NULL");
831         else if (!m2 || marks_validate(m1, m2))
832                 return 1;
833         return Efalse;
834 }
835
836 static void mp_init_map(void)
837 {
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);
859 }
860 DEF_LOOKUP_CMD(mp_handle, mp_map);
861
862 DEF_CMD(attach_mp)
863 {
864         struct mp_info *mpi;
865         struct pane *h;
866
867         h = doc_register(ci->home, &mp_handle.c);
868         if (!h)
869                 return Efail;
870         mpi = h->doc_data;
871
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);
875 }
876
877 void edlib_init(struct pane *ed safe)
878 {
879         mp_init_map();
880         call_comm("global-set-command", ed, &attach_mp, 0, NULL,
881                   "attach-doc-multipart");
882 }