2 * Copyright Neil Brown ©2020-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Individual document handlers are responsible for creating
6 * autosave files. The task of this module is to provide access
9 * - When a file is visited we check if it has an autosaved version.
10 * If so, we display a diff and ask if it should be restored.
15 #define PANE_DATA_VOID
20 * When an old autosave file is detected, we pop up a window showing a diff
21 * from the saved file to the autosave file and as 'Should this be restored?'
22 * If 'y' or 's' is given, the autosave file is renamed over the saved file,
23 * which is then reloaded.
24 * 'n' or 'q' discard the diff.
27 static const char mesg[] =
28 "\nAutosave file has these differences, type:\n"
31 "'d' to delete autosaved file.\n\n";
33 DEF_CMD(autosave_keep)
35 char *orig = pane_attr_get(ci->focus, "orig_name");
36 char *as = pane_attr_get(ci->focus, "autosave_name");
42 d = call_ret(pane, "doc:open", ci->focus, -1, NULL, orig);
44 call("doc:load-file", d, 4, NULL, NULL, -1);
45 call("popup:close", ci->focus);
49 DEF_CMD(autosave_ignore)
51 call("popup:close", ci->focus);
57 char *fn = pane_attr_get(ci->focus, "orig_name");
58 char *as = pane_attr_get(ci->focus, "autosave_name");
59 char *ast = pane_attr_get(ci->focus, "autosave_type");
62 if (!fn || !as || !ast)
66 d = call_ret(pane, "doc:open", ci->focus, -1, NULL, fn, 4);
68 call("Message", ci->focus, 0, NULL,
69 strconcat(ci->focus, "Cannot open ", fn));
72 if (strcmp(ast, "autosave") == 0) {
73 if (call("doc:autosave-delete", d, 0, NULL, as) == 1)
74 call("Message", ci->focus, 0, NULL,
75 strconcat(ci->focus, as, " deleted."));
78 call("Message", ci->focus, 0, NULL,
79 strconcat(ci->focus, as, " deleted."));
81 call("popup:close", ci->focus);
85 DEF_CMD(autosave_dir_view)
87 /* Open in other pane, and follow symlink */
88 home_call(ci->home->parent, "doc:cmd-o", ci->focus, 1);
92 DEF_CMD(autosave_dir_ignore)
96 /* If this is the last, then bury the doc */
99 m = mark_dup(ci->mark);
100 doc_next(ci->home->parent, m);
101 if (call("doc:render-line", ci->focus, 0, m) < 0 ||
103 call("Tile:bury", ci->focus);
105 /* Ask viewer to move forward */
109 DEF_CMD(autosave_dir_delete)
111 struct mark *m = ci->mark;
118 fn = pane_mark_attr(ci->focus, m, "target");
119 if (!fn || *fn != '/')
122 dir = pane_attr_get(ci->focus, "dirname");
123 base = pane_mark_attr(ci->focus, m, "name");
125 fn = strconcat(ci->focus, dir, base);
127 /* trigger a directory reread */
128 call("doc:notify:doc:revisit", ci->focus, 1);
135 DEF_CMD(autosave_dir_empty)
137 call("Tile:bury", ci->focus);
141 static struct map *asd_map;
142 DEF_LOOKUP_CMD(autosavedir_handle, asd_map);
144 static struct map *as_map;
145 DEF_LOOKUP_CMD(autosave_handle, as_map);
146 static void autosave_init(void)
150 as_map = key_alloc();
151 key_add(as_map, "doc:cmd-s", &autosave_keep);
152 key_add(as_map, "doc:cmd-y", &autosave_keep);
153 key_add(as_map, "doc:cmd-d", &autosave_del);
154 key_add(as_map, "doc:cmd-n", &autosave_ignore);
155 key_add(as_map, "doc:cmd-q", &autosave_ignore);
156 key_add(as_map, "doc:replaced", &autosave_ignore);
158 asd_map = key_alloc();
159 key_add(asd_map, "doc:cmd-v", &autosave_dir_view);
160 key_add(asd_map, "doc:cmd-y", &autosave_dir_view);
161 key_add(asd_map, "doc:cmd-f", &autosave_dir_view);
162 key_add(asd_map, "doc:cmd-o", &autosave_dir_view);
163 key_add(asd_map, "doc:cmd-\n", &autosave_dir_view);
164 key_add(asd_map, "doc:cmd:Enter", &autosave_dir_view);
166 key_add(asd_map, "doc:cmd-d", &autosave_dir_delete);
167 key_add(asd_map, "doc:cmd-i", &autosave_dir_ignore);
168 key_add(asd_map, "doc:cmd-n", &autosave_dir_ignore);
169 key_add(asd_map, "Notify:filter:empty", &autosave_dir_empty);
175 struct call_return *cr = container_of(ci->comm, struct call_return, c);
177 if (cr->p == NULL || ci->num > cr->i) {
184 DEF_CMD(ask_autosave)
186 struct pane *p = ci->focus;
188 struct call_return cr;
189 char *f = NULL, *a = NULL, *diffcmd;
192 char *autosave_type = "";
194 /* Need to choose best display */
195 cr.i = 0; cr.p = NULL;
197 call_comm("editor:notify:all-displays", p, &cr.c);
202 p2 = call_ret(pane, "PopupTile", pane_focus(cr.p), 0, NULL, "DM3sta");
206 if ((s = pane_attr_get(p, "autosave-exists")) != NULL &&
207 strcmp(s, "yes") == 0) {
208 f = pane_attr_get(p, "filename");
209 a = pane_attr_get(p, "autosave-name");
210 autosave_type = "autosave";
211 } else if ((s = pane_attr_get(p, "is_backup")) != NULL &&
212 strcmp(s, "yes") == 0) {
213 f = pane_attr_get(p, "base-name");
214 a = pane_attr_get(p, "filename");
215 autosave_type = "backup";
219 call("popup:close", p2);
222 doc = call_ret(pane, "doc:from-text", p,
223 0, NULL, "*Autosave-Diff*",
226 call("doc:replace", doc, 0, NULL, "Original file: ");
227 call("doc:replace", doc, 0, NULL, f);
228 call("doc:replace", doc, 0, NULL, "\nAutosave file: ");
229 call("doc:replace", doc, 0, NULL, a);
230 call("doc:replace", doc, 0, NULL, "\n\n");
231 call("doc:set:autoclose", doc, 1);
232 diffcmd = strconcat(p, "diff -Nu ",f," ",a);
233 call("attach-shellcmd", doc, 2, NULL, diffcmd);
234 attr_set_str(&doc->attrs, "view-default", "diff");
235 p2 = home_call_ret(pane, doc, "doc:attach-view", p2, 1);
239 p2 = pane_register(p2, 0, &autosave_handle.c);
243 attr_set_str(&p2->attrs, "orig_name", f);
244 attr_set_str(&p2->attrs, "autosave_name", a);
245 attr_set_str(&p2->attrs, "autosave_type", autosave_type);
247 pane_add_notify(p2, doc, "doc:replaced");
252 DEF_CMD(check_autosave)
255 struct pane *p = ci->focus;
257 s = pane_attr_get(p, "filename");
258 if (s && strlen(s) > 17 &&
259 strcmp(s + strlen(s) - 17, "/.edlib_autosave/") == 0) {
260 attr_set_str(&p->attrs, "view-default", "autosave-dir-view");
263 s = pane_attr_get(p, "autosave-exists");
264 if (!s || strcmp(s, "yes") != 0)
265 s = pane_attr_get(p, "is_backup");
266 if (s && strcmp(s, "yes") == 0)
267 call_comm("event:on-idle", p, &ask_autosave);
272 DEF_CMD(attach_asview)
276 p = call_ret(pane, "attach-dirview", ci->focus);
279 p = pane_register(p, 0, &autosavedir_handle.c);
282 attr_set_str(&p->attrs, "line-format", " %target");
283 attr_set_str(&p->attrs, "heading",
284 "Autosave files: [v]iew, [d]elete, [i]gnore");
285 p = call_ret(pane, "attach-linefilter", p);
287 attr_set_str(&p->attrs, "filter:attr", "arrow");
288 attr_set_str(&p->attrs, "filter:match", " -> ");
289 comm_call(ci->comm2, "cb", p);
294 DEF_CMD(show_autosave)
297 char *home = getenv("HOME");
298 char *dirname = getenv("EDLIB_AUTOSAVE");
302 call("Message", ci->focus, 0, NULL,
303 "Cannot determine HOME directory");
306 dirname = strconcat(ci->focus, home, "/.edlib_autosave");
308 p = call_ret(pane, "ThisPane", ci->focus);
311 d = call_ret(pane, "doc:open", p, -1, NULL, dirname);
313 home_call_ret(pane, d, "doc:attach-view", p,
316 call("Message", ci->focus, 0, NULL,
317 "Cannot open $HOME/.edlib_autosave");
323 DEF_CMD(check_autosave_dir)
325 /* Should I open the direct doc and use filter to do the search?? */
328 char *home = getenv("HOME");
329 char *dirname = getenv("EDLIB_AUTOSAVE");
332 dirname = strconcat(ci->focus, home ?: "", "/.edlib_autosave");
333 dir = opendir(dirname);
336 while ((de = readdir(dir)) != NULL) {
337 if (de->d_name[0] == '.')
339 if (de->d_type == DT_LNK)
341 if (de->d_type != DT_UNKNOWN)
343 /* FIXME I should probably lstat the name */
348 call("editor:notify:Message:broadcast", ci->focus, 0, NULL,
349 "Autosave files exist - use \"recover\" command to view them.");
353 void edlib_init(struct pane *ed safe)
356 call_comm("global-set-command", ed, &check_autosave,
357 0, NULL, "doc:appeared-check-autosave");
358 call_comm("global-set-command", ed, &attach_asview,
359 0, NULL, "attach-autosave-dir-view");
360 call_comm("global-set-command", ed, &show_autosave,
361 0, NULL, "interactive-cmd-recover");
362 call_comm("global-set-command", ed, &check_autosave_dir,
363 0, NULL, "startup-autosave");