]> git.neil.brown.name Git - edlib.git/blob - lib-autosave.c
TODO: clean out done items.
[edlib.git] / lib-autosave.c
1 /*
2  * Copyright Neil Brown ©2020-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Individual document handlers are responsible for creating
6  * autosave files. The task of this module is to provide access
7  * to those files.
8  *
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.
11  */
12
13 #include <unistd.h>
14 #include <dirent.h>
15 #define PANE_DATA_VOID
16 #include "core.h"
17
18 /*
19  * Autosave restore:
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.
25  */
26
27 static const char mesg[] =
28         "\nAutosave file has these differences, type:\n"
29         "'y' to restore,\n"
30         "'n' to ignore,\n"
31         "'d' to delete autosaved file.\n\n";
32
33 DEF_CMD(autosave_keep)
34 {
35         char *orig = pane_attr_get(ci->focus, "orig_name");
36         char *as = pane_attr_get(ci->focus, "autosave_name");
37         struct pane *d;
38
39         if (!orig || !as)
40                 return 1;
41
42         d = call_ret(pane, "doc:open", ci->focus, -1, NULL, orig);
43         if (d)
44                 call("doc:load-file", d, 4, NULL, NULL, -1);
45         call("popup:close", ci->focus);
46         return 1;
47 }
48
49 DEF_CMD(autosave_ignore)
50 {
51         call("popup:close", ci->focus);
52         return 1;
53 }
54
55 DEF_CMD(autosave_del)
56 {
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");
60         struct pane *d;
61
62         if (!fn || !as || !ast)
63                 return Efail;
64
65         /* 4 is autocreate */
66         d = call_ret(pane, "doc:open", ci->focus, -1, NULL, fn, 4);
67         if (!d) {
68                 call("Message", ci->focus, 0, NULL,
69                      strconcat(ci->focus, "Cannot open ", fn));
70                 return Efail;
71         }
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."));
76         } else {
77                 if (unlink(as) == 0)
78                         call("Message", ci->focus, 0, NULL,
79                              strconcat(ci->focus, as, " deleted."));
80         }
81         call("popup:close", ci->focus);
82         return 1;
83 }
84
85 DEF_CMD(autosave_dir_view)
86 {
87         /* Open in other pane, and follow symlink */
88         home_call(ci->home->parent, "doc:cmd-o", ci->focus, 1);
89         return 2;
90 }
91
92 DEF_CMD(autosave_dir_ignore)
93 {
94         struct mark *m;
95
96         /* If this is the last, then bury the doc */
97         if (!ci->mark)
98                 return Enoarg;
99         m = mark_dup(ci->mark);
100         doc_next(ci->home->parent, m);
101         if (call("doc:render-line", ci->focus, 0, m) < 0 ||
102             m->ref.p == NULL)
103                 call("Tile:bury", ci->focus);
104         mark_free(m);
105         /* Ask viewer to move forward */
106         return 2;
107 }
108
109 DEF_CMD(autosave_dir_delete)
110 {
111         struct mark *m = ci->mark;
112         char *dir;
113         char *base;
114         char *fn;
115
116         if (!m)
117                 return Enoarg;
118         fn = pane_mark_attr(ci->focus, m, "target");
119         if (!fn || *fn != '/')
120                 return 2;
121
122         dir = pane_attr_get(ci->focus, "dirname");
123         base = pane_mark_attr(ci->focus, m, "name");
124         if (dir && base) {
125                 fn = strconcat(ci->focus, dir, base);
126                 unlink(fn);
127                 /* trigger a directory reread */
128                 call("doc:notify:doc:revisit", ci->focus, 1);
129                 return 1;
130         } else
131                 /* Move to next */
132                 return 2;
133 }
134
135 DEF_CMD(autosave_dir_empty)
136 {
137         call("Tile:bury", ci->focus);
138         return 1;
139 }
140
141 static struct map *asd_map;
142 DEF_LOOKUP_CMD(autosavedir_handle, asd_map);
143
144 static struct map *as_map;
145 DEF_LOOKUP_CMD(autosave_handle, as_map);
146 static void autosave_init(void)
147 {
148         if (as_map)
149                 return;
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);
157
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);
165
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);
170
171 }
172
173 DEF_CB(choose_new)
174 {
175         struct call_return *cr = container_of(ci->comm, struct call_return, c);
176
177         if (cr->p == NULL || ci->num > cr->i) {
178                 cr->p = ci->focus;
179                 cr->i = ci->num;
180         }
181         return 1;
182 }
183
184 DEF_CMD(ask_autosave)
185 {
186         struct pane *p = ci->focus;
187         struct pane *p2;
188         struct call_return cr;
189         char *f = NULL, *a = NULL, *diffcmd;
190         char *s;
191         struct pane *doc;
192         char *autosave_type = "";
193
194         /* Need to choose best display */
195         cr.i = 0; cr.p = NULL;
196         cr.c = choose_new;
197         call_comm("editor:notify:all-displays", p, &cr.c);
198         if (!cr.p)
199                 /* No display!!! */
200                 return Efalse;
201
202         p2 = call_ret(pane, "PopupTile", pane_focus(cr.p), 0, NULL, "DM3sta");
203         if (!p2)
204                 return Efalse;
205
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";
216         }
217
218         if (!a || !f) {
219                 call("popup:close", p2);
220                 return Efalse;
221         }
222         doc = call_ret(pane, "doc:from-text", p,
223                        0, NULL, "*Autosave-Diff*",
224                        0, NULL, mesg);
225         if (doc) {
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);
236         } else
237                 p2 = NULL;
238         if (p2)
239                 p2 = pane_register(p2, 0, &autosave_handle.c);
240         if (!p2)
241                 return Efail;
242
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);
246         if (doc)
247                 pane_add_notify(p2, doc, "doc:replaced");
248
249         return 1;
250 }
251
252 DEF_CMD(check_autosave)
253 {
254         char *s;
255         struct pane *p = ci->focus;
256
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");
261         }
262
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);
268
269         return Efallthrough;
270 }
271
272 DEF_CMD(attach_asview)
273 {
274         struct pane *p;
275
276         p = call_ret(pane, "attach-dirview", ci->focus);
277         if (!p)
278                 return Efail;
279         p = pane_register(p, 0, &autosavedir_handle.c);
280         if (!p)
281                 return Efail;
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);
286         if (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);
290         }
291         return 1;
292 }
293
294 DEF_CMD(show_autosave)
295 {
296         struct pane *p, *d;
297         char *home = getenv("HOME");
298         char *dirname = getenv("EDLIB_AUTOSAVE");
299
300         if (!dirname) {
301                 if (!home) {
302                         call("Message", ci->focus, 0, NULL,
303                              "Cannot determine HOME directory");
304                         return 1;
305                 }
306                 dirname = strconcat(ci->focus, home, "/.edlib_autosave");
307         }
308         p = call_ret(pane, "ThisPane", ci->focus);
309         if (!p)
310                 return Efail;
311         d = call_ret(pane, "doc:open", p, -1, NULL, dirname);
312         if (d)
313                 home_call_ret(pane, d, "doc:attach-view", p,
314                               0, NULL, "simple");
315         else {
316                 call("Message", ci->focus, 0, NULL,
317                      "Cannot open $HOME/.edlib_autosave");
318                 pane_close(p);
319         }
320         return 1;
321 }
322
323 DEF_CMD(check_autosave_dir)
324 {
325         /* Should I open the direct doc and use filter to do the search?? */
326         DIR *dir;
327         struct dirent *de;
328         char *home = getenv("HOME");
329         char *dirname = getenv("EDLIB_AUTOSAVE");
330
331         if (!dirname)
332                 dirname = strconcat(ci->focus, home ?: "", "/.edlib_autosave");
333         dir = opendir(dirname);
334         if (!dir)
335                 return Efallthrough;
336         while ((de = readdir(dir)) != NULL) {
337                 if (de->d_name[0] == '.')
338                         continue;
339                 if (de->d_type == DT_LNK)
340                         break;
341                 if (de->d_type != DT_UNKNOWN)
342                         continue;
343                 /* FIXME I should probably lstat the name */
344                 continue;
345         }
346         closedir(dir);
347         if (de)
348                 call("editor:notify:Message:broadcast", ci->focus, 0, NULL,
349                      "Autosave files exist - use \"recover\" command to view them.");
350         return Efallthrough;
351 }
352
353 void edlib_init(struct pane *ed safe)
354 {
355         autosave_init();
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");
364
365 }