]> git.neil.brown.name Git - edlib.git/blob - render-complete.c
render-lines: replace "render-lines:redraw" with DAMAGED_VIEW
[edlib.git] / render-complete.c
1 /*
2  * Copyright Neil Brown ©2015 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * render-complete - support string completion.
6  *
7  * This should be attached between render-lines and the pane which
8  * provides the lines.  It is given a prefix and it suppresses all
9  * lines which start with the prefix.
10  * All events are redirected to the controlling window (where the text
11  * to be completed is being entered)
12  *
13  * This module doesn't hold any marks on any document.  The marks
14  * held by the rendered should be sufficient.
15  */
16
17 #include <stdlib.h>
18 #include <string.h>
19 #include "core.h"
20 #include "misc.h"
21
22 struct complete_data {
23         char *prefix;
24 };
25
26 struct rlcb {
27         struct command c;
28         int keep, plen, cmp;
29         char *prefix, *str;
30 };
31
32 static char *add_highlight_prefix(char *orig, int plen, char *attr)
33 {
34         struct buf ret;
35
36         if (orig == NULL)
37                 return orig;
38         buf_init(&ret);
39         buf_concat(&ret, attr);
40         while (plen > 0 && *orig) {
41                 if (*orig == '<')
42                         buf_append_byte(&ret, *orig++);
43                 buf_append_byte(&ret, *orig++);
44                 plen -= 1;
45         }
46         buf_concat(&ret, "</>");
47         buf_concat(&ret, orig);
48         return buf_final(&ret);
49 }
50
51 DEF_CMD(save_highlighted)
52 {
53         struct call_return *cr = container_of(ci->comm, struct call_return, c);
54         cr->s = add_highlight_prefix(ci->str, cr->i, "<fg:red>");
55         return 1;
56 }
57
58 DEF_CMD(rcl_cb)
59 {
60         struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
61         if (ci->str == NULL)
62                 cb->cmp = 0;
63         else
64                 cb->cmp = strncmp(ci->str, cb->prefix, cb->plen);
65         return 1;
66 }
67 DEF_CMD(render_complete_line)
68 {
69         /* The first line *must* match the prefix.
70          * skip over any following lines that don't
71          */
72         struct cmd_info ci2 = {};
73         struct complete_data *cd = ci->home->data;
74         int plen = strlen(cd->prefix);
75         struct call_return cr;
76         struct rlcb cb;
77         int ret;
78
79         if (!ci->mark)
80                 return -1;
81
82         ci2.key = ci->key;
83         ci2.mark = ci->mark;
84         ci2.mark2 = ci->mark2;
85         ci2.focus = ci->home->parent;
86         ci2.numeric = ci->numeric;
87         cr.c = save_highlighted;
88         cr.i = plen;
89         cr.s = NULL;
90         ci2.comm2 = &cr.c;
91         if (key_handle(&ci2) == 0)
92                 return 0;
93
94         ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, cr.s, 0);
95         if (ci->numeric != NO_NUMERIC)
96                 return ret;
97         /* Need to continue over other matching lines */
98         ci2.mark = mark_dup(ci->mark, 1);
99         while (1) {
100                 ci2.numeric = ci->numeric;
101                 ci2.focus = ci->home->parent;
102                 cb.c = rcl_cb;
103                 cb.plen = plen;
104                 cb.prefix = cd->prefix;
105                 cb.cmp = 0;
106                 ci2.comm2 = &cb.c;
107                 key_handle(&ci2);
108                 if (cb.cmp == 0)
109                         break;
110
111                 /* have a non-match, so move the mark over it. */
112                 mark_to_mark(ci->mark, ci2.mark);
113         }
114         mark_free(ci2.mark);
115         return ret;
116 }
117
118 DEF_CMD(rlcb)
119 {
120         struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
121         if (ci->str == NULL)
122                 cb->cmp = -1;
123         else
124                 cb->cmp = strncmp(ci->str, cb->prefix, cb->plen);
125         if (cb->cmp == 0 && cb->keep && ci->str)
126                 cb->str = strdup(ci->str);
127         return 1;
128 }
129 DEF_CMD(render_complete_prev)
130 {
131         /* If ->numeric is 0 we just need 'start of line' so use
132          * underlying function.
133          * otherwise call repeatedly and then render the line and see if
134          * it matches the prefix.
135          */
136         struct cmd_info ci2 = {}, ci3 = {};
137         struct rlcb cb;
138         struct complete_data *cd = ci->home->data;
139         int ret;
140
141         ci2.key = ci->key;
142         ci2.mark = ci->mark;
143         ci2.focus = ci->home->parent;
144         ci2.numeric = 0;
145
146         ci3.key = "render-line";
147         ci3.focus = ci->home->parent;
148         cb.c = rlcb;
149         cb.str = NULL;
150         cb.prefix = cd->prefix;
151         cb.plen = strlen(cb.prefix);
152         cb.cmp = 0;
153         ci3.comm2 = &cb.c;
154         while (1) {
155                 ret = key_handle(&ci2);
156                 if (ret <= 0 || ci->numeric == 0)
157                         /* Either hit start-of-file, or have what we need */
158                         break;
159                 /* we must be looking at a possible option for the previous
160                  * line
161                  */
162                 if (ci2.mark == ci->mark)
163                         ci2.mark = mark_dup(ci->mark, 1);
164                 ci3.mark = mark_dup(ci2.mark, 1);
165                 ci3.numeric = NO_NUMERIC;
166                 cb.keep = ci2.numeric == 1 && ci->extra == 42;
167                 cb.str = NULL;
168                 if (key_handle(&ci3) != 1) {
169                         mark_free(ci3.mark);
170                         break;
171                 }
172                 mark_free(ci3.mark);
173                 /* This is a horrible hack, but as it is entirely internal
174                  * to this module it can say for now.
175                  * Cast ci to remove any 'const' tag that I hope to add soon.
176                  */
177                 ((struct cmd_info*)ci)->str2 = cb.str;
178
179                 if (cb.cmp == 0 && ci2.numeric == 1)
180                         /* This is a valid new start-of-line */
181                         break;
182                 /* step back once more */
183                 ci2.numeric = 1;
184         }
185         if (ci2.mark != ci->mark) {
186                 if (ret > 0)
187                         /* move ci->mark back to ci2.mark */
188                         mark_to_mark(ci->mark, ci2.mark);
189                 mark_free(ci2.mark);
190         }
191         return ret;
192 }
193
194 DEF_CMD(complete_close)
195 {
196         struct pane *p = ci->home;
197         struct complete_data *cd = p->data;
198         free(cd->prefix);
199         free(cd);
200         return 1;
201 }
202
203 DEF_CMD(complete_attach);
204 DEF_CMD(complete_clone)
205 {
206         struct pane *parent = ci->focus;
207
208         complete_attach.func(ci);
209         pane_clone_children(ci->home, parent->focus);
210         return 1;
211 }
212
213 DEF_CMD(complete_nomove)
214 {
215         if (strcmp(ci->key, "Move-File") == 0)
216                 return 0;
217         if (strcmp(ci->key, "Move-to") == 0)
218                 return 0;
219         if (strcmp(ci->key, "Move-Line") == 0)
220                 return 0;
221         return 1;
222 }
223
224 DEF_CMD(eol_cb)
225 {
226         /* don't save anything */
227         return 1;
228 }
229
230 DEF_CMD(complete_eol)
231 {
232         int rpt = RPT_NUM(ci);
233
234         if (rpt >= -1 && rpt <= 1)
235                 /* movement within the line */
236                 return 1;
237         while (rpt < -1) {
238                 struct cmd_info ci2 = {};
239                 ci2.key = "render-line-prev";
240                 ci2.numeric = 1;
241                 ci2.mark = ci->mark;
242                 ci2.focus = ci->focus;
243                 ci2.home = ci->home;
244                 if (render_complete_prev_func(&ci2) < 0)
245                         rpt = -1;
246                 rpt += 1;
247         }
248         while (rpt > 1) {
249                 struct cmd_info ci2 = {};
250                 struct call_return cr;
251                 ci2.key = "render-line";
252                 ci2.numeric = NO_NUMERIC;
253                 ci2.mark = ci->mark;
254                 ci2.focus = ci->focus;
255                 ci2.home = ci->home;
256                 cr.c = eol_cb;
257                 ci2.comm2 = &cr.c;
258                 if (render_complete_line_func(&ci2) <= 0)
259                         rpt = 1;
260                 rpt -= 1;
261         }
262         return 1;
263 }
264
265 static int common_len(char *a, char *b)
266 {
267         int len = 0;
268         while (*a && *a == *b) {
269                 a += 1;
270                 b += 1;
271                 len += 1;
272         }
273         return len;
274 }
275
276 DEF_CMD(complete_set_prefix)
277 {
278         /* Set the prefix, force a full refresh, and move point
279          * to the first match.
280          * If there is no match, return -1.
281          * Otherwise return number of matches in ->extra and
282          * the longest common prefix in ->str.
283          */
284         struct pane *p = ci->home;
285         struct complete_data *cd = p->data;
286         struct cmd_info ci2 = {};
287         struct mark *m;
288         int cnt = 0;
289         char *common = NULL;
290
291         free(cd->prefix);
292         cd->prefix = strdup(ci->str);
293
294         m = mark_at_point(ci->focus, NULL, MARK_UNGROUPED);
295         call3("Move-File", ci->focus, 1, m);
296
297         ci2.key = "render-line-prev";
298         ci2.numeric = 1;
299         ci2.mark = m;
300         ci2.focus = p;
301         ci2.home = p;
302         ci2.extra = 42; /* request copy of line in str2 */
303         while (render_complete_prev_func(&ci2) > 0) {
304                 char *c = ci2.str2;
305                 int l = strlen(c);
306                 if (c[l-1] == '\n')
307                         l -= 1;
308                 if (common == NULL)
309                         common = strndup(c, l);
310                 else
311                         common[common_len(c, common)] = 0;
312                 cnt += 1;
313         }
314         comm_call(ci->comm2, "callback:prefix", ci->focus, 0, NULL, common, 0);
315         free(common);
316         call3("Move-to", ci->focus, 0, m);
317         mark_free(m);
318         pane_damaged(ci->focus, DAMAGED_VIEW);
319         return cnt + 1;
320 }
321
322 DEF_CMD(save_str)
323 {
324         struct call_return *cr = container_of(ci->comm, struct call_return, c);
325         cr->s = ci->str ? strdup(ci->str) : NULL;
326         return 1;
327 }
328
329 DEF_CMD(complete_return)
330 {
331         /* submit the selected entry to the popup */
332         struct pane *p = ci->home;
333         struct complete_data *cd = p->data;
334         struct cmd_info ci2 = {};
335         struct call_return cr;
336         int l;
337         char *c1, *c2;
338
339         ci2.key = "render-line";
340         ci2.focus = ci->home;
341         ci2.home = ci->home;
342         ci2.mark = ci->mark;
343         ci2.numeric = NO_NUMERIC;
344         cr.c = save_str;
345         cr.s = NULL;
346         ci2.comm2 = &cr.c;
347         render_complete_line_func(&ci2);
348         if (!cr.s)
349                 return 1;
350         l = strlen(cr.s);
351         if (l && cr.s[l-1] == '\n')
352                 cr.s[l-1] = 0;
353         c1 = c2 = cr.s;
354         while (*c2) {
355                 if (*c2 != '<') {
356                         *c1++ = *c2++;
357                         continue;
358                 }
359                 c2 += 1;
360                 if (*c2 == '<') {
361                         *c1++ = *c2++;
362                         continue;
363                 }
364                 while (*c2 && c2[-1] != '>')
365                         c2++;
366         }
367         *c1 = 0;
368
369         call5("popup:close", ci->home->parent, NO_NUMERIC, NULL,
370               cr.s + strlen(cd->prefix), 0);
371         free(cr.s);
372         return 1;
373 }
374
375 static struct map *rc_map;
376
377 DEF_LOOKUP_CMD(complete_handle, rc_map)
378
379 static void register_map(void)
380 {
381         rc_map = key_alloc();
382
383         key_add(rc_map, "render-line", &render_complete_line);
384         key_add(rc_map, "render-line-prev", &render_complete_prev);
385         key_add(rc_map, "Close", &complete_close);
386         key_add(rc_map, "Clone", &complete_clone);
387
388         key_add_range(rc_map, "Move-", "Move-\377", &complete_nomove);
389         key_add(rc_map, "Move-EOL", &complete_eol);
390
391         key_add(rc_map, "Return", &complete_return);
392
393         key_add(rc_map, "Complete:prefix", &complete_set_prefix);
394 }
395
396 REDEF_CMD(complete_attach)
397 {
398         struct pane *complete;
399         struct complete_data *cd;
400
401         if (!rc_map)
402                 register_map();
403
404         cd = calloc(1, sizeof(*cd));
405         complete = pane_register(ci->focus, 0, &complete_handle.c, cd, NULL);
406         if (!complete) {
407                 free(cd);
408                 return -1;
409         }
410         cd->prefix = strdup("");
411
412         return comm_call(ci->comm2, "callback:attach", complete, 0, NULL, NULL, 0);
413 }
414
415 void edlib_init(struct pane *ed)
416 {
417         call_comm("global-set-command", ed, 0, NULL, "attach-render-complete",
418                   0, &complete_attach);
419 }