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