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.
19 * When an old autosave file is detected, we pop up a window showing a diff
20 * from the saved file to the autosave file and as 'Should this be restored?'
21 * If 'y' or 's' is given, the autosave file is renamed over the saved file,
22 * which is then reloaded.
23 * 'n' or 'q' discard the diff.
26 static const char mesg[] =
27 "\nAutosave file has these differences, type:\n"
30 "'d' to delete autosaved file.\n\n";
32 DEF_CMD(autosave_keep)
34 char *orig = pane_attr_get(ci->focus, "orig_name");
35 char *as = pane_attr_get(ci->focus, "autosave_name");
41 d = call_ret(pane, "doc:open", ci->focus, -1, NULL, orig);
43 call("doc:load-file", d, 4, NULL, NULL, -1);
44 call("popup:close", ci->focus);
48 DEF_CMD(autosave_ignore)
50 call("popup:close", ci->focus);
56 char *fn = pane_attr_get(ci->focus, "orig_name");
57 char *as = pane_attr_get(ci->focus, "autosave_name");
58 char *ast = pane_attr_get(ci->focus, "autosave_type");
61 if (!fn || !as || !ast)
65 d = call_ret(pane, "doc:open", ci->focus, -1, NULL, fn, 4);
67 call("Message", ci->focus, 0, NULL,
68 strconcat(ci->focus, "Cannot open ", fn));
71 if (strcmp(ast, "autosave") == 0) {
72 if (call("doc:autosave-delete", d, 0, NULL, as) == 1)
73 call("Message", ci->focus, 0, NULL,
74 strconcat(ci->focus, as, " deleted."));
77 call("Message", ci->focus, 0, NULL,
78 strconcat(ci->focus, as, " deleted."));
80 call("popup:close", ci->focus);
84 DEF_CMD(autosave_dir_view)
86 /* Open in other pane, and follow symlink */
87 home_call(ci->home->parent, "doc:cmd-o", ci->focus, 1);
91 DEF_CMD(autosave_dir_ignore)
95 /* If this is the last, then bury the doc */
98 m = mark_dup(ci->mark);
99 doc_next(ci->home->parent, m);
100 if (call("doc:render-line", ci->focus, 0, m) < 0 ||
102 call("Window:bury", ci->focus);
104 /* Ask viewer to move forward */
108 DEF_CMD(autosave_dir_delete)
110 struct mark *m = ci->mark;
117 fn = pane_mark_attr(ci->focus, m, "target");
118 if (!fn || *fn != '/')
121 dir = pane_attr_get(ci->focus, "dirname");
122 base = pane_mark_attr(ci->focus, m, "name");
124 fn = strconcat(ci->focus, dir, base);
126 /* trigger a directory reread */
127 call("doc:notify:doc:revisit", ci->focus, 1);
134 DEF_CMD(autosave_dir_empty)
136 call("Window:bury", ci->focus);
140 static struct map *asd_map;
141 DEF_LOOKUP_CMD(autosavedir_handle, asd_map);
143 static struct map *as_map;
144 DEF_LOOKUP_CMD(autosave_handle, as_map);
145 static void autosave_init(void)
149 as_map = key_alloc();
150 key_add(as_map, "doc:cmd-s", &autosave_keep);
151 key_add(as_map, "doc:cmd-y", &autosave_keep);
152 key_add(as_map, "doc:cmd-d", &autosave_del);
153 key_add(as_map, "doc:cmd-n", &autosave_ignore);
154 key_add(as_map, "doc:cmd-q", &autosave_ignore);
155 key_add(as_map, "doc:replaced", &autosave_ignore);
157 asd_map = key_alloc();
158 key_add(asd_map, "doc:cmd-v", &autosave_dir_view);
159 key_add(asd_map, "doc:cmd-y", &autosave_dir_view);
160 key_add(asd_map, "doc:cmd-f", &autosave_dir_view);
161 key_add(asd_map, "doc:cmd-o", &autosave_dir_view);
162 key_add(asd_map, "doc:cmd-\n", &autosave_dir_view);
163 key_add(asd_map, "doc:cmd:Enter", &autosave_dir_view);
165 key_add(asd_map, "doc:cmd-d", &autosave_dir_delete);
166 key_add(asd_map, "doc:cmd-i", &autosave_dir_ignore);
167 key_add(asd_map, "doc:cmd-n", &autosave_dir_ignore);
168 key_add(asd_map, "Notify:filter:empty", &autosave_dir_empty);
174 struct call_return *cr = container_of(ci->comm, struct call_return, c);
176 if (cr->p == NULL || ci->num > cr->i) {
183 DEF_CMD(ask_autosave)
185 struct pane *p = ci->focus;
187 struct call_return cr;
188 char *f = NULL, *a = NULL, *diffcmd;
191 char *autosave_type = "";
193 /* Need to choose best display */
194 cr.i = 0; cr.p = NULL;
196 call_comm("editor:notify:all-displays", p, &cr.c);
201 p2 = call_ret(pane, "PopupTile", pane_leaf(cr.p), 0, NULL, "DM3sta");
205 if ((s = pane_attr_get(p, "autosave-exists")) != NULL &&
206 strcmp(s, "yes") == 0) {
207 f = pane_attr_get(p, "filename");
208 a = pane_attr_get(p, "autosave-name");
209 autosave_type = "autosave";
210 } else if ((s = pane_attr_get(p, "is_backup")) != NULL &&
211 strcmp(s, "yes") == 0) {
212 f = pane_attr_get(p, "base-name");
213 a = pane_attr_get(p, "filename");
214 autosave_type = "backup";
218 call("popup:close", p2);
221 doc = call_ret(pane, "doc:from-text", p,
222 0, NULL, "*Autosave-Diff*",
225 call("doc:replace", doc, 0, NULL, "Original file: ");
226 call("doc:replace", doc, 0, NULL, f);
227 call("doc:replace", doc, 0, NULL, "\nAutosave file: ");
228 call("doc:replace", doc, 0, NULL, a);
229 call("doc:replace", doc, 0, NULL, "\n\n");
230 call("doc:set:autoclose", doc, 1);
231 diffcmd = strconcat(p, "diff -Nu ",f," ",a);
232 call("attach-shellcmd", doc, 2, NULL, diffcmd);
233 attr_set_str(&doc->attrs, "view-default", "diff");
234 p2 = home_call_ret(pane, doc, "doc:attach-view", p2, 1);
238 p2 = pane_register(p2, 0, &autosave_handle.c);
242 attr_set_str(&p2->attrs, "orig_name", f);
243 attr_set_str(&p2->attrs, "autosave_name", a);
244 attr_set_str(&p2->attrs, "autosave_type", autosave_type);
246 pane_add_notify(p2, doc, "doc:replaced");
251 DEF_CMD(check_autosave)
254 struct pane *p = ci->focus;
256 s = pane_attr_get(p, "filename");
257 if (s && strlen(s) > 17 &&
258 strcmp(s + strlen(s) - 17, "/.edlib_autosave/") == 0) {
259 attr_set_str(&p->attrs, "view-default", "autosave-dir-view");
262 s = pane_attr_get(p, "autosave-exists");
263 if (!s || strcmp(s, "yes") != 0)
264 s = pane_attr_get(p, "is_backup");
265 if (s && strcmp(s, "yes") == 0)
266 call_comm("event:on-idle", p, &ask_autosave);
271 DEF_CMD(attach_asview)
275 p = call_ret(pane, "attach-dirview", ci->focus);
278 p = pane_register(p, 0, &autosavedir_handle.c);
281 attr_set_str(&p->attrs, "line-format", " %target");
282 attr_set_str(&p->attrs, "heading",
283 "Autosave files: [v]iew, [d]elete, [i]gnore");
284 p = call_ret(pane, "attach-linefilter", p);
286 attr_set_str(&p->attrs, "filter:attr", "arrow");
287 attr_set_str(&p->attrs, "filter:match", " -> ");
288 comm_call(ci->comm2, "cb", p);
293 DEF_CMD(show_autosave)
296 char *home = getenv("HOME");
297 char *dirname = getenv("EDLIB_AUTOSAVE");
301 call("Message", ci->focus, 0, NULL,
302 "Cannot determine HOME directory");
305 dirname = strconcat(ci->focus, home, "/.edlib_autosave");
307 p = call_ret(pane, "ThisPane", ci->focus);
310 d = call_ret(pane, "doc:open", p, -1, NULL, dirname);
312 home_call_ret(pane, d, "doc:attach-view", p,
315 call("Message", ci->focus, 0, NULL,
316 "Cannot open $HOME/.edlib_autosave");
322 DEF_CMD(check_autosave_dir)
324 /* Should I open the direct doc and use filter to do the search?? */
327 char *home = getenv("HOME");
328 char *dirname = getenv("EDLIB_AUTOSAVE");
331 dirname = strconcat(ci->focus, home ?: "", "/.edlib_autosave");
332 dir = opendir(dirname);
335 while ((de = readdir(dir)) != NULL) {
336 if (de->d_name[0] == '.')
338 if (de->d_type == DT_LNK)
340 if (de->d_type != DT_UNKNOWN)
342 /* FIXME I should probably lstat the name */
347 call("editor:notify:Message:broadcast", ci->focus, 0, NULL,
348 "Autosave files exist - use \"recover\" command to view them.");
352 void edlib_init(struct pane *ed safe)
355 call_comm("global-set-command", ed, &check_autosave,
356 0, NULL, "doc:appeared-check-autosave");
357 call_comm("global-set-command", ed, &attach_asview,
358 0, NULL, "attach-autosave-dir-view");
359 call_comm("global-set-command", ed, &show_autosave,
360 0, NULL, "interactive-cmd-recover");
361 call_comm("global-set-command", ed, &check_autosave_dir,
362 0, NULL, "startup-autosave");