]> git.neil.brown.name Git - edlib.git/blob - doc-docs.c
TODO: clean out done items.
[edlib.git] / doc-docs.c
1 /*
2  * Copyright Neil Brown ©2016-2017-2019 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Document management is eased by having a well defined collection
6  * of documents.  This module provides a pane and a document to manage
7  * that collection.
8  *
9  * The document presents as a list of documents, called "*Documents*",
10  * providing a "line-format" to guide display of each line.
11  * The auxiliary pane becomes the parent of all attached documents, so
12  * that the list of children is exactly the content of the document.
13  * This pane receives doc:revisit notification  from the
14  * individual documents, and also requests notification of
15  * doc:status-changed.
16  *
17  * Supported global operations include:
18  * docs:byname - report pane with given (str)name
19  * docs:byfd - find a document given a path and file-descriptor.
20  * Each document is asked whether it matches the path and/or fd.
21  * docs:choose - choose and return a document which is not currently displayed
22  * somewhere.
23  * docs:save-all - each each document to save itself
24  * docs:show-modified - display a pane, in given window, listing just the
25  * documents that are modified and might need saving.
26  * Pane auto-closes when empty.
27  *
28  * After a document is created and bound to a pane "doc:appeared-*" is called
29  * which adds that pane to the list if it isn't already attached somewhere else.
30  * If docs sees two documents with the same name, it changes one to keep them
31  * all unique.
32  *
33  */
34
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <stdio.h>
39
40 #include "safe.h"
41 #define PRIVATE_DOC_REF
42 struct doc_ref {
43         struct pane     *p;
44         unsigned int    ignore;
45 };
46 #define DOC_SHARESREF
47 #define DOC_DATA_TYPE struct docs
48 #define DOC_NEXT(d,m,r,b) docs_next(d,r,b)
49 #define DOC_PREV(d,m,r,b) docs_prev(d,r,b)
50
51 #define PANE_DATA_PTR_TYPE struct pane *
52 #define PANE_DATA_VOID_2
53 #include "core.h"
54
55 static struct map *docs_map, *docs_aux_map, *docs_modified_map,
56         *docs_callback_map;
57 DEF_LOOKUP_CMD(docs_handle, docs_map);
58 DEF_LOOKUP_CMD(docs_aux, docs_aux_map);
59 DEF_LOOKUP_CMD(docs_modified_handle, docs_modified_map);
60 DEF_LOOKUP_CMD(docs_callback_handle, docs_callback_map);
61
62 struct docs {
63         struct doc              doc;
64         struct command          callback;
65         struct pane             *collection safe;
66 };
67 #include "core-pane.h"
68
69 static void docs_demark(struct pane *d safe, struct pane *p safe)
70 {
71         /* This document (p) is about to be moved in the list (d->collection).
72          * Any mark pointing at it is moved forward
73          */
74         struct docs *doc = d->doc_data;
75         struct mark *m, *first = NULL;
76         struct pane *next;
77         struct pane *col = doc->collection;
78
79         if (list_empty(&p->siblings) ||
80             p == list_last_entry(&col->children, struct pane, siblings))
81                 next = NULL;
82         else
83                 next = list_next_entry(p, siblings);
84
85         for (m = mark_first(&doc->doc);
86              m;
87              m = mark_next(m))
88                 if (m->ref.p == p) {
89                         if (!first) {
90                                 first = mark_prev(m);
91                                 if (!first)
92                                         first = m;
93                         }
94                         m->ref.p = next;
95                 } else if (first)
96                         break;
97         if (first)
98                 pane_notify("doc:replaced", d, 1, first);
99 }
100
101 static void docs_enmark(struct pane *d safe, struct pane *p safe)
102 {
103         /* This document has just been added to the list.
104          * any mark pointing just past it is moved back.
105          */
106         struct docs *doc = d->doc_data;
107         struct mark *m, *first = NULL;
108         struct pane *next;
109         struct pane *col = doc->collection;
110
111         if (p == list_last_entry(&col->children, struct pane, siblings))
112                 next = NULL;
113         else
114                 next = list_next_entry(p, siblings);
115
116         for (m = mark_first(&doc->doc);
117              m;
118              m = mark_next(m))
119                 if (m->ref.p == next) {
120                         if (!first)
121                                 first = m;
122                         m->ref.p = p;
123                 } else if (first)
124                         break;
125         if (first)
126                 pane_notify("doc:replaced", d, 1, first);
127 }
128
129 static bool doc_save(struct pane *p safe, struct pane *focus safe, int test)
130 {
131         char *fn = pane_attr_get(p, "filename");
132         char *mod = pane_attr_get(p, "doc-modified");
133         if (!fn || !*fn)
134                 call("Message", focus, 0, NULL,
135                      "File has no filename - cannot be saved.");
136         else if (!mod || strcmp(mod, "yes") != 0)
137                 call("Message", focus, 0, NULL,
138                      "File not modified - no need to save.");
139         else if (test)
140                 return True;
141         else
142                 home_call(p, "doc:save-file", focus);
143         return False;
144 }
145
146 static void check_name(struct docs *docs safe, struct pane *pane safe)
147 {
148         struct doc *d = &pane->doc;
149         char *nname;
150         int unique = 1;
151         int conflict = 1;
152
153         if (!d->name)
154                 d->name = strdup("*unknown*");
155
156         nname = malloc(strlen(d->name) + sizeof("<xxx>"));
157         while (conflict && unique < 1000) {
158                 struct pane *p;
159                 conflict = 0;
160                 if (unique > 1)
161                         sprintf(nname, "%s<%d>", d->name, unique);
162                 else
163                         strcpy(nname, d->name);
164                 list_for_each_entry(p, &docs->collection->children, siblings) {
165                         struct doc *d2 = &p->doc;
166                         if (d != d2 && d2->name &&
167                             strcmp(nname, d2->name) == 0) {
168                                 conflict = 1;
169                                 unique += 1;
170                                 break;
171                         }
172                 }
173         }
174         if (unique > 1) {
175                 free(d->name);
176                 d->name = nname;
177         } else
178                 free(nname);
179 }
180
181 static void doc_checkname(struct pane *p safe, struct pane *d safe, int n)
182 {
183         struct docs *ds = d->doc_data;
184         ASSERT(p->parent->handle == &docs_aux.c);
185         check_name(ds, p);
186         if (n) {
187                 docs_demark(d, p);
188                 if (n > 0)
189                         list_move(&p->siblings, &ds->collection->children);
190                 else
191                         list_move_tail(&p->siblings, &ds->collection->children);
192                 docs_enmark(d, p);
193         }
194 }
195
196 /*
197  * Interactive saving of files, particularly as happens when the editor
198  * is exiting, pops up a document-list window which only display
199  * documents which need saving.  They can be saved or killed, both of which
200  * actions removes them from the list.  When the list is empty an event can be
201  * sent back to the pane that requested the popup.
202  */
203
204 static int docs_open(struct pane *home safe, struct pane *focus safe,
205                      struct mark *m, bool other);
206
207 DEF_CMD(docs_mod_next)
208 {
209         struct mark *m;
210
211         /* If this is the last, then quit */
212         if (!ci->mark)
213                 return Enoarg;
214         m = mark_dup(ci->mark);
215         call("doc:EOL", ci->home->parent, 1, m, NULL, 1);
216         /* Passing '0' is deliberate.  We don't want to render
217          * anything, just see if there is anything tha could be rendered.
218          */
219         if (call("doc:render-line", ci->focus, 0, m) < 0 ||
220             m->ref.p == NULL) {
221                 mark_free(m);
222                 return call("popup:close", ci->focus);
223         }
224         mark_free(m);
225         /* Ask viewer to move forward */
226         return 2;
227 }
228
229 DEF_CMD(docs_mod_quit)
230 {
231         return call("popup:close", ci->home);
232 }
233
234 DEF_CMD(docs_mod_other)
235 {
236         /* abort the current action, and open this in another window */
237         docs_open(ci->home, ci->focus, ci->mark, True);
238         call("Abort", ci->home);
239         return 1;
240 }
241
242 DEF_CMD(docs_mod_empty)
243 {
244         call("popup:close", ci->focus);
245         return 1;
246 }
247
248 DEF_CMD(docs_mod_noop)
249 {
250         /* Don't want anything else to fall through to default */
251         return 1;
252 }
253
254 DEF_CMD(docs_callback_complete)
255 {
256         struct pane *p;
257
258         p = home_call_ret(pane, ci->home, "doc:attach-view", ci->focus,
259                           0, NULL, "simple");
260         if (p) {
261                 attr_set_str(&p->attrs, "line-format", "%doc-name");
262                 attr_set_str(&p->attrs, "heading", "");
263                 attr_set_str(&p->attrs, "done-key", "Replace");
264                 p = call_ret(pane, "attach-render-complete", p);
265         }
266         if (p)
267                 return comm_call(ci->comm2, "callback:doc", p);
268         return Efail;
269 }
270
271 DEF_CMD(docs_callback_byname)
272 {
273         struct docs *doc = ci->home->doc_data;
274         struct pane *p;
275
276         if (ci->str == NULL || strcmp(ci->str, "*Documents*") == 0)
277                 return comm_call(ci->comm2, "callback:doc",
278                                  ci->home);
279         list_for_each_entry(p, &doc->collection->children, siblings) {
280                 struct doc *dc = &p->doc;
281                 char *n = dc->name;
282                 if (n && strcmp(ci->str, n) == 0)
283                         return comm_call(ci->comm2, "callback:doc", p);
284         }
285         return Efail;
286 }
287
288 DEF_CMD(docs_callback_byfd)
289 {
290         struct docs *doc = ci->home->doc_data;
291         struct pane *p;
292
293         list_for_each_entry(p, &doc->collection->children, siblings) {
294                 if (call("doc:same-file", p, 0, NULL, ci->str,
295                          ci->num2) > 0)
296                         return comm_call(ci->comm2, "callback:doc", p);
297         }
298         return Efail;
299 }
300
301 DEF_CMD(docs_callback_byeach)
302 {
303         struct docs *doc = ci->home->doc_data;
304         struct pane *p;
305         int ret = 1;
306
307         list_for_each_entry(p, &doc->collection->children, siblings) {
308                 int r;
309                 r = comm_call(ci->comm2, "callback:doc", p);
310                 if (r > ret)
311                         ret = r;
312                 if (r == Efalse)
313                         return ret;
314                 if (r < Efalse)
315                         return r;
316         }
317         return ret;
318 }
319
320 DEF_CMD(docs_callback_choose)
321 {
322         struct docs *doc = ci->home->doc_data;
323         struct pane *choice = NULL, *last = NULL;
324         struct pane *p;
325
326         /* Choose a document with no notifiees or no pointer,
327          * but ignore 'CLOSED'
328          */
329
330         list_for_each_entry(p, &doc->collection->children, siblings) {
331                 struct doc *d = &p->doc;
332
333                 if (p->damaged & DAMAGED_CLOSED)
334                         continue;
335                 last = p;
336                 if (list_empty(&p->notifiees)) {
337                         choice = p;
338                         break;
339                 }
340                 if (tlist_empty(&d->points)) {
341                         choice = p;
342                         break;
343                 }
344         }
345         if (!choice)
346                 choice = last;
347         if (!choice)
348                 choice = ci->home;
349         return comm_call(ci->comm2, "callback:doc", choice);
350 }
351
352 DEF_CMD(docs_callback_saveall)
353 {
354         struct docs *doc = ci->home->doc_data;
355         struct pane *p;
356         int dirlen = ci->str ? (int)strlen(ci->str) : -1;
357
358         list_for_each_entry(p, &doc->collection->children, siblings) {
359                 if (dirlen > 0) {
360                         char *fn = pane_attr_get(p, "dirname");
361                         if (!fn || strncmp(ci->str, fn, dirlen) != 0)
362                                 continue;
363                 }
364                 if (doc_save(p, p, ci->num2))
365                         /* Something needs to be saved, we were only asked
366                          * to test.
367                          */
368                         return 2;
369         }
370         return 1;
371 }
372
373 DEF_CMD(docs_callback_modified)
374 {
375         struct pane *p;
376
377         p = home_call_ret(pane, ci->home, "doc:attach-view", ci->focus,
378                           0, NULL, "simple");
379         if (!p)
380                 return Efail;
381         p = call_ret(pane, "attach-linefilter", p);
382         if (!p)
383                 return Efail;
384         attr_set_str(&p->attrs, "filter:attr", "doc-can-save");
385         attr_set_str(&p->attrs, "filter:match", "yes");
386         p = pane_register_2(p, 0, &docs_modified_handle.c);
387         if (!p)
388                 return Efail;
389         attr_set_str(&p->attrs, "doc-name", "*Modified Documents*");
390         attr_set_str(&p->attrs, "line-format", "%doc-name:20 %filename");
391         attr_set_str(&p->attrs, "heading",
392                      "<bold>Document             File</>\n"
393                      "<bold,underline>[s]ave [y]es [n]o [q]uit</>");
394         /* Don't want to inherit position from some earlier instance,
395          * always move to the start.
396          */
397         call("doc:file", p, -1);
398         return 1;
399 }
400
401 DEF_CMD(docs_callback_appeared)
402 {
403         struct docs *doc = ci->home->doc_data;
404         struct pane *p;
405
406         /* Always return Efallthrough so other handlers get a chance */
407         p = ci->focus;
408         if (!p)
409                 return Efallthrough;
410         if (p->parent != p->parent->parent)
411                 /* This has a parent which is not the root,
412                  * so we shouldn't interfere.
413                  */
414                 return Efallthrough;
415         if (p == ci->home)
416                 /* The docs doc is attached separately */
417                 return Efallthrough;
418         pane_reparent(p, doc->collection);
419         home_call(p, "doc:request:doc:revisit", doc->collection);
420         home_call(p, "doc:request:doc:status-changed",
421                   doc->collection);
422         doc_checkname(p, ci->home, ci->num ?: -1);
423
424         return Efallthrough;
425 }
426
427 DEF_CMD(doc_damage)
428 {
429         struct pane *dp = ci->home->data;
430         struct mark *m = mark_new(dp);
431         struct pane *child = ci->focus;
432
433         if (!child || !m)
434                 return Enoarg;
435         do {
436                 if (m->ref.p == child) {
437                         pane_notify("doc:replaced", dp, 1, m);
438                         break;
439                 }
440         } while (doc_next(dp, m) != WEOF);
441         mark_free(m);
442         return 1;
443 }
444
445 DEF_CMD(doc_revisit)
446 {
447         struct pane *p = ci->focus;
448         struct pane *dp = ci->home->data;
449         struct docs *docs = dp->doc_data;
450
451         if (!p)
452                 return Einval;
453         if (p->parent != docs->collection)
454                 return Efallthrough;
455         if (p == ci->home)
456                 return 1;
457         doc_checkname(p, dp, ci->num);
458         return 1;
459 }
460
461 static inline wint_t docs_next(struct pane *home safe, struct doc_ref *r safe, bool bytes)
462 {
463         struct docs *d = home->doc_data;
464         struct pane *p = r->p;
465
466         if (p == NULL)
467                 return WEOF;
468
469         if (p == list_last_entry(&d->collection->children,
470                                  struct pane, siblings))
471                 r->p = NULL;
472         else
473                 r->p = list_next_entry(p, siblings);
474         return '\n';
475 }
476 static inline wint_t docs_prev(struct pane *home safe, struct doc_ref *r safe, bool bytes)
477 {
478         struct docs *d = home->doc_data;
479         struct pane *p = r->p;
480
481         if (list_empty(&d->collection->children))
482                 return WEOF;
483         else if (!p)
484                 p = list_last_entry(&d->collection->children,
485                                     struct pane, siblings);
486         else if (p != list_first_entry(&d->collection->children,
487                                        struct pane, siblings))
488                 p = list_prev_entry(p, siblings);
489         else
490                 return WEOF;
491         r->p = p;
492         return '\n';
493 }
494
495 DEF_CMD(docs_char)
496 {
497         return do_char_byte(ci);
498 }
499
500 DEF_CMD(docs_set_ref)
501 {
502         struct docs *d = ci->home->doc_data;
503         struct mark *m = ci->mark;
504
505         if (!m)
506                 return Enoarg;
507
508         mark_to_end(ci->home, m, ci->num != 1);
509         if (ci->num == 1 && !list_empty(&d->collection->children))
510                 m->ref.p = list_first_entry(&d->collection->children,
511                                             struct pane, siblings);
512         else
513                 m->ref.p = NULL;
514
515         m->ref.ignore = 0;
516         return 1;
517 }
518
519 DEF_CMD(docs_doc_get_attr)
520 {
521         struct mark *m = ci->mark;
522         const char *attr = ci->str;
523         char *val;
524
525         if (!m || !attr)
526                 return Enoarg;
527
528         if (!m->ref.p)
529                 return Efallthrough;
530
531         val = pane_attr_get(m->ref.p, attr);
532         /* use 'while' instead of 'if' to allow 'break' */
533         while (!val && strcmp(attr, "doc-can-save") == 0) {
534                 char *mod, *fl, *dir;
535                 val = "no";
536                 mod = pane_attr_get(m->ref.p, "doc-modified");
537                 if (!mod || strcmp(mod, "yes") != 0)
538                         break;
539                 fl = pane_attr_get(m->ref.p, "filename");
540                 if (!fl || !*fl)
541                         break;
542                 dir = pane_attr_get(ci->focus, "only-here");
543                 if (!dir || strncmp(dir, fl, strlen(dir)) == 0)
544                         val = "yes";
545         }
546
547         if (!val)
548                 return Efallthrough;
549         comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, m, val,
550                   0, NULL, attr);
551         return 1;
552 }
553
554 DEF_CMD(docs_get_attr)
555 {
556         const char *attr = ci->str;
557         char *val;
558
559         if (!attr)
560                 return Enoarg;
561
562         if ((val = attr_find(ci->home->attrs, attr)) != NULL)
563                 ;
564         else if (strcmp(attr, "heading") == 0)
565                 val = "<bold,underline> Mod Document             File</>";
566         else if (strcmp(attr, "line-format") == 0)
567                 val = " %doc-modified:3 %doc-name:20 %filename";
568         else if (strcmp(attr, "render-default") == 0)
569                 val = "format";
570         else if (strcmp(attr, "render-simple") == 0)
571                 val = "format";
572         else if (strcmp(attr, "view-default") == 0)
573                 val = "viewer";
574         else if (strcmp(attr, "doc-type") == 0)
575                 val = "docs";
576         else
577                 return Efallthrough;
578
579         comm_call(ci->comm2, "callback:get_attr", ci->focus,
580                   0, NULL, val);
581         return 1;
582 }
583
584 static int docs_open(struct pane *home safe, struct pane *focus safe,
585                      struct mark *m, bool other)
586 {
587         struct pane *p = NULL;
588         struct pane *dp;
589         struct pane *par;
590
591         if (!m)
592                 return Enoarg;
593         dp = m->ref.p;
594         /* close this pane, open the given document. */
595         if (dp == NULL)
596                 return Efail;
597
598         if (other) {
599                 par = home_call_ret(pane, focus, "DocPane", dp);
600                 if (par) {
601                         pane_take_focus(par);
602                         return 1;
603                 }
604                 par = call_ret(pane, "OtherPane", focus);
605         } else
606                 par = call_ret(pane, "ThisPane", focus);
607         if (par)
608                 p = home_call_ret(pane, dp, "doc:attach-view", par, 1);
609         if (p) {
610                 pane_take_focus(p);
611                 return 1;
612         } else {
613                 return Efail;
614         }
615 }
616
617 static int docs_open_alt(struct pane *home safe, struct pane *focus safe,
618                          struct mark *m, char cmd)
619 {
620         struct pane *p;
621         struct pane *dp;
622         char *renderer = NULL;
623         char *viewer = NULL;
624         struct pane *par;
625         char buf[100];
626
627         if (!m)
628                 return Enoarg;
629         dp = m->ref.p;
630         /* close this pane, open the given document. */
631         if (dp == NULL)
632                 return Efail;
633
634         snprintf(buf, sizeof(buf), "render-cmd-%c", cmd);
635         renderer = pane_attr_get(dp, buf);
636         snprintf(buf, sizeof(buf), "view-cmd-%c", cmd);
637         viewer = pane_attr_get(dp, buf);
638         if (!renderer && !viewer)
639                 return Efail;
640
641         par = call_ret(pane, "ThisPane", focus);
642         if (!par)
643                 return Efail;
644         p = home_call_ret(pane, dp, "doc:attach-view", par, 1, NULL, buf+5);
645         if (p) {
646                 pane_take_focus(p);
647                 return 1;
648         } else {
649                 return Efail;
650         }
651 }
652
653 static int docs_bury(struct pane *focus safe)
654 {
655         /* If the docs list is in a tile, put something else there. */
656         /* FIXME should this be a function of the pane manager? */
657         struct pane *tile, *doc;
658         tile = call_ret(pane, "ThisPane", focus);
659         if (!tile)
660                 return 1;
661         /* Discourage this doc from being chosen again */
662         call("doc:notify:doc:revisit", focus, -1);
663         doc = call_ret(pane, "docs:choose", focus);
664         if (doc)
665                 home_call(doc, "doc:attach-view", tile);
666         return 1;
667 }
668
669 static int docs_save(struct pane *focus safe, struct mark *m)
670 {
671         struct pane *dp;
672
673         if (!m)
674                 return Enoarg;
675         dp = m->ref.p;
676         if (!dp)
677                 return Efail;
678         doc_save(dp, focus, 0);
679         return 1;
680 }
681
682 static int docs_kill(struct pane *focus safe, struct mark *m, int num)
683 {
684         struct pane *dp;
685         char *mod;
686
687         if (!m)
688                 return Enoarg;
689         dp = m->ref.p;
690         if (!dp)
691                 return Efail;
692         mod = pane_attr_get(dp, "doc-modified");
693         if (mod && strcmp(mod, "yes") == 0 &&
694             num == NO_NUMERIC) {
695                 call("Message", focus, 0, NULL,
696                      "File modified, cannot kill.");
697                 return 1;
698         }
699         call("doc:destroy", dp);
700         return 1;
701 }
702
703 DEF_CMD(docs_destroy)
704 {
705         /* Not allowed to destroy this document
706          * So handle command here, so we don't get
707          * to the default handler
708          */
709         return 1;
710 }
711
712 DEF_CMD(docs_child_closed)
713 {
714         struct pane *pd = ci->home->data;
715
716         if (ci->num < 0)
717             docs_demark(pd, ci->focus);
718         return 1;
719 }
720
721 DEF_CMD(docs_do_open)
722 {
723         return docs_open(ci->home, ci->focus, ci->mark, False);
724 }
725
726 DEF_CMD(docs_do_open_other)
727 {
728         return docs_open(ci->home, ci->focus, ci->mark, True);
729 }
730
731 DEF_CMD(docs_do_open_alt)
732 {
733         const char *c = ksuffix(ci, "doc:cmd-");
734
735         return docs_open_alt(ci->home,
736                              ci->focus, ci->mark, c[0]);
737 }
738
739 DEF_CMD(docs_do_quit)
740 {
741         return docs_bury(ci->focus);
742 }
743
744 DEF_CMD(docs_do_save)
745 {
746         return docs_save(ci->focus, ci->mark);
747 }
748
749 DEF_CMD(docs_do_kill)
750 {
751         return docs_kill(ci->focus, ci->mark, ci->num);
752 }
753
754 DEF_CMD(docs_shares_ref)
755 {
756         return 1;
757 }
758
759 DEF_CMD(docs_val_marks)
760 {
761         struct docs *d = ci->home->doc_data;
762         struct pane *p;
763         int found;
764
765         if (!ci->mark || !ci->mark2)
766                 return Enoarg;
767
768         if (ci->mark->ref.p == ci->mark2->ref.p) {
769                 if (ci->mark->ref.ignore < ci->mark2->ref.ignore)
770                         return 1;
771                 LOG("docs_val_marks: same buf, bad offset: %u, %u",
772                     ci->mark->ref.ignore, ci->mark2->ref.ignore);
773                 return Efalse;
774         }
775         if (ci->mark->ref.p == NULL) {
776                 LOG("docs_val_marks: mark.p is NULL");
777                 return Efalse;
778         }
779         found = 0;
780         list_for_each_entry(p, &d->collection->children, siblings) {
781                 if (ci->mark->ref.p == p)
782                         found = 1;
783                 if (ci->mark2->ref.p == p) {
784                         if (found == 1)
785                                 return 1;
786                         LOG("docs_val_marks: mark2.p found before mark1");
787                         return Efalse;
788                 }
789         }
790         if (ci->mark2->ref.p == NULL) {
791                 if (found == 1)
792                         return 1;
793                 LOG("docs_val_marks: mark2.p (NULL) found before mark1");
794                 return Efalse;
795         }
796         if (found == 0)
797                 LOG("docs_val_marks: Neither mark found in pane list");
798         if (found == 1)
799                 LOG("docs_val_marks: mark2 not found in pane list");
800         return Efalse;
801 }
802
803 DEF_CMD_CLOSED(docs_close)
804 {
805         struct docs *docs = ci->home->doc_data;
806
807         call_comm("global-set-command-prefix", ci->home, &edlib_noop,
808                   0, NULL, "docs:");
809         call_comm("global-set-command", ci->home, &edlib_noop,
810                   0, NULL, "doc:appeared-docs-register");
811         pane_close(docs->collection);
812         return Efallthrough;
813 }
814
815 static void docs_init_map(void)
816 {
817         if (docs_map)
818                 return;
819         docs_map = key_alloc();
820         docs_aux_map = key_alloc();
821         docs_modified_map = key_alloc();
822         docs_callback_map = key_alloc();
823         /* A "docs" document provides services to children and also behaves as
824          * a document which lists those children
825          */
826         key_add_chain(docs_map, doc_default_cmd);
827         key_add(docs_map, "doc:set-ref", &docs_set_ref);
828         key_add(docs_map, "doc:get-attr", &docs_doc_get_attr);
829         key_add(docs_map, "doc:char", &docs_char);
830         key_add(docs_map, "doc:destroy", &docs_destroy);
831         key_add(docs_map, "doc:cmd-f", &docs_do_open);
832         key_add(docs_map, "doc:cmd-\n", &docs_do_open);
833         key_add(docs_map, "doc:cmd:Enter", &docs_do_open);
834         key_add(docs_map, "doc:cmd-o", &docs_do_open_other);
835         key_add(docs_map, "doc:cmd-q", &docs_do_quit);
836         key_add(docs_map, "doc:cmd-s", &docs_do_save);
837         key_add(docs_map, "doc:cmd-k", &docs_do_kill);
838         key_add_range(docs_map, "doc:cmd-A", "doc:cmd-Z", &docs_do_open_alt);
839         key_add(docs_map, "doc:shares-ref", &docs_shares_ref);
840         if(0)key_add(docs_map, "debug:validate-marks", &docs_val_marks);
841
842         key_add(docs_map, "get-attr", &docs_get_attr);
843         key_add(docs_map, "Close", &docs_close);
844
845         key_add(docs_aux_map, "doc:revisit", &doc_revisit);
846         key_add(docs_aux_map, "doc:status-changed", &doc_damage);
847         key_add(docs_aux_map, "Child-Notify", &docs_child_closed);
848
849         key_add_prefix(docs_modified_map, "doc:cmd-", &docs_mod_noop);
850         key_add_prefix(docs_modified_map, "doc:cmd:", &docs_mod_noop);
851         key_add(docs_modified_map, "doc:cmd-s", &docs_do_save);
852         key_add(docs_modified_map, "doc:cmd-y", &docs_do_save);
853         key_add(docs_modified_map, "doc:cmd-n", &docs_mod_next);
854         key_add(docs_modified_map, "doc:cmd-q", &docs_mod_quit);
855         key_add(docs_modified_map, "doc:cmd-o", &docs_mod_other);
856
857         key_add(docs_modified_map, "Notify:filter:empty", &docs_mod_empty);
858
859         key_add(docs_callback_map, "docs:complete", &docs_callback_complete);
860         key_add(docs_callback_map, "docs:byname", &docs_callback_byname);
861         key_add(docs_callback_map, "docs:byfd", &docs_callback_byfd);
862         key_add(docs_callback_map, "docs:byeach", &docs_callback_byeach);
863         key_add(docs_callback_map, "docs:choose", &docs_callback_choose);
864         key_add(docs_callback_map, "docs:save-all", &docs_callback_saveall);
865         key_add(docs_callback_map, "docs:show-modified",
866                 &docs_callback_modified);
867         key_add(docs_callback_map, "doc:appeared-docs-register",
868                 &docs_callback_appeared);
869 }
870
871 DEF_CB(docs_callback_lookup)
872 {
873         struct docs *docs = container_of(ci->comm, struct docs, callback);
874         struct pane *home = docs->collection->data;
875
876         return do_call_val(TYPE_comm, home, &docs_callback_handle.c,
877                            ci->key, ci->focus,
878                            ci->num, ci->mark, ci->str,
879                            ci->num2, ci->mark2, ci->str2,
880                            ci->x, ci->y, ci->comm2);
881 }
882
883 DEF_CMD(attach_docs)
884 {
885         /* Attach a docs handler.  We register some commands with the editor
886          * so we can be found
887          */
888         struct docs *doc;
889         struct pane *pd, *paux;
890
891         docs_init_map();
892
893         pd = doc_register(ci->home, &docs_handle.c);
894         if (!pd)
895                 return Efail;
896         doc = pd->doc_data;
897         doc->doc.name = strdup("*Documents*");
898         paux = pane_register(ci->home, 0, &docs_aux.c, pd);
899         if (!paux) {
900                 pane_close(pd);
901                 return Efail;
902         }
903         doc->collection = paux;
904
905         doc->callback = docs_callback_lookup;
906         call_comm("global-set-command-prefix", ci->home, &doc->callback,
907                   0, NULL, "docs:");
908         call_comm("global-set-command", ci->home, &doc->callback,
909                   0, NULL, "doc:appeared-docs-register");
910
911         pane_reparent(pd, doc->collection);
912
913         return comm_call(ci->comm2, "callback:doc", pd);
914 }
915
916 void edlib_init(struct pane *ed safe)
917 {
918         call_comm("global-set-command", ed, &attach_docs, 0, NULL,
919                   "attach-doc-docs");
920 }