]> git.neil.brown.name Git - edlib.git/blob - render-complete.c
render-complete: simplify render_complete_line
[edlib.git] / render-complete.c
1 /*
2  * Copyright Neil Brown ©2015-2021 <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 string and it suppresses all
9  * lines which don't match the string.  Matching can be case-insensitive,
10  * and may require the string to be at the start of the line.
11  *
12  * The linefilter module is used manage the selective display of lines.
13  * This module examine the results provided by linefilter and extends the
14  * string to the maximum that still matches the same set of lines.
15  * Keystrokes can extend or contract the match, which will cause display
16  * to be updated.
17  *
18  * This module doesn't hold any marks on any document.  The marks
19  * held by the rendered should be sufficient.
20  */
21
22 #define _GNU_SOURCE for strcasestr
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include "core.h"
27 #include "misc.h"
28
29 struct complete_data {
30         char *orig;
31         struct stk {
32                 struct stk *prev;
33                 const char *substr safe;
34         } *stk safe;
35         int prefix_only;
36 };
37
38 static struct map *rc_map;
39
40 DEF_LOOKUP_CMD(complete_handle, rc_map);
41
42 struct rlcb {
43         struct command c;
44         int plen;
45         const char *prefix safe, *str;
46 };
47
48 static char *add_highlight_prefix(const char *orig, int start, int plen,
49                                   const char *attr safe)
50 {
51         struct buf ret;
52         const char *c safe;
53
54         if (orig == NULL)
55                 return NULL;
56         buf_init(&ret);
57         c = orig;
58         while (start > 0 && *c) {
59                 if (*c == '<')
60                         buf_append_byte(&ret, *c++);
61                 buf_append_byte(&ret, *c++);
62                 start -= 1;
63         }
64         buf_concat(&ret, attr);
65         while (plen > 0 && *c) {
66                 if (*c == '<')
67                         buf_append_byte(&ret, *c++);
68                 buf_append_byte(&ret, *c++);
69                 plen -= 1;
70         }
71         buf_concat(&ret, "</>");
72         buf_concat(&ret, c);
73         return buf_final(&ret);
74 }
75
76 DEF_CMD(render_complete_line)
77 {
78         struct complete_data *cd = ci->home->data;
79         char *line, *start, *hl;
80         const char *match;
81         int ret;
82
83         if (!ci->mark)
84                 return Enoarg;
85
86         line = call_ret(str, ci->key, ci->home->parent, ci->num, ci->mark, NULL,
87                         0, ci->mark2);
88         if (!line)
89                 return Efail;
90         match = cd->stk->substr;
91         start = strcasestr(line, match);
92         if (!start)
93                 start = line;
94         hl = add_highlight_prefix(line, start - line, strlen(match),
95                                   "<fg:red>");
96
97         ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, hl);
98         free(hl);
99         free(line);
100         return ret;
101 }
102
103 DEF_CMD(complete_free)
104 {
105         struct complete_data *cd = ci->home->data;
106         struct stk *stk = cd->stk;
107
108         while (stk) {
109                 struct stk *t = stk;
110                 stk = stk->prev;
111                 free((void*)t->substr);
112                 free(t);
113         }
114
115         unalloc(cd, pane);
116         return 1;
117 }
118
119 static struct pane *complete_pane(struct pane *focus)
120 {
121         struct pane *complete;
122         struct complete_data *cd;
123
124         alloc(cd, pane);
125         complete = pane_register(focus, 0, &complete_handle.c, cd);
126         if (!complete) {
127                 unalloc(cd, pane);
128                 return NULL;
129         }
130         cd->stk = malloc(sizeof(cd->stk[0]));
131         cd->stk->prev = NULL;
132         cd->stk->substr = strdup("");
133         cd->prefix_only = 1;
134         return complete;
135 }
136
137 DEF_CMD(complete_clone)
138 {
139         struct pane *parent = ci->focus;
140         struct pane *complete;
141
142         complete = complete_pane(parent);
143         if (complete)
144                 pane_clone_children(ci->home, complete);
145         return 1;
146 }
147
148 DEF_CMD(complete_ignore_replace)
149 {
150         return 1;
151 }
152
153 DEF_CMD(complete_escape)
154 {
155         /* submit the original prefix back*/
156         struct complete_data *cd = ci->home->data;
157
158         /* This pane might be closed before the reply string is used,
159          * so we need to save it.
160          */
161         call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
162              strsave(ci->home, cd->orig));
163         return 1;
164 }
165
166 DEF_CMD(complete_char)
167 {
168         struct complete_data *cd = ci->home->data;
169         char *np;
170         int pl = strlen(cd->stk->substr);
171         const char *suffix = ksuffix(ci, "doc:char-");
172
173         np = malloc(pl + strlen(suffix) + 1);
174         strcpy(np, cd->stk->substr);
175         strcpy(np+pl, suffix);
176         call("Complete:prefix", ci->focus, !cd->prefix_only, NULL, np);
177         return 1;
178 }
179
180 DEF_CMD(complete_bs)
181 {
182         struct complete_data *cd = ci->home->data;
183         struct stk *stk = cd->stk;
184         char *old = NULL;
185
186         if (!stk->prev)
187                 return 1;
188         if (stk->substr[0] && !stk->prev->substr[0]) {
189                 old = (void*)stk->substr;
190                 old[strlen(old)-1] = 0;
191         } else {
192                 cd->stk = stk->prev;
193                 free((void*)stk->substr);
194                 free(stk);
195         }
196         call("Complete:prefix", ci->home, 0, NULL, NULL, 1);
197         return 1;
198 }
199
200 static int csame(char a, char b)
201 {
202         if (isupper(a))
203                 a = tolower(a);
204         if (isupper(b))
205                 b = tolower(b);
206         return a == b;
207 }
208
209 static int common_len(const char *a safe, const char *b safe)
210 {
211         int len = 0;
212         while (*a && csame(*a, *b)) {
213                 a += 1;
214                 b += 1;
215                 len += 1;
216         }
217         return len;
218 }
219
220 static void adjust_pre(char *common safe, const char *new safe, int len)
221 {
222         int l = strlen(common);
223         int newlen = 0;
224
225         while (l && len && csame(common[l-1], new[len-1])) {
226                 l -= 1;
227                 len -= 1;
228                 newlen += 1;
229         }
230         if (l)
231                 memmove(common, common+l, newlen+1);
232 }
233
234 struct setcb {
235         struct command c;
236         struct complete_data *cd safe;
237         const char *ss safe;
238         int best_match;
239         char *common;
240         /* common_pre is the longest common prefix to 'common' that
241          * appears in all matches in which 'common' appears.  It is
242          * allocated with enough space to append 'common' after the
243          * prefix.
244          */
245         char *common_pre;
246         struct mark *bestm;
247         int cnt;
248 };
249
250 DEF_CB(set_cb)
251 {
252         struct setcb *cb = container_of(ci->comm, struct setcb, c);
253         struct complete_data *cd = cb->cd;
254         const char *ss = cb->ss;
255         int len = strlen(ss);
256         const char *c = ci->str;
257         const char *match;
258         int this_match = 0;
259         int l;
260
261         if (!c)
262                 return Enoarg;
263         if (cd->prefix_only) {
264                 match = c;
265                 if (strncmp(match, ss, len) == 0)
266                         this_match += 1;
267         } else {
268                 match = strcasestr(c, ss);
269                 if (strncasecmp(c, ss, len) == 0) {
270                         this_match += 1;
271                         if (strncmp(c, ss, len) == 0)
272                                 this_match += 1;
273                 } else if (strstr(c, ss))
274                         this_match += 1;
275         }
276
277         if (!match)
278                 /* should be impossible */
279                 return 1;
280
281         l = strlen(match);
282         if (l && match[l-1] == '\n')
283                 l -= 1;
284
285         if (this_match > cb->best_match) {
286                 /* Only use matches at least this good to calculate
287                  * 'common'
288                  */
289                 cb->best_match = this_match;
290                 free(cb->common);
291                 cb->common = NULL;
292                 free(cb->common_pre);
293                 cb->common_pre = NULL;
294         }
295
296         if (this_match == cb->best_match) {
297                 /* This match can be used for 'common' and
298                  * initial cursor
299                  */
300                 mark_free(cb->bestm);
301                 if (ci->mark)
302                         cb->bestm = mark_dup(ci->mark);
303
304                 if (!cb->common) {
305                         cb->common = strndup(match, l);
306                 } else {
307                         cb->common[common_len(match, cb->common)] = 0;
308                         /* If 'match' and 'common' disagree on case of
309                          * 'prefix', use that of 'prefix'
310                          */
311                         if (memcmp(cb->common, match, len) != 0)
312                                 memcpy(cb->common, ss, len);
313                 }
314                 if (!cb->common_pre) {
315                         cb->common_pre = strndup(c, l + match-c);
316                         strncpy(cb->common_pre, c, match-c);
317                         cb->common_pre[match-c] = 0;
318                 } else
319                         adjust_pre(cb->common_pre, c, match-c);
320         }
321         cb->cnt += 1;
322         return 1;
323 }
324
325 DEF_CMD(complete_set_prefix)
326 {
327         /* Set the prefix, force a full refresh, and move point
328          * to the first match at start-of-line, or first match
329          * If there is no match, return -1.
330          * Otherwise return number of matches in ->num2 and
331          * the longest common prefix in ->str.
332          * If ci->num with ->str, allow substrings, else prefix-only
333          * if ci->num2, don't autocomplete, just display matches
334          */
335         struct pane *p = ci->home;
336         struct complete_data *cd = p->data;
337         struct setcb cb;
338         struct stk *stk;
339         struct mark *m;
340
341         /* Save a copy of the point so we can restore it if needed */
342         m = call_ret(mark, "doc:point", ci->focus);
343         if (m)
344                 m = mark_dup(m);
345
346         cb.c = set_cb;
347         cb.cd = cd;
348         cb.best_match = 0;
349         cb.common = NULL;
350         cb.common_pre = NULL;
351         cb.bestm = NULL;
352         cb.cnt = 0;
353         if (ci->str) {
354                 cb.ss = ci->str;
355                 cd->prefix_only = !ci->num;
356         } else {
357                 cb.ss = cd->stk->substr;
358         }
359
360         call_comm("Filter:set", ci->focus, &cb.c,
361                   cd->prefix_only ? 3 : 2, NULL, cb.ss);
362
363         if (cb.cnt <= 0) {
364                 /* Revert */
365                 call("Filter:set", ci->focus,
366                      cd->prefix_only ? 3 : 2, NULL, cd->stk->substr);
367                 if (m)
368                         call("Move-to", ci->focus, 0, m);
369         }
370         mark_free(m);
371
372         if (cb.common_pre && cb.common && cb.cnt && ci->str) {
373                 if (ci->num2 == 0)
374                         strcat(cb.common_pre, cb.common);
375                 stk = malloc(sizeof(*stk));
376                 stk->substr = cb.common_pre;
377                 stk->prev = cd->stk;
378                 cd->stk = stk;
379                 cb.common_pre = NULL;
380                 call("Filter:set", ci->focus,
381                      cd->prefix_only ? 3 : 2, NULL, cd->stk->substr);
382                 comm_call(ci->comm2, "callback:prefix", ci->focus, cb.cnt,
383                           NULL, cd->stk->substr);
384                 if (!cd->orig)
385                         cd->orig = strdup(ci->str);
386         } else {
387                 comm_call(ci->comm2, "callback:prefix", ci->focus, 0);
388         }
389         free(cb.common);
390         free(cb.common_pre);
391         if (cb.bestm) {
392                 call("Move-to", ci->focus, 0, cb.bestm);
393                 mark_free(cb.bestm);
394         }
395
396         call("view:changed", ci->focus);
397
398         return cb.cnt + 1;
399 }
400
401 DEF_CB(save_str)
402 {
403         struct call_return *cr = container_of(ci->comm, struct call_return, c);
404         cr->s = ci->str ? strdup(ci->str) : NULL;
405         return 1;
406 }
407
408 DEF_CMD(complete_return)
409 {
410         /* submit the selected entry to the popup */
411         struct call_return cr;
412         int l;
413         char *c1, *c2;
414
415         if (!ci->mark)
416                 return Enoarg;
417
418         cr.c = save_str;
419         cr.s = NULL;
420         home_call(ci->home, "doc:render-line",
421                   ci->home, NO_NUMERIC, ci->mark, NULL, 0, NULL,
422                   NULL, 0,0, &cr.c);
423         if (!cr.s)
424                 return 1;
425         l = strlen(cr.s);
426         if (l && cr.s[l-1] == '\n')
427                 cr.s[l-1] = 0;
428         c1 = c2 = cr.s;
429         while (*c2) {
430                 if (*c2 != '<') {
431                         *c1++ = *c2++;
432                         continue;
433                 }
434                 c2 += 1;
435                 if (*c2 == '<') {
436                         *c1++ = *c2++;
437                         continue;
438                 }
439                 while (*c2 && c2[-1] != '>')
440                         c2++;
441         }
442         *c1 = 0;
443
444         call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
445              cr.s, 0);
446         free(cr.s);
447         return 1;
448 }
449
450 static void register_map(void)
451 {
452         rc_map = key_alloc();
453
454         key_add(rc_map, "doc:render-line", &render_complete_line);
455         key_add(rc_map, "Free", &complete_free);
456         key_add(rc_map, "Clone", &complete_clone);
457
458         key_add(rc_map, "Replace", &complete_ignore_replace);
459         key_add(rc_map, "K:ESC", &complete_escape);
460         key_add_range(rc_map, "doc:char- ", "doc:char-~", &complete_char);
461         key_add(rc_map, "K:Backspace", &complete_bs);
462
463         key_add(rc_map, "K:Enter", &complete_return);
464
465         key_add(rc_map, "Complete:prefix", &complete_set_prefix);
466 }
467
468 DEF_CMD(complete_attach)
469 {
470         struct pane *p = ci->focus;
471         struct pane *complete;
472
473         if (!rc_map)
474                 register_map();
475
476         p = call_ret(pane, "attach-linefilter", p);
477         if (!p)
478                 return Efail;
479         complete = complete_pane(p);
480         if (!complete) {
481                 pane_close(p);
482                 return Efail;
483         }
484
485         return comm_call(ci->comm2, "callback:attach", complete);
486 }
487
488 void edlib_init(struct pane *ed safe)
489 {
490         call_comm("global-set-command", ed, &complete_attach,
491                   0, NULL, "attach-render-complete");
492 }