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