2 * Copyright Neil Brown ©2016-2017-2019 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Document management is eased by having a well defined collection
6 * of documents. This module provides a pane and a document to manage
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
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
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.
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
41 #define PRIVATE_DOC_REF
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)
51 #define PANE_DATA_PTR_TYPE struct pane *
52 #define PANE_DATA_VOID_2
55 static struct map *docs_map, *docs_aux_map, *docs_modified_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);
64 struct command callback;
65 struct pane *collection safe;
67 #include "core-pane.h"
69 static void docs_demark(struct pane *d safe, struct pane *p safe)
71 /* This document (p) is about to be moved in the list (d->collection).
72 * Any mark pointing at it is moved forward
74 struct docs *doc = d->doc_data;
75 struct mark *m, *first = NULL;
77 struct pane *col = doc->collection;
79 if (list_empty(&p->siblings) ||
80 p == list_last_entry(&col->children, struct pane, siblings))
83 next = list_next_entry(p, siblings);
85 for (m = mark_first(&doc->doc);
98 pane_notify("doc:replaced", d, 1, first);
101 static void docs_enmark(struct pane *d safe, struct pane *p safe)
103 /* This document has just been added to the list.
104 * any mark pointing just past it is moved back.
106 struct docs *doc = d->doc_data;
107 struct mark *m, *first = NULL;
109 struct pane *col = doc->collection;
111 if (p == list_last_entry(&col->children, struct pane, siblings))
114 next = list_next_entry(p, siblings);
116 for (m = mark_first(&doc->doc);
119 if (m->ref.p == next) {
126 pane_notify("doc:replaced", d, 1, first);
129 static bool doc_save(struct pane *p safe, struct pane *focus safe, int test)
131 char *fn = pane_attr_get(p, "filename");
132 char *mod = pane_attr_get(p, "doc-modified");
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.");
142 home_call(p, "doc:save-file", focus);
146 static void check_name(struct docs *docs safe, struct pane *pane safe)
148 struct doc *d = &pane->doc;
154 d->name = strdup("*unknown*");
156 nname = malloc(strlen(d->name) + sizeof("<xxx>"));
157 while (conflict && unique < 1000) {
161 sprintf(nname, "%s<%d>", d->name, unique);
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) {
181 static void doc_checkname(struct pane *p safe, struct pane *d safe, int n)
183 struct docs *ds = d->doc_data;
184 ASSERT(p->parent->handle == &docs_aux.c);
189 list_move(&p->siblings, &ds->collection->children);
191 list_move_tail(&p->siblings, &ds->collection->children);
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.
204 static int docs_open(struct pane *home safe, struct pane *focus safe,
205 struct mark *m, bool other);
207 DEF_CMD(docs_mod_next)
211 /* If this is the last, then quit */
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.
219 if (call("doc:render-line", ci->focus, 0, m) < 0 ||
222 return call("popup:close", ci->focus);
225 /* Ask viewer to move forward */
229 DEF_CMD(docs_mod_quit)
231 return call("popup:close", ci->home);
234 DEF_CMD(docs_mod_other)
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);
242 DEF_CMD(docs_mod_empty)
244 call("popup:close", ci->focus);
248 DEF_CMD(docs_mod_noop)
250 /* Don't want anything else to fall through to default */
254 DEF_CMD(docs_callback_complete)
258 p = home_call_ret(pane, ci->home, "doc:attach-view", ci->focus,
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);
267 return comm_call(ci->comm2, "callback:doc", p);
271 DEF_CMD(docs_callback_byname)
273 struct docs *doc = ci->home->doc_data;
276 if (ci->str == NULL || strcmp(ci->str, "*Documents*") == 0)
277 return comm_call(ci->comm2, "callback:doc",
279 list_for_each_entry(p, &doc->collection->children, siblings) {
280 struct doc *dc = &p->doc;
282 if (n && strcmp(ci->str, n) == 0)
283 return comm_call(ci->comm2, "callback:doc", p);
288 DEF_CMD(docs_callback_byfd)
290 struct docs *doc = ci->home->doc_data;
293 list_for_each_entry(p, &doc->collection->children, siblings) {
294 if (call("doc:same-file", p, 0, NULL, ci->str,
296 return comm_call(ci->comm2, "callback:doc", p);
301 DEF_CMD(docs_callback_byeach)
303 struct docs *doc = ci->home->doc_data;
306 list_for_each_entry(p, &doc->collection->children, siblings) {
308 r = comm_call(ci->comm2, "callback:doc", p);
315 DEF_CMD(docs_callback_choose)
317 struct docs *doc = ci->home->doc_data;
318 struct pane *choice = NULL, *last = NULL;
321 /* Choose a document with no notifiees or no pointer,
322 * but ignore 'CLOSED'
325 list_for_each_entry(p, &doc->collection->children, siblings) {
326 struct doc *d = &p->doc;
328 if (p->damaged & DAMAGED_CLOSED)
331 if (list_empty(&p->notifiees)) {
335 if (tlist_empty(&d->points)) {
344 return comm_call(ci->comm2, "callback:doc", choice);
347 DEF_CMD(docs_callback_saveall)
349 struct docs *doc = ci->home->doc_data;
351 int dirlen = ci->str ? (int)strlen(ci->str) : -1;
353 list_for_each_entry(p, &doc->collection->children, siblings) {
355 char *fn = pane_attr_get(p, "dirname");
356 if (!fn || strncmp(ci->str, fn, dirlen) != 0)
359 if (doc_save(p, p, ci->num2))
360 /* Something needs to be saved, we were only asked
368 DEF_CMD(docs_callback_modified)
372 p = home_call_ret(pane, ci->home, "doc:attach-view", ci->focus,
376 p = call_ret(pane, "attach-linefilter", p);
379 attr_set_str(&p->attrs, "filter:attr", "doc-can-save");
380 attr_set_str(&p->attrs, "filter:match", "yes");
381 p = pane_register_2(p, 0, &docs_modified_handle.c);
384 attr_set_str(&p->attrs, "doc-name", "*Modified Documents*");
385 attr_set_str(&p->attrs, "line-format", "%doc-name:20 %filename");
386 attr_set_str(&p->attrs, "heading",
387 "<bold>Document File</>\n"
388 "<bold,underline>[s]ave [y]es [n]o [q]uit</>");
389 /* Don't want to inherit position from some earlier instance,
390 * always move to the start.
392 call("doc:file", p, -1);
396 DEF_CMD(docs_callback_appeared)
398 struct docs *doc = ci->home->doc_data;
401 /* Always return Efallthrough so other handlers get a chance */
405 if (p->parent != p->parent->parent)
406 /* This has a parent which is not the root,
407 * so we shouldn't interfere.
411 /* The docs doc is attached separately */
413 pane_reparent(p, doc->collection);
414 home_call(p, "doc:request:doc:revisit", doc->collection);
415 home_call(p, "doc:request:doc:status-changed",
417 doc_checkname(p, ci->home, ci->num ?: -1);
424 struct pane *dp = ci->home->data;
425 struct mark *m = mark_new(dp);
426 struct pane *child = ci->focus;
431 if (m->ref.p == child) {
432 pane_notify("doc:replaced", dp, 1, m);
435 } while (doc_next(dp, m) != WEOF);
442 struct pane *p = ci->focus;
443 struct pane *dp = ci->home->data;
444 struct docs *docs = dp->doc_data;
448 if (p->parent != docs->collection)
452 doc_checkname(p, dp, ci->num);
456 static inline wint_t docs_next(struct pane *home safe, struct doc_ref *r safe, bool bytes)
458 struct docs *d = home->doc_data;
459 struct pane *p = r->p;
464 if (p == list_last_entry(&d->collection->children,
465 struct pane, siblings))
468 r->p = list_next_entry(p, siblings);
471 static inline wint_t docs_prev(struct pane *home safe, struct doc_ref *r safe, bool bytes)
473 struct docs *d = home->doc_data;
474 struct pane *p = r->p;
476 if (list_empty(&d->collection->children))
479 p = list_last_entry(&d->collection->children,
480 struct pane, siblings);
481 else if (p != list_first_entry(&d->collection->children,
482 struct pane, siblings))
483 p = list_prev_entry(p, siblings);
492 return do_char_byte(ci);
495 DEF_CMD(docs_set_ref)
497 struct docs *d = ci->home->doc_data;
498 struct mark *m = ci->mark;
503 mark_to_end(ci->home, m, ci->num != 1);
504 if (ci->num == 1 && !list_empty(&d->collection->children))
505 m->ref.p = list_first_entry(&d->collection->children,
506 struct pane, siblings);
514 DEF_CMD(docs_doc_get_attr)
516 struct mark *m = ci->mark;
517 const char *attr = ci->str;
526 val = pane_attr_get(m->ref.p, attr);
527 /* use 'while' instead of 'if' to allow 'break' */
528 while (!val && strcmp(attr, "doc-can-save") == 0) {
529 char *mod, *fl, *dir;
531 mod = pane_attr_get(m->ref.p, "doc-modified");
532 if (!mod || strcmp(mod, "yes") != 0)
534 fl = pane_attr_get(m->ref.p, "filename");
537 dir = pane_attr_get(ci->focus, "only-here");
538 if (!dir || strncmp(dir, fl, strlen(dir)) == 0)
544 comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, m, val,
549 DEF_CMD(docs_get_attr)
551 const char *attr = ci->str;
557 if ((val = attr_find(ci->home->attrs, attr)) != NULL)
559 else if (strcmp(attr, "heading") == 0)
560 val = "<bold,underline> Mod Document File</>";
561 else if (strcmp(attr, "line-format") == 0)
562 val = " %doc-modified:3 %doc-name:20 %filename";
563 else if (strcmp(attr, "render-default") == 0)
565 else if (strcmp(attr, "render-simple") == 0)
567 else if (strcmp(attr, "view-default") == 0)
569 else if (strcmp(attr, "doc-type") == 0)
574 comm_call(ci->comm2, "callback:get_attr", ci->focus,
579 static int docs_open(struct pane *home safe, struct pane *focus safe,
580 struct mark *m, bool other)
582 struct pane *p = NULL;
589 /* close this pane, open the given document. */
594 par = home_call_ret(pane, focus, "DocPane", dp);
596 pane_take_focus(par);
599 par = call_ret(pane, "OtherPane", focus);
601 par = call_ret(pane, "ThisPane", focus);
603 p = home_call_ret(pane, dp, "doc:attach-view", par, 1);
612 static int docs_open_alt(struct pane *home safe, struct pane *focus safe,
613 struct mark *m, char cmd)
617 char *renderer = NULL;
625 /* close this pane, open the given document. */
629 snprintf(buf, sizeof(buf), "render-cmd-%c", cmd);
630 renderer = pane_attr_get(dp, buf);
631 snprintf(buf, sizeof(buf), "view-cmd-%c", cmd);
632 viewer = pane_attr_get(dp, buf);
633 if (!renderer && !viewer)
636 par = call_ret(pane, "ThisPane", focus);
639 p = home_call_ret(pane, dp, "doc:attach-view", par, 1, NULL, buf+5);
648 static int docs_bury(struct pane *focus safe)
650 /* If the docs list is in a tile, put something else there. */
651 /* FIXME should this be a function of the pane manager? */
652 struct pane *tile, *doc;
653 tile = call_ret(pane, "ThisPane", focus);
656 /* Discourage this doc from being chosen again */
657 call("doc:notify:doc:revisit", focus, -1);
658 doc = call_ret(pane, "docs:choose", focus);
660 home_call(doc, "doc:attach-view", tile);
664 static int docs_save(struct pane *focus safe, struct mark *m)
673 doc_save(dp, focus, 0);
677 static int docs_kill(struct pane *focus safe, struct mark *m, int num)
687 mod = pane_attr_get(dp, "doc-modified");
688 if (mod && strcmp(mod, "yes") == 0 &&
690 call("Message", focus, 0, NULL,
691 "File modified, cannot kill.");
694 call("doc:destroy", dp);
698 DEF_CMD(docs_destroy)
700 /* Not allowed to destroy this document
701 * So handle command here, so we don't get
702 * to the default handler
707 DEF_CMD(docs_child_closed)
709 struct pane *pd = ci->home->data;
712 docs_demark(pd, ci->focus);
716 DEF_CMD(docs_do_open)
718 return docs_open(ci->home, ci->focus, ci->mark, False);
721 DEF_CMD(docs_do_open_other)
723 return docs_open(ci->home, ci->focus, ci->mark, True);
726 DEF_CMD(docs_do_open_alt)
728 const char *c = ksuffix(ci, "doc:cmd-");
730 return docs_open_alt(ci->home,
731 ci->focus, ci->mark, c[0]);
734 DEF_CMD(docs_do_quit)
736 return docs_bury(ci->focus);
739 DEF_CMD(docs_do_save)
741 return docs_save(ci->focus, ci->mark);
744 DEF_CMD(docs_do_kill)
746 return docs_kill(ci->focus, ci->mark, ci->num);
749 DEF_CMD(docs_shares_ref)
754 DEF_CMD(docs_val_marks)
756 struct docs *d = ci->home->doc_data;
760 if (!ci->mark || !ci->mark2)
763 if (ci->mark->ref.p == ci->mark2->ref.p) {
764 if (ci->mark->ref.ignore < ci->mark2->ref.ignore)
766 LOG("docs_val_marks: same buf, bad offset: %u, %u",
767 ci->mark->ref.ignore, ci->mark2->ref.ignore);
770 if (ci->mark->ref.p == NULL) {
771 LOG("docs_val_marks: mark.p is NULL");
775 list_for_each_entry(p, &d->collection->children, siblings) {
776 if (ci->mark->ref.p == p)
778 if (ci->mark2->ref.p == p) {
781 LOG("docs_val_marks: mark2.p found before mark1");
785 if (ci->mark2->ref.p == NULL) {
788 LOG("docs_val_marks: mark2.p (NULL) found before mark1");
792 LOG("docs_val_marks: Neither mark found in pane list");
794 LOG("docs_val_marks: mark2 not found in pane list");
798 DEF_CMD_CLOSED(docs_close)
800 struct docs *docs = ci->home->doc_data;
802 call_comm("global-set-command-prefix", ci->home, &edlib_noop,
804 call_comm("global-set-command", ci->home, &edlib_noop,
805 0, NULL, "doc:appeared-docs-register");
806 pane_close(docs->collection);
810 static void docs_init_map(void)
814 docs_map = key_alloc();
815 docs_aux_map = key_alloc();
816 docs_modified_map = key_alloc();
817 docs_callback_map = key_alloc();
818 /* A "docs" document provides services to children and also behaves as
819 * a document which lists those children
821 key_add_chain(docs_map, doc_default_cmd);
822 key_add(docs_map, "doc:set-ref", &docs_set_ref);
823 key_add(docs_map, "doc:get-attr", &docs_doc_get_attr);
824 key_add(docs_map, "doc:char", &docs_char);
825 key_add(docs_map, "doc:destroy", &docs_destroy);
826 key_add(docs_map, "doc:cmd-f", &docs_do_open);
827 key_add(docs_map, "doc:cmd-\n", &docs_do_open);
828 key_add(docs_map, "doc:cmd:Enter", &docs_do_open);
829 key_add(docs_map, "doc:cmd-o", &docs_do_open_other);
830 key_add(docs_map, "doc:cmd-q", &docs_do_quit);
831 key_add(docs_map, "doc:cmd-s", &docs_do_save);
832 key_add(docs_map, "doc:cmd-k", &docs_do_kill);
833 key_add_range(docs_map, "doc:cmd-A", "doc:cmd-Z", &docs_do_open_alt);
834 key_add(docs_map, "doc:shares-ref", &docs_shares_ref);
835 if(0)key_add(docs_map, "debug:validate-marks", &docs_val_marks);
837 key_add(docs_map, "get-attr", &docs_get_attr);
838 key_add(docs_map, "Close", &docs_close);
840 key_add(docs_aux_map, "doc:revisit", &doc_revisit);
841 key_add(docs_aux_map, "doc:status-changed", &doc_damage);
842 key_add(docs_aux_map, "Child-Notify", &docs_child_closed);
844 key_add_prefix(docs_modified_map, "doc:cmd-", &docs_mod_noop);
845 key_add_prefix(docs_modified_map, "doc:cmd:", &docs_mod_noop);
846 key_add(docs_modified_map, "doc:cmd-s", &docs_do_save);
847 key_add(docs_modified_map, "doc:cmd-y", &docs_do_save);
848 key_add(docs_modified_map, "doc:cmd-n", &docs_mod_next);
849 key_add(docs_modified_map, "doc:cmd-q", &docs_mod_quit);
850 key_add(docs_modified_map, "doc:cmd-o", &docs_mod_other);
852 key_add(docs_modified_map, "Notify:filter:empty", &docs_mod_empty);
854 key_add(docs_callback_map, "docs:complete", &docs_callback_complete);
855 key_add(docs_callback_map, "docs:byname", &docs_callback_byname);
856 key_add(docs_callback_map, "docs:byfd", &docs_callback_byfd);
857 key_add(docs_callback_map, "docs:byeach", &docs_callback_byeach);
858 key_add(docs_callback_map, "docs:choose", &docs_callback_choose);
859 key_add(docs_callback_map, "docs:save-all", &docs_callback_saveall);
860 key_add(docs_callback_map, "docs:show-modified",
861 &docs_callback_modified);
862 key_add(docs_callback_map, "doc:appeared-docs-register",
863 &docs_callback_appeared);
866 DEF_CB(docs_callback_lookup)
868 struct docs *docs = container_of(ci->comm, struct docs, callback);
869 struct pane *home = docs->collection->data;
871 return do_call_val(TYPE_comm, home, &docs_callback_handle.c,
873 ci->num, ci->mark, ci->str,
874 ci->num2, ci->mark2, ci->str2,
875 ci->x, ci->y, ci->comm2);
880 /* Attach a docs handler. We register some commands with the editor
884 struct pane *pd, *paux;
888 pd = doc_register(ci->home, &docs_handle.c);
892 doc->doc.name = strdup("*Documents*");
893 paux = pane_register(ci->home, 0, &docs_aux.c, pd);
898 doc->collection = paux;
900 doc->callback = docs_callback_lookup;
901 call_comm("global-set-command-prefix", ci->home, &doc->callback,
903 call_comm("global-set-command", ci->home, &doc->callback,
904 0, NULL, "doc:appeared-docs-register");
906 pane_reparent(pd, doc->collection);
908 return comm_call(ci->comm2, "callback:doc", pd);
911 void edlib_init(struct pane *ed safe)
913 call_comm("global-set-command", ed, &attach_docs, 0, NULL,