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