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