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;
307 list_for_each_entry(p, &doc->collection->children, siblings) {
309 r = comm_call(ci->comm2, "callback:doc", p);
320 DEF_CMD(docs_callback_choose)
322 struct docs *doc = ci->home->doc_data;
323 struct pane *choice = NULL, *last = NULL;
326 /* Choose a document with no notifiees or no pointer,
327 * but ignore 'CLOSED'
330 list_for_each_entry(p, &doc->collection->children, siblings) {
331 struct doc *d = &p->doc;
333 if (p->damaged & DAMAGED_CLOSED)
336 if (list_empty(&p->notifiees)) {
340 if (tlist_empty(&d->points)) {
349 return comm_call(ci->comm2, "callback:doc", choice);
352 DEF_CMD(docs_callback_saveall)
354 struct docs *doc = ci->home->doc_data;
356 int dirlen = ci->str ? (int)strlen(ci->str) : -1;
358 list_for_each_entry(p, &doc->collection->children, siblings) {
360 char *fn = pane_attr_get(p, "dirname");
361 if (!fn || strncmp(ci->str, fn, dirlen) != 0)
364 if (doc_save(p, p, ci->num2))
365 /* Something needs to be saved, we were only asked
373 DEF_CMD(docs_callback_modified)
377 p = home_call_ret(pane, ci->home, "doc:attach-view", ci->focus,
381 p = call_ret(pane, "attach-linefilter", p);
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);
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.
397 call("doc:file", p, -1);
401 DEF_CMD(docs_callback_appeared)
403 struct docs *doc = ci->home->doc_data;
406 /* Always return Efallthrough so other handlers get a chance */
410 if (p->parent != p->parent->parent)
411 /* This has a parent which is not the root,
412 * so we shouldn't interfere.
416 /* The docs doc is attached separately */
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",
422 doc_checkname(p, ci->home, ci->num ?: -1);
429 struct pane *dp = ci->home->data;
430 struct mark *m = mark_new(dp);
431 struct pane *child = ci->focus;
436 if (m->ref.p == child) {
437 pane_notify("doc:replaced", dp, 1, m);
440 } while (doc_next(dp, m) != WEOF);
447 struct pane *p = ci->focus;
448 struct pane *dp = ci->home->data;
449 struct docs *docs = dp->doc_data;
453 if (p->parent != docs->collection)
457 doc_checkname(p, dp, ci->num);
461 static inline wint_t docs_next(struct pane *home safe, struct doc_ref *r safe, bool bytes)
463 struct docs *d = home->doc_data;
464 struct pane *p = r->p;
469 if (p == list_last_entry(&d->collection->children,
470 struct pane, siblings))
473 r->p = list_next_entry(p, siblings);
476 static inline wint_t docs_prev(struct pane *home safe, struct doc_ref *r safe, bool bytes)
478 struct docs *d = home->doc_data;
479 struct pane *p = r->p;
481 if (list_empty(&d->collection->children))
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);
497 return do_char_byte(ci);
500 DEF_CMD(docs_set_ref)
502 struct docs *d = ci->home->doc_data;
503 struct mark *m = ci->mark;
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);
519 DEF_CMD(docs_doc_get_attr)
521 struct mark *m = ci->mark;
522 const char *attr = ci->str;
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;
536 mod = pane_attr_get(m->ref.p, "doc-modified");
537 if (!mod || strcmp(mod, "yes") != 0)
539 fl = pane_attr_get(m->ref.p, "filename");
542 dir = pane_attr_get(ci->focus, "only-here");
543 if (!dir || strncmp(dir, fl, strlen(dir)) == 0)
549 comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, m, val,
554 DEF_CMD(docs_get_attr)
556 const char *attr = ci->str;
562 if ((val = attr_find(ci->home->attrs, attr)) != NULL)
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)
570 else if (strcmp(attr, "render-simple") == 0)
572 else if (strcmp(attr, "view-default") == 0)
574 else if (strcmp(attr, "doc-type") == 0)
579 comm_call(ci->comm2, "callback:get_attr", ci->focus,
584 static int docs_open(struct pane *home safe, struct pane *focus safe,
585 struct mark *m, bool other)
587 struct pane *p = NULL;
594 /* close this pane, open the given document. */
599 par = home_call_ret(pane, focus, "DocPane", dp);
601 pane_take_focus(par);
604 par = call_ret(pane, "OtherPane", focus);
606 par = call_ret(pane, "ThisPane", focus);
608 p = home_call_ret(pane, dp, "doc:attach-view", par, 1);
617 static int docs_open_alt(struct pane *home safe, struct pane *focus safe,
618 struct mark *m, char cmd)
622 char *renderer = NULL;
630 /* close this pane, open the given document. */
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)
641 par = call_ret(pane, "ThisPane", focus);
644 p = home_call_ret(pane, dp, "doc:attach-view", par, 1, NULL, buf+5);
653 static int docs_bury(struct pane *focus safe)
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);
661 /* Discourage this doc from being chosen again */
662 call("doc:notify:doc:revisit", focus, -1);
663 doc = call_ret(pane, "docs:choose", focus);
665 home_call(doc, "doc:attach-view", tile);
669 static int docs_save(struct pane *focus safe, struct mark *m)
678 doc_save(dp, focus, 0);
682 static int docs_kill(struct pane *focus safe, struct mark *m, int num)
692 mod = pane_attr_get(dp, "doc-modified");
693 if (mod && strcmp(mod, "yes") == 0 &&
695 call("Message", focus, 0, NULL,
696 "File modified, cannot kill.");
699 call("doc:destroy", dp);
703 DEF_CMD(docs_destroy)
705 /* Not allowed to destroy this document
706 * So handle command here, so we don't get
707 * to the default handler
712 DEF_CMD(docs_child_closed)
714 struct pane *pd = ci->home->data;
717 docs_demark(pd, ci->focus);
721 DEF_CMD(docs_do_open)
723 return docs_open(ci->home, ci->focus, ci->mark, False);
726 DEF_CMD(docs_do_open_other)
728 return docs_open(ci->home, ci->focus, ci->mark, True);
731 DEF_CMD(docs_do_open_alt)
733 const char *c = ksuffix(ci, "doc:cmd-");
735 return docs_open_alt(ci->home,
736 ci->focus, ci->mark, c[0]);
739 DEF_CMD(docs_do_quit)
741 return docs_bury(ci->focus);
744 DEF_CMD(docs_do_save)
746 return docs_save(ci->focus, ci->mark);
749 DEF_CMD(docs_do_kill)
751 return docs_kill(ci->focus, ci->mark, ci->num);
754 DEF_CMD(docs_shares_ref)
759 DEF_CMD(docs_val_marks)
761 struct docs *d = ci->home->doc_data;
765 if (!ci->mark || !ci->mark2)
768 if (ci->mark->ref.p == ci->mark2->ref.p) {
769 if (ci->mark->ref.ignore < ci->mark2->ref.ignore)
771 LOG("docs_val_marks: same buf, bad offset: %u, %u",
772 ci->mark->ref.ignore, ci->mark2->ref.ignore);
775 if (ci->mark->ref.p == NULL) {
776 LOG("docs_val_marks: mark.p is NULL");
780 list_for_each_entry(p, &d->collection->children, siblings) {
781 if (ci->mark->ref.p == p)
783 if (ci->mark2->ref.p == p) {
786 LOG("docs_val_marks: mark2.p found before mark1");
790 if (ci->mark2->ref.p == NULL) {
793 LOG("docs_val_marks: mark2.p (NULL) found before mark1");
797 LOG("docs_val_marks: Neither mark found in pane list");
799 LOG("docs_val_marks: mark2 not found in pane list");
803 DEF_CMD_CLOSED(docs_close)
805 struct docs *docs = ci->home->doc_data;
807 call_comm("global-set-command-prefix", ci->home, &edlib_noop,
809 call_comm("global-set-command", ci->home, &edlib_noop,
810 0, NULL, "doc:appeared-docs-register");
811 pane_close(docs->collection);
815 static void docs_init_map(void)
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
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);
842 key_add(docs_map, "get-attr", &docs_get_attr);
843 key_add(docs_map, "Close", &docs_close);
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);
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);
857 key_add(docs_modified_map, "Notify:filter:empty", &docs_mod_empty);
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);
871 DEF_CB(docs_callback_lookup)
873 struct docs *docs = container_of(ci->comm, struct docs, callback);
874 struct pane *home = docs->collection->data;
876 return do_call_val(TYPE_comm, home, &docs_callback_handle.c,
878 ci->num, ci->mark, ci->str,
879 ci->num2, ci->mark2, ci->str2,
880 ci->x, ci->y, ci->comm2);
885 /* Attach a docs handler. We register some commands with the editor
889 struct pane *pd, *paux;
893 pd = doc_register(ci->home, &docs_handle.c);
897 doc->doc.name = strdup("*Documents*");
898 paux = pane_register(ci->home, 0, &docs_aux.c, pd);
903 doc->collection = paux;
905 doc->callback = docs_callback_lookup;
906 call_comm("global-set-command-prefix", ci->home, &doc->callback,
908 call_comm("global-set-command", ci->home, &doc->callback,
909 0, NULL, "doc:appeared-docs-register");
911 pane_reparent(pd, doc->collection);
913 return comm_call(ci->comm2, "callback:doc", pd);
916 void edlib_init(struct pane *ed safe)
918 call_comm("global-set-command", ed, &attach_docs, 0, NULL,