]> git.neil.brown.name Git - edlib.git/blob - render-complete.c
Move *Documents* to a separate doc handle.
[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 = {0};
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 = {0}, ci3 = {0};
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         struct pane *p = ci->home, *c;
208
209         complete_attach.func(ci);
210         c = pane_child(p);
211         if (c)
212                 return pane_clone(c, parent->focus);
213         return 1;
214 }
215
216 DEF_CMD(complete_nomove)
217 {
218         if (strcmp(ci->key, "Move-File") == 0)
219                 return 0;
220         if (strcmp(ci->key, "Move-to") == 0)
221                 return 0;
222         if (strcmp(ci->key, "Move-Line") == 0)
223                 return 0;
224         return 1;
225 }
226
227 DEF_CMD(eol_cb)
228 {
229         /* don't save anything */
230         return 1;
231 }
232
233 DEF_CMD(complete_eol)
234 {
235         int rpt = RPT_NUM(ci);
236
237         if (rpt >= -1 && rpt <= 1)
238                 /* movement within the line */
239                 return 1;
240         while (rpt < -1) {
241                 struct cmd_info ci2 = {0};
242                 ci2.key = "render-line-prev";
243                 ci2.numeric = 1;
244                 ci2.mark = ci->mark;
245                 ci2.focus = ci->focus;
246                 ci2.home = ci->home;
247                 if (render_complete_prev_func(&ci2) < 0)
248                         rpt = -1;
249                 rpt += 1;
250         }
251         while (rpt > 1) {
252                 struct cmd_info ci2 = {0};
253                 struct call_return cr;
254                 ci2.key = "render-line";
255                 ci2.numeric = NO_NUMERIC;
256                 ci2.mark = ci->mark;
257                 ci2.focus = ci->focus;
258                 ci2.home = ci->home;
259                 cr.c = eol_cb;
260                 ci2.comm2 = &cr.c;
261                 if (render_complete_line_func(&ci2) <= 0)
262                         rpt = 1;
263                 rpt -= 1;
264         }
265         return 1;
266 }
267
268 static int common_len(char *a, char *b)
269 {
270         int len = 0;
271         while (*a && *a == *b) {
272                 a += 1;
273                 b += 1;
274                 len += 1;
275         }
276         return len;
277 }
278
279 DEF_CMD(complete_set_prefix)
280 {
281         /* Set the prefix, force a full refresh, and move point
282          * to the first match.
283          * If there is no match, return -1.
284          * Otherwise return number of matches in ->extra and
285          * the longest common prefix in ->str.
286          */
287         struct pane *p = ci->home;
288         struct complete_data *cd = p->data;
289         struct cmd_info ci2 = {0};
290         struct mark *m;
291         int cnt = 0;
292         char *common = NULL;
293
294         free(cd->prefix);
295         cd->prefix = strdup(ci->str);
296
297         m = mark_at_point(ci->focus, NULL, MARK_UNGROUPED);
298         call3("Move-File", ci->focus, 1, m);
299
300         ci2.key = "render-line-prev";
301         ci2.numeric = 1;
302         ci2.mark = m;
303         ci2.focus = p;
304         ci2.home = p;
305         ci2.extra = 42; /* request copy of line in str2 */
306         while (render_complete_prev_func(&ci2) > 0) {
307                 char *c = ci2.str2;
308                 int l = strlen(c);
309                 if (c[l-1] == '\n')
310                         l -= 1;
311                 if (common == NULL)
312                         common = strndup(c, l);
313                 else
314                         common[common_len(c, common)] = 0;
315                 cnt += 1;
316         }
317         comm_call(ci->comm2, "callback:prefix", ci->focus, 0, NULL, common, 0);
318         free(common);
319         call3("Move-to", ci->focus, 0, m);
320         mark_free(m);
321         call3("render-lines:redraw", ci->focus, 0, NULL);
322         return cnt + 1;
323 }
324
325 DEF_CMD(save_str)
326 {
327         struct call_return *cr = container_of(ci->comm, struct call_return, c);
328         cr->s = ci->str ? strdup(ci->str) : NULL;
329         return 1;
330 }
331
332 DEF_CMD(complete_return)
333 {
334         /* submit the selected entry to the popup */
335         struct pane *p = ci->home;
336         struct complete_data *cd = p->data;
337         struct cmd_info ci2 = {0};
338         struct call_return cr;
339         int l;
340         char *c1, *c2;
341
342         ci2.key = "render-line";
343         ci2.focus = ci->home;
344         ci2.home = ci->home;
345         ci2.mark = ci->mark;
346         ci2.numeric = NO_NUMERIC;
347         cr.c = save_str;
348         cr.s = NULL;
349         ci2.comm2 = &cr.c;
350         render_complete_line_func(&ci2);
351         if (!cr.s)
352                 return 1;
353         l = strlen(cr.s);
354         if (l && cr.s[l-1] == '\n')
355                 cr.s[l-1] = 0;
356         c1 = c2 = cr.s;
357         while (*c2) {
358                 if (*c2 != '<') {
359                         *c1++ = *c2++;
360                         continue;
361                 }
362                 c2 += 1;
363                 if (*c2 == '<') {
364                         *c1++ = *c2++;
365                         continue;
366                 }
367                 while (*c2 && c2[-1] != '>')
368                         c2++;
369         }
370         *c1 = 0;
371
372         memset(&ci2, 0, sizeof(ci2));
373         ci2.key = ci->key;
374         ci2.focus = ci->home->parent;
375         ci2.str = cr.s + strlen(cd->prefix);
376         ci2.numeric = NO_NUMERIC;
377         key_handle(&ci2);
378         free(cr.s);
379         return 1;
380 }
381
382 static struct map *rc_map;
383
384 DEF_LOOKUP_CMD(complete_handle, rc_map)
385
386 static void register_map(void)
387 {
388         rc_map = key_alloc();
389
390         key_add(rc_map, "render-line", &render_complete_line);
391         key_add(rc_map, "render-line-prev", &render_complete_prev);
392         key_add(rc_map, "Close", &complete_close);
393         key_add(rc_map, "Clone", &complete_clone);
394
395         key_add_range(rc_map, "Move-", "Move-\377", &complete_nomove);
396         key_add(rc_map, "Move-EOL", &complete_eol);
397
398         key_add(rc_map, "popup:Return", &complete_return);
399
400         key_add(rc_map, "Complete:prefix", &complete_set_prefix);
401 }
402
403 REDEF_CMD(complete_attach)
404 {
405         struct pane *complete;
406         struct complete_data *cd;
407
408         if (!rc_map)
409                 register_map();
410
411         cd = calloc(1, sizeof(*cd));
412         complete = pane_register(ci->focus, 0, &complete_handle.c, cd, NULL);
413         if (!complete) {
414                 free(cd);
415                 return -1;
416         }
417         pane_check_size(complete);
418         cd->prefix = strdup("");
419
420         return comm_call(ci->comm2, "callback:attach", complete, 0, NULL, NULL, 0);
421 }
422
423 void edlib_init(struct editor *ed)
424 {
425         key_add(ed->commands, "render-complete-attach", &complete_attach);
426 }