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