]> git.neil.brown.name Git - edlib.git/blob - lib-history.c
Add lib-whitespace.
[edlib.git] / lib-history.c
1 /*
2  * Copyright Neil Brown ©2016-2018 <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         free(hi->search.b);
44         free(hi->saved);
45         free(hi);
46         ci->home->data = safe_cast NULL;
47         return 1;
48 }
49
50 DEF_CMD(history_notify_close)
51 {
52         struct history_info *hi = ci->home->data;
53
54         if (ci->focus == hi->history)
55                 /* The history document is going away!!! */
56                 hi->history = NULL;
57         return 1;
58 }
59
60 DEF_CMD(history_done)
61 {
62         struct history_info *hi = ci->home->data;
63         char *eol;
64         char *line = ci->str;
65
66         if (!hi->history || !ci->str)
67                 /* history document was destroyed */
68                 return 0;
69         /* Must never include a newline in a history entry! */
70         eol = strchr(ci->str, '\n');
71         if (eol)
72                 line = strnsave(ci->home, ci->str, eol - ci->str);
73         call("Move-File", hi->history, 1);
74         call("Replace", hi->history, 1, NULL, line, 1);
75         call("Replace", hi->history, 1, NULL, "\n", 1);
76         return 0;
77 }
78
79
80 DEF_CMD(history_notify_replace)
81 {
82         struct history_info *hi = ci->home->data;
83
84         if (hi->history)
85                 hi->changed = 1;
86         return 1;
87 }
88
89 DEF_CMD(history_move)
90 {
91         struct history_info *hi = ci->home->data;
92         struct mark *m;
93         char *l, *e;
94
95         if (!hi->history || !ci->mark)
96                 return 0;
97         if (ci->key[6] == 'p') {
98                 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
99                 call("Move-EOL", hi->history, -2);
100         } else {
101                 call("Move-EOL", hi->history, 1);
102                 call("Move-Char", hi->history, 1);
103                 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
104                 call("Move-EOL", hi->history, 1, m);
105                 call("Move-Char", hi->history, 1, m);
106         }
107         l = call_ret(str, "doc:get-str", hi->history, 0, NULL, NULL, 0, m);
108         if (!l || !*l) {
109                 /* No more history */
110                 free(l);
111                 if (ci->key[6] == 'p') {
112                         mark_free(m);
113                         return 1;
114                 } else
115                         l = hi->saved;
116         }
117         if (l) {
118                 e = strchr(l, '\n');
119                 if (e)
120                         *e = 0;
121         }
122         call("Move-EOL", ci->focus, -1, ci->mark);
123         m = mark_dup(ci->mark);
124         call("Move-EOL", ci->focus, 1, m);
125         if (hi->changed) {
126                 if (l != hi->saved)
127                         free(hi->saved);
128                 hi->saved = call_ret(str, "doc:get-str", ci->focus,
129                                      0, ci->mark, NULL,
130                                      0, m);
131         }
132         call("Replace", ci->focus, 1, m, l, 1);
133         if (l != hi->saved){
134                 free(l);
135                 hi->changed = 0;
136         }
137         mark_free(m);
138         return 1;
139 }
140
141 DEF_CMD(history_attach)
142 {
143         struct history_info *hi;
144         struct pane *p;
145
146         if (!ci->str || !ci->str2)
147                 return Enoarg;
148
149         hi = calloc(1, sizeof(*hi));
150         hi->done_map = key_alloc();
151         hi->handle = history_handle;
152         hi->handle.m = &hi->done_map;
153         key_add_chain(hi->done_map, history_map);
154         key_add(hi->done_map, ci->str2, &history_done);
155         p = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
156         if (!p)
157                 p = call_ret(pane, "doc:from-text", ci->focus, 0, NULL, ci->str,
158                              0, NULL, "");
159         if (!p) {
160                 free(hi);
161                 return 0;
162         }
163         hi->history = call_ret(pane, "doc:attach-view", p, -1, NULL, "invisible");
164         if (!hi->history)
165                 return 0;
166         call("Move-File", hi->history, 1);
167         buf_init(&hi->search);
168         p = pane_register(ci->focus, 0, &hi->handle.c, hi, NULL);
169         pane_add_notify(p, hi->history, "Notify:Close");
170         call("doc:Request:Notify:doc:Replace", p);
171         return comm_call(ci->comm2, "callback:attach", p);
172 }
173
174 DEF_CMD(history_last)
175 {
176         /* Get last line from the given history document */
177         struct pane *doc;
178         struct mark *m, *m2;
179
180         doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
181         if (!doc)
182                 return 1;
183         m = vmark_new(doc, MARK_UNGROUPED, NULL);
184         if (!m)
185                 return 1;
186         call("doc:set-ref", doc, 0, m);
187         call("doc:set", doc, 0, m, NULL, 1);
188         mark_step_pane(doc, m, 0, 1);
189         m2 = mark_dup(m);
190         while (doc_prior_pane(doc, m) != '\n')
191                 if (mark_step_pane(doc, m, 0, 1) == WEOF)
192                         break;
193         return call_comm("doc:get-str", doc, ci->comm2, 0, m, NULL, 0, m2);
194 }
195
196 void edlib_init(struct pane *ed safe)
197 {
198         call_comm("global-set-command", ed, &history_attach, 0, NULL, "attach-history");
199         call_comm("global-set-command", ed, &history_last, 0, NULL, "history-get-last");
200
201         if (history_map)
202                 return;
203
204         history_map = key_alloc();
205         key_add(history_map, "Close", &history_close);
206         key_add(history_map, "Notify:Close", &history_notify_close);
207         key_add(history_map, "Notify:doc:Replace", &history_notify_replace);
208         key_add(history_map, "M-Chr-p", &history_move);
209         key_add(history_map, "M-Chr-n", &history_move);
210 }