]> git.neil.brown.name Git - edlib.git/blob - render-complete.c
Complete: prefer matches at start of line.
[edlib.git] / render-complete.c
1 /*
2  * Copyright Neil Brown ©2015-2019 <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 don't 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 safe;
24         int prefix_only;
25 };
26
27 struct rlcb {
28         struct command c;
29         int keep, plen, cmp;
30         int prefix_only;
31         char *prefix safe, *str;
32 };
33
34 static char *add_highlight_prefix(char *orig, int start, int plen, char *attr safe)
35 {
36         struct buf ret;
37         char *c safe;
38
39         if (orig == NULL)
40                 return orig;
41         buf_init(&ret);
42         c = orig;
43         while (start > 0 && *c) {
44                 if (*c == '<')
45                         buf_append_byte(&ret, *c++);
46                 buf_append_byte(&ret, *c++);
47                 start -= 1;
48         }
49         buf_concat(&ret, attr);
50         while (plen > 0 && *c) {
51                 if (*c == '<')
52                         buf_append_byte(&ret, *c++);
53                 buf_append_byte(&ret, *c++);
54                 plen -= 1;
55         }
56         buf_concat(&ret, "</>");
57         buf_concat(&ret, c);
58         return buf_final(&ret);
59 }
60
61 DEF_CMD(save_highlighted)
62 {
63         struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
64         char *start;
65
66         if (!ci->str)
67                 return 1;
68
69         start = strstr(ci->str, cb->prefix);
70         if (!start)
71                 start = ci->str;
72         cb->str = add_highlight_prefix(ci->str, start - ci->str, cb->plen, "<fg:red>");
73         return 1;
74 }
75
76 DEF_CMD(rcl_cb)
77 {
78         struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
79         if (ci->str == NULL)
80                 cb->cmp = 0;
81         else if (cb->prefix_only)
82                 cb->cmp = strncmp(ci->str, cb->prefix, cb->plen);
83         else
84                 cb->cmp = strstr(ci->str, cb->prefix) ? 0 : 1;
85         return 1;
86 }
87 DEF_CMD(render_complete_line)
88 {
89         /* The first line *must* match the prefix.
90          * skip over any following lines that don't
91          */
92         struct complete_data *cd = ci->home->data;
93         int plen = strlen(cd->prefix);
94         struct rlcb cb;
95         int ret;
96         struct mark *m;
97
98         if (!ci->mark || !ci->home->parent)
99                 return Enoarg;
100
101         cb.c = save_highlighted;
102         cb.plen = plen;
103         cb.prefix_only = cd->prefix_only;
104         cb.prefix = cd->prefix;
105         cb.str = NULL;
106         if (call_comm(ci->key, ci->home->parent, &cb.c, ci->num, ci->mark, NULL,
107                       0, ci->mark2) == 0)
108                 return 0;
109
110         ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, cb.str);
111         free(cb.str);
112         if (ci->num != NO_NUMERIC)
113                 return ret;
114         /* Need to continue over other matching lines */
115         m = mark_dup(ci->mark);
116         while (1) {
117                 cb.c = rcl_cb;
118                 cb.plen = plen;
119                 cb.prefix = cd->prefix;
120                 cb.prefix_only = cd->prefix_only;
121                 cb.cmp = 0;
122                 call_comm(ci->key, ci->home->parent, &cb.c, ci->num, m, NULL,
123                           0, ci->mark2);
124
125                 if (cb.cmp == 0)
126                         break;
127
128                 /* have a non-match, so move the mark over it. */
129                 mark_to_mark(ci->mark, m);
130         }
131         mark_free(m);
132         return ret;
133 }
134
135 DEF_CMD(rlcb)
136 {
137         struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
138         if (ci->str == NULL)
139                 cb->cmp = -1;
140         else if (cb->prefix_only)
141                 cb->cmp = strncmp(ci->str, cb->prefix, cb->plen);
142         else
143                 cb->cmp = strstr(ci->str, cb->prefix) ? 0 : 1;
144         if (cb->cmp == 0 && cb->keep && ci->str)
145                 cb->str = strdup(ci->str);
146         return 1;
147 }
148
149 static int do_render_complete_prev(struct complete_data *cd safe, struct mark *m safe,
150                                    struct pane *focus safe, int n, char **savestr)
151 {
152         /* If 'n' is 0 we just need 'start of line' so use
153          * underlying function.
154          * otherwise call repeatedly and then render the line and see if
155          * it matches the prefix.
156          */
157         struct rlcb cb;
158         int ret;
159         struct mark *m2, *m3;
160         int n2;
161
162         if (savestr)
163                 *savestr = NULL;
164         m2 = m;
165         n2 = 0;
166
167         cb.c = rlcb;
168         cb.str = NULL;
169         cb.prefix = cd->prefix;
170         cb.prefix_only = cd->prefix_only;
171         cb.plen = strlen(cb.prefix);
172         cb.cmp = 0;
173         while (1) {
174                 ret = call("render-line-prev", focus, n2, m2);
175                 if (ret <= 0 || n == 0)
176                         /* Either hit start-of-file, or have what we need */
177                         break;
178                 /* we must be looking at a possible option for the previous
179                  * line
180                  */
181                 if (m2 == m)
182                         m2 = mark_dup(m);
183                 m3 = mark_dup(m2);
184                 cb.keep = n2 == 1 && savestr;
185                 cb.str = NULL;
186                 if (call_comm("render-line", focus, &cb.c, NO_NUMERIC, m3)
187                     != 1) {
188                         mark_free(m3);
189                         break;
190                 }
191                 mark_free(m3);
192
193                 if (savestr)
194                         *savestr = cb.str;
195
196                 if (cb.cmp == 0 && n2 == 1)
197                         /* This is a valid new start-of-line */
198                         break;
199                 /* step back once more */
200                 n2 = 1;
201         }
202         if (m2 != m) {
203                 if (ret > 0)
204                         /* move m back to m2 */
205                         mark_to_mark(m, m2);
206                 mark_free(m2);
207         }
208         return ret;
209 }
210
211 DEF_CMD(render_complete_prev)
212 {
213         /* If ->num is 0 we just need 'start of line' so use
214          * underlying function.
215          * otherwise call repeatedly and then render the line and see if
216          * it matches the prefix.
217          */
218         struct complete_data *cd = ci->home->data;
219         if (!ci->mark || !ci->home->parent)
220                 return Enoarg;
221         return do_render_complete_prev(cd, ci->mark, ci->home->parent, ci->num, NULL);
222 }
223
224 DEF_CMD(complete_close)
225 {
226         struct complete_data *cd = ci->home->data;
227
228         free(cd->prefix);
229         free(cd);
230         return 1;
231 }
232
233 DEF_CMD(complete_attach);
234 DEF_CMD(complete_clone)
235 {
236         struct pane *parent = ci->focus;
237
238         complete_attach.func(ci);
239         pane_clone_children(ci->home, parent->focus);
240         return 1;
241 }
242
243 DEF_CMD(complete_nomove)
244 {
245         if (strcmp(ci->key, "Move-File") == 0)
246                 return 0;
247         if (strcmp(ci->key, "Move-to") == 0)
248                 return 0;
249         if (strcmp(ci->key, "Move-Line") == 0)
250                 return 0;
251         return 1;
252 }
253
254 DEF_CMD(eol_cb)
255 {
256         /* don't save anything */
257         return 1;
258 }
259
260 DEF_CMD(complete_eol)
261 {
262         int rpt = RPT_NUM(ci);
263
264         if (!ci->mark || !ci->focus->parent)
265                 return Enoarg;
266         if (rpt >= -1 && rpt <= 1)
267                 /* movement within the line */
268                 return 1;
269         while (rpt < -1) {
270                 if (do_render_complete_prev(ci->home->data, ci->mark,
271                                             ci->focus->parent, 1, NULL) < 0)
272                         rpt = -1;
273                 rpt += 1;
274         }
275         while (rpt > 1) {
276                 struct call_return cr;
277                 cr.c = eol_cb;
278                 if (home_call(ci->home, "render-line",
279                               ci->focus, NO_NUMERIC, ci->mark, NULL,
280                               0, NULL, NULL, 0,0, &cr.c) <= 0)
281                         rpt = 1;
282                 rpt -= 1;
283         }
284         return 1;
285 }
286
287 static int common_len(char *a safe, char *b safe)
288 {
289         int len = 0;
290         while (*a && *a == *b) {
291                 a += 1;
292                 b += 1;
293                 len += 1;
294         }
295         return len;
296 }
297
298 DEF_CMD(complete_set_prefix)
299 {
300         /* Set the prefix, force a full refresh, and move point
301          * to the first match at start-of-line, or first match
302          * If there is no match, return -1.
303          * Otherwise return number of matches in ->num2 and
304          * the longest common prefix in ->str.
305          */
306         struct pane *p = ci->home;
307         struct complete_data *cd = p->data;
308         struct mark *m;
309         struct mark *m2 = NULL;
310         char *c;
311         int cnt = 0;
312         char *common = NULL;
313         int at_start = 0;
314
315         if (!ci->str)
316                 return Enoarg;
317         free(cd->prefix);
318         cd->prefix = strdup(ci->str);
319         cd->prefix_only = !ci->num;
320
321         m = mark_at_point(ci->focus, NULL, MARK_UNGROUPED);
322         if (!m || !p->parent)
323                 return Esys;
324         /* Move to end-of-document */
325         call("Move-File", ci->focus, 1, m);
326
327         while (do_render_complete_prev(cd, m, p->parent, 1, &c) > 0 && c) {
328                 int l;
329                 char *match = c;
330                 if (!cd->prefix_only)
331                         match = strstr(match, cd->prefix);
332                 if (!match)
333                         break;
334                 l = strlen(match);
335                 if (l && match[l-1] == '\n')
336                         l -= 1;
337                 if (!cd->prefix_only && strncmp(c, cd->prefix, strlen(cd->prefix)) == 0) {
338                         if (m2)
339                                 mark_free(m2);
340                         m2 = mark_dup(m);
341                         if (!at_start) {
342                                 /* If there are matches at the start, the common prefix
343                                  * calculation only noticed them
344                                  */
345                                 at_start = 1;
346                                 free(common);
347                                 common = NULL;
348                         }
349                 } else if (at_start)
350                         at_start = 2;
351                 if (at_start != 2) {
352                         if (common == NULL)
353                                 common = strndup(match, l);
354                         else
355                                 common[common_len(match, common)] = 0;
356                 }
357                 cnt += 1;
358         }
359         comm_call(ci->comm2, "callback:prefix", ci->focus, 0, NULL, common);
360         free(common);
361         if (m2) {
362                 call("Move-to", ci->focus, 0, m2);
363                 mark_free(m2);
364         } else
365                 call("Move-to", ci->focus, 0, m);
366         mark_free(m);
367         
368         pane_damaged(ci->focus, DAMAGED_VIEW);
369         return cnt + 1;
370 }
371
372 DEF_CMD(save_str)
373 {
374         struct call_return *cr = container_of(ci->comm, struct call_return, c);
375         cr->s = ci->str ? strdup(ci->str) : NULL;
376         return 1;
377 }
378
379 DEF_CMD(complete_return)
380 {
381         /* submit the selected entry to the popup */
382         struct call_return cr;
383         int l;
384         char *c1, *c2;
385
386         if (!ci->mark || !ci->home->parent)
387                 return Enoarg;
388
389         cr.c = save_str;
390         cr.s = NULL;
391         home_call(ci->home, "render-line",
392                   ci->home, NO_NUMERIC, ci->mark, NULL, 0, NULL,
393                   NULL, 0,0, &cr.c);
394         if (!cr.s)
395                 return 1;
396         l = strlen(cr.s);
397         if (l && cr.s[l-1] == '\n')
398                 cr.s[l-1] = 0;
399         c1 = c2 = cr.s;
400         while (*c2) {
401                 if (*c2 != '<') {
402                         *c1++ = *c2++;
403                         continue;
404                 }
405                 c2 += 1;
406                 if (*c2 == '<') {
407                         *c1++ = *c2++;
408                         continue;
409                 }
410                 while (*c2 && c2[-1] != '>')
411                         c2++;
412         }
413         *c1 = 0;
414
415         call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
416               cr.s, 0);
417         free(cr.s);
418         return 1;
419 }
420
421 static struct map *rc_map;
422
423 DEF_LOOKUP_CMD(complete_handle, rc_map)
424
425 static void register_map(void)
426 {
427         rc_map = key_alloc();
428
429         key_add(rc_map, "render-line", &render_complete_line);
430         key_add(rc_map, "render-line-prev", &render_complete_prev);
431         key_add(rc_map, "Close", &complete_close);
432         key_add(rc_map, "Clone", &complete_clone);
433
434         key_add_range(rc_map, "Move-", "Move-\377", &complete_nomove);
435         key_add(rc_map, "Move-EOL", &complete_eol);
436
437         key_add(rc_map, "Enter", &complete_return);
438
439         key_add(rc_map, "Complete:prefix", &complete_set_prefix);
440 }
441
442 REDEF_CMD(complete_attach)
443 {
444         struct pane *complete;
445         struct complete_data *cd;
446
447         if (!rc_map)
448                 register_map();
449
450         cd = calloc(1, sizeof(*cd));
451         complete = pane_register(ci->focus, 0, &complete_handle.c, cd, NULL);
452         if (!complete) {
453                 free(cd);
454                 return Esys;
455         }
456         cd->prefix = strdup("");
457         cd->prefix_only = 1;
458
459         return comm_call(ci->comm2, "callback:attach", complete);
460 }
461
462 void edlib_init(struct pane *ed safe)
463 {
464         call_comm("global-set-command", ed, &complete_attach, 0, NULL, "attach-render-complete");
465 }