]> git.neil.brown.name Git - edlib.git/blob - lib-history.c
Remove Chr- from keystroke names.
[edlib.git] / lib-history.c
1 /*
2  * Copyright Neil Brown ©2016-2019 <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         free(hi);
53         ci->home->data = safe_cast NULL;
54         /* handle was in 'hi' */
55         ci->home->handle = NULL;
56         return 1;
57 }
58
59 DEF_CMD(history_notify_close)
60 {
61         struct history_info *hi = ci->home->data;
62
63         if (ci->focus == hi->history)
64                 /* The history document is going away!!! */
65                 hi->history = NULL;
66         return 1;
67 }
68
69 DEF_CMD(history_save)
70 {
71         struct history_info *hi = ci->home->data;
72         const char *eol;
73         const char *line = ci->str;
74         const char *prev;
75
76         if (!hi->history || !ci->str)
77                 /* history document was destroyed */
78                 return 1;
79         /* Must never include a newline in a history entry! */
80         eol = strchr(ci->str, '\n');
81         if (eol)
82                 line = strnsave(ci->home, ci->str, eol - ci->str);
83
84         prev = call_ret(strsave, "history:get-last", ci->focus);
85         if (prev && line && strcmp(prev, line) == 0)
86                 return 0;
87
88         call("Move-File", hi->history, 1);
89         call("Replace", hi->history, 1, NULL, line);
90         call("Replace", hi->history, 1, NULL, "\n", 1);
91         return 1;
92 }
93
94 DEF_CMD(history_done)
95 {
96         history_save_func(ci);
97         return Efallthrough;
98 }
99
100
101 DEF_CMD(history_notify_replace)
102 {
103         struct history_info *hi = ci->home->data;
104
105         if (hi->history)
106                 hi->changed = 1;
107         return 1;
108 }
109
110 DEF_CMD(history_move)
111 {
112         struct history_info *hi = ci->home->data;
113         struct mark *m;
114         char *l, *e;
115         const char *suffix = ksuffix(ci, "K:M-");
116
117         if (!hi->history || !ci->mark)
118                 return 0;
119         if (*suffix == 'p') {
120                 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
121                 call("Move-EOL", hi->history, -2);
122         } else {
123                 call("Move-EOL", hi->history, 1);
124                 call("Move-Char", hi->history, 1);
125                 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
126                 call("Move-EOL", hi->history, 1, m);
127                 call("Move-Char", hi->history, 1, m);
128         }
129         l = call_ret(str, "doc:get-str", hi->history, 0, NULL, NULL, 0, m);
130         if (!l || !*l) {
131                 /* No more history */
132                 free(l);
133                 if (*suffix == 'p') {
134                         mark_free(m);
135                         return 1;
136                 } else
137                         l = hi->saved;
138         }
139         if (l) {
140                 e = strchr(l, '\n');
141                 if (e)
142                         *e = 0;
143         }
144         call("Move-EOL", ci->focus, -1, ci->mark);
145         m = mark_dup(ci->mark);
146         call("Move-EOL", ci->focus, 1, m);
147         if (hi->changed) {
148                 if (l != hi->saved)
149                         free(hi->saved);
150                 hi->saved = call_ret(str, "doc:get-str", ci->focus,
151                                      0, ci->mark, NULL,
152                                      0, m);
153         }
154         call("Replace", ci->focus, 1, m, l);
155         if (l != hi->saved){
156                 free(l);
157                 hi->changed = 0;
158         }
159         mark_free(m);
160         return 1;
161 }
162
163 DEF_CMD(history_attach)
164 {
165         struct history_info *hi;
166         struct pane *p;
167
168         if (!ci->str || !ci->str2)
169                 return Enoarg;
170
171         hi = calloc(1, sizeof(*hi));
172         hi->done_map = key_alloc();
173         hi->handle = history_handle;
174         hi->handle.m = &hi->done_map;
175         key_add_chain(hi->done_map, history_map);
176         key_add(hi->done_map, ci->str2, &history_done);
177         p = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
178         if (!p)
179                 p = call_ret(pane, "doc:from-text", ci->focus, 0, NULL, ci->str,
180                              0, NULL, "");
181         if (!p) {
182                 free(hi);
183                 return 0;
184         }
185         hi->history = call_ret(pane, "doc:attach-view", p, -1, NULL, "invisible");
186         if (!hi->history)
187                 return 0;
188         call("Move-File", hi->history, 1);
189         buf_init(&hi->search);
190         p = pane_register(ci->focus, 0, &hi->handle.c, hi);
191         pane_add_notify(p, hi->history, "Notify:Close");
192         call("doc:request:doc:replaced", p);
193         return comm_call(ci->comm2, "callback:attach", p);
194 }
195
196 DEF_CMD(history_hlast)
197 {
198         struct history_info *hi = ci->home->data;
199         struct pane *doc = hi->history;
200         struct mark *m, *m2;
201         int rv;
202
203         if (!doc)
204                 return Einval;
205
206         m = vmark_new(doc, MARK_UNGROUPED, NULL);
207         if (!m)
208                 return 1;
209         call("doc:set-ref", doc, 0, m);
210         call("doc:set", doc, 0, m, NULL, 1);
211         mark_step_pane(doc, m, 0, 1);
212         m2 = mark_dup(m);
213         while (doc_prior_pane(doc, m) != '\n')
214                 if (mark_step_pane(doc, m, 0, 1) == WEOF)
215                         break;
216         rv = call_comm("doc:get-str", doc, ci->comm2, 0, m, NULL, 0, m2);
217         mark_free(m);
218         mark_free(m2);
219         return rv;
220 }
221
222 DEF_CMD(history_last)
223 {
224         /* Get last line from the given history document */
225         struct pane *doc;
226         struct mark *m, *m2;
227         int rv;
228
229         doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
230         if (!doc)
231                 return 1;
232         m = vmark_new(doc, MARK_UNGROUPED, NULL);
233         if (!m)
234                 return 1;
235         call("doc:set-ref", doc, 0, m);
236         call("doc:set", doc, 0, m, NULL, 1);
237         mark_step_pane(doc, m, 0, 1);
238         m2 = mark_dup(m);
239         while (doc_prior_pane(doc, m) != '\n')
240                 if (mark_step_pane(doc, m, 0, 1) == WEOF)
241                         break;
242         rv = call_comm("doc:get-str", doc, ci->comm2, 0, m, NULL, 0, m2);
243         mark_free(m);
244         mark_free(m2);
245         return rv;
246 }
247
248 void edlib_init(struct pane *ed safe)
249 {
250         call_comm("global-set-command", ed, &history_attach, 0, NULL, "attach-history");
251         call_comm("global-set-command", ed, &history_last, 0, NULL, "history-get-last");
252
253         if (history_map)
254                 return;
255
256         history_map = key_alloc();
257         key_add(history_map, "Close", &history_close);
258         key_add(history_map, "Free", &history_free);
259         key_add(history_map, "Notify:Close", &history_notify_close);
260         key_add(history_map, "doc:replaced", &history_notify_replace);
261         key_add(history_map, "K:M-p", &history_move);
262         key_add(history_map, "K:M-n", &history_move);
263         key_add(history_map, "history:save", &history_save);
264         key_add(history_map, "history:get-last", &history_hlast);
265 }