]> git.neil.brown.name Git - edlib.git/blob - lib-history.c
history: add "history:add" and names for entries.
[edlib.git] / lib-history.c
1 /*
2  * Copyright Neil Brown ©2016-2020 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * history
6  *
7  * A history pane supports selection of lines from a separate
8  * document.  The underlying document is assumed to be one line
9  * and this line can be replaced by various lines from the history document.
10  * When a line is replaced, if it had been modified, it is saved first.
11  * :M-p - replace current line with previous line from history, if there is one
12  * :M-n - replace current line with next line from history.  If none, restore
13  *        saved line
14  * :M-r - incremental search - later
15  * When a selection is committed, it is added to end of history.
16  */
17
18 #include <unistd.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include "core.h"
23 #include "misc.h"
24
25 struct history_info {
26         struct pane     *history;
27         char            *saved;
28         struct buf      search;
29         int             changed;
30         struct map      *done_map;
31         struct lookup_cmd handle;
32 };
33
34 static struct map *history_map;
35 DEF_LOOKUP_CMD(history_handle, history_map);
36
37 DEF_CMD(history_close)
38 {
39         struct history_info *hi = ci->home->data;
40
41         if (hi->history)
42                 pane_close(hi->history);
43         return 1;
44 }
45
46 DEF_CMD(history_free)
47 {
48         struct history_info *hi = ci->home->data;
49
50         free(hi->search.b);
51         free(hi->saved);
52         unalloc(hi, pane);
53         /* handle was in 'hi' */
54         ci->home->handle = NULL;
55         return 1;
56 }
57
58 DEF_CMD(history_notify_close)
59 {
60         struct history_info *hi = ci->home->data;
61
62         if (ci->focus == hi->history)
63                 /* The history document is going away!!! */
64                 hi->history = NULL;
65         return 1;
66 }
67
68 DEF_CMD(history_save)
69 {
70         struct history_info *hi = ci->home->data;
71         const char *eol;
72         const char *line = ci->str;
73         const char *prev;
74
75         if (!hi->history || !ci->str)
76                 /* history document was destroyed */
77                 return 1;
78         /* Must never include a newline in a history entry! */
79         eol = strchr(ci->str, '\n');
80         if (eol)
81                 line = strnsave(ci->home, ci->str, eol - ci->str);
82
83         prev = call_ret(strsave, "history:get-last", ci->focus);
84         if (prev && line && strcmp(prev, line) == 0)
85                 return 0;
86
87         call("Move-File", hi->history, 1);
88         call("Replace", hi->history, 1, NULL, line);
89         call("Replace", hi->history, 1, NULL, "\n", 1);
90         return 1;
91 }
92
93 DEF_CMD(history_done)
94 {
95         history_save_func(ci);
96         return Efallthrough;
97 }
98
99
100 DEF_CMD(history_notify_replace)
101 {
102         struct history_info *hi = ci->home->data;
103
104         if (hi->history)
105                 hi->changed = 1;
106         return 1;
107 }
108
109 DEF_CMD(history_move)
110 {
111         struct history_info *hi = ci->home->data;
112         struct mark *m;
113         char *l, *e;
114         const char *suffix = ksuffix(ci, "K:M-");
115
116         if (!hi->history || !ci->mark)
117                 return 0;
118         if (*suffix == 'p') {
119                 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
120                 call("Move-EOL", hi->history, -2);
121         } else {
122                 call("Move-EOL", hi->history, 1);
123                 call("Move-Char", hi->history, 1);
124                 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
125                 call("Move-EOL", hi->history, 1, m);
126                 call("Move-Char", hi->history, 1, m);
127         }
128         l = call_ret(str, "doc:get-str", hi->history, 0, NULL, NULL, 0, m);
129         if (!l || !*l) {
130                 /* No more history */
131                 free(l);
132                 if (*suffix == 'p') {
133                         mark_free(m);
134                         return 1;
135                 } else
136                         l = hi->saved;
137         }
138         if (l) {
139                 e = strchr(l, '\n');
140                 if (e)
141                         *e = 0;
142         }
143         call("Move-EOL", ci->focus, -1, ci->mark);
144         m = mark_dup(ci->mark);
145         call("Move-EOL", ci->focus, 1, m);
146         if (hi->changed) {
147                 if (l != hi->saved)
148                         free(hi->saved);
149                 hi->saved = call_ret(str, "doc:get-str", ci->focus,
150                                      0, ci->mark, NULL,
151                                      0, m);
152         }
153         call("Replace", ci->focus, 1, m, l);
154         if (l != hi->saved){
155                 free(l);
156                 hi->changed = 0;
157         }
158         mark_free(m);
159         return 1;
160 }
161
162 DEF_CMD(history_attach)
163 {
164         struct history_info *hi;
165         struct pane *p;
166
167         if (!ci->str || !ci->str2)
168                 return Enoarg;
169
170         alloc(hi, pane);
171         hi->done_map = key_alloc();
172         hi->handle = history_handle;
173         hi->handle.m = &hi->done_map;
174         key_add_chain(hi->done_map, history_map);
175         key_add(hi->done_map, ci->str2, &history_done);
176         p = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
177         if (!p)
178                 p = call_ret(pane, "doc:from-text", ci->focus, 0, NULL, ci->str);
179
180         if (!p) {
181                 free(hi);
182                 return 0;
183         }
184         hi->history = call_ret(pane, "doc:attach-view", p, -1, NULL, "invisible");
185         if (!hi->history)
186                 return 0;
187         call("Move-File", hi->history, 1);
188         buf_init(&hi->search);
189         p = pane_register(ci->focus, 0, &hi->handle.c, hi);
190         if (!p)
191                 return Efail;
192         pane_add_notify(p, hi->history, "Notify:Close");
193         call("doc:request:doc:replaced", p);
194         return comm_call(ci->comm2, "callback:attach", p);
195 }
196
197 DEF_CMD(history_hlast)
198 {
199         struct history_info *hi = ci->home->data;
200         struct pane *doc = hi->history;
201         struct mark *m, *m2;
202         int rv;
203
204         if (!doc)
205                 return Einval;
206
207         m = vmark_new(doc, MARK_UNGROUPED, NULL);
208         if (!m)
209                 return 1;
210         call("doc:set-ref", doc, 0, m);
211         call("doc:set", doc, 0, m, NULL, 1);
212         doc_prev(doc,m);
213         m2 = mark_dup(m);
214         while (doc_prior(doc, m) != '\n')
215                 if (doc_prev(doc,m) == WEOF)
216                         break;
217         rv = call_comm("doc:get-str", doc, ci->comm2, 0, m, NULL, 0, m2);
218         mark_free(m);
219         mark_free(m2);
220         return rv;
221 }
222
223 static bool has_name(struct pane *doc safe, struct mark *m safe,
224                      const char *name safe)
225 {
226         char *a;
227
228         a = call_ret(strsave, "doc:get-attr", doc, 0, m, "history:name");
229         return a && strcmp(a, name) == 0;
230 }
231
232 DEF_CMD(history_last)
233 {
234         /* Get last line from the given history document
235          * If ci->num > 1 get nth last line
236          * else if ci->str, get the line with given name
237          * If both set, assign str to the nth last line
238          * Names are assign with attribute "history:name"
239          */
240         struct pane *doc;
241         struct mark *m, *m2;
242         int num = ci->num;
243         const char *name = ci->str2;
244         int rv;
245
246         doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
247         if (!doc)
248                 return 1;
249         m = vmark_new(doc, MARK_UNGROUPED, NULL);
250         if (!m)
251                 return 1;
252         call("doc:set-ref", doc, 0, m);
253         call("doc:set", doc, 0, m, NULL, 1);
254         do {
255                 doc_prev(doc,m);
256                 m2 = mark_dup(m);
257                 while (doc_prior(doc, m) != '\n')
258                         if (doc_prev(doc,m) == WEOF)
259                                 break;
260         } while (!mark_same(m, m2) && num > 1 &&
261                  (name == NULL || has_name(doc, m, name)));
262         if (mark_same(m, m2) || num > 1)
263                 rv = Efail;
264         else {
265                 if (num == 1 && name)
266                         call("doc:set-attr", doc, 0, m, "history:name",
267                              0, NULL, name);
268                 rv = call_comm("doc:get-str", doc, ci->comm2,
269                                0, m, NULL, 0, m2);
270         }
271         mark_free(m);
272         mark_free(m2);
273         return rv;
274 }
275
276 DEF_CMD(history_add)
277 {
278         const char *docname = ci->str;
279         const char *line = ci->str2;
280         struct pane *doc;
281
282         if (!docname || !line || strchr(line, '\n'))
283                 return Einval;
284         doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
285         if (!doc) {
286                 doc = call_ret(pane, "doc:from-text", ci->focus,
287                                0, NULL, ci->str);
288                 if (doc)
289                         call("global-multicall-doc:appeared-", doc);
290         }
291         if (!doc)
292                 return Efail;
293         call("doc:replace", doc, 1, NULL, line, 1);
294         call("doc:replace", doc, 1, NULL, "\n", 1);
295         return 1;
296 }
297
298 void edlib_init(struct pane *ed safe)
299 {
300         call_comm("global-set-command", ed, &history_attach, 0, NULL, "attach-history");
301         call_comm("global-set-command", ed, &history_last, 0, NULL, "history:get-last");
302         call_comm("global-set-command", ed, &history_add, 0, NULL, "history:add");
303
304         if (history_map)
305                 return;
306
307         history_map = key_alloc();
308         key_add(history_map, "Close", &history_close);
309         key_add(history_map, "Free", &history_free);
310         key_add(history_map, "Notify:Close", &history_notify_close);
311         key_add(history_map, "doc:replaced", &history_notify_replace);
312         key_add(history_map, "K:M-p", &history_move);
313         key_add(history_map, "K:M-n", &history_move);
314         key_add(history_map, "history:save", &history_save);
315         key_add(history_map, "history:get-last", &history_hlast);
316 }