2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * render-complete - support string completion.
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.
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
18 * This module doesn't hold any marks on any document. The marks
19 * held by the rendered should be sufficient.
22 #define _GNU_SOURCE for strcasestr
26 #define PANE_DATA_TYPE struct complete_data
30 struct complete_data {
35 const char *substr safe;
39 #include "core-pane.h"
41 static struct map *rc_map;
43 DEF_LOOKUP_CMD(complete_handle, rc_map);
48 const char *prefix safe, *str;
51 static void strip_attrs(char *c safe)
56 if (*c == ack || *c == etx)
67 if (*c == '<' && c[1] == '<') {
82 static const char *add_highlight(const char *orig, int start, int len,
83 const char *attr safe, int *offset, int *cpos)
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.
113 buf_append_byte(&ret, ack);
116 while (*c && (!offset || ret.len < *offset)) {
117 if (cp >= 0 && (c-orig) >= cp && *cpos == -1)
119 if ((use_lt && (*c != '<' || c[1] == '<')) ||
120 (!use_lt && (*c != ack && *c != soh && *c != etx))) {
121 /* This is regular text */
124 else if (start == 0) {
126 buf_append(&ret, '<');
127 buf_concat(&ret, attr);
128 buf_append(&ret, '>');
130 buf_append(&ret, soh);
131 buf_concat(&ret, attr);
132 buf_append(&ret, stx);
136 if (use_lt && *c == '<')
137 buf_append_byte(&ret, *c++);
138 buf_append_byte(&ret, *c++);
139 if (start < 0 && len > 0) {
143 buf_concat(&ret, "</>");
145 buf_append(&ret, etx);
150 /* Not regular text. */
151 if (start < 0 && len > 0) {
152 /* Close the attr highlight */
155 buf_concat(&ret, "</>");
157 buf_append(&ret, etx);
160 buf_append(&ret, *c);
161 if (*c == ack || *c == etx) {
166 while (*c && *c != etx)
167 buf_append(&ret, *c++);
169 buf_append(&ret, *c++);
171 while (*c && *c != '>')
172 buf_append_byte(&ret, *c++);
174 buf_append(&ret, *c++);
179 return buf_final(&ret);
190 DEF_CMD(render_complete_line)
192 struct complete_data *cd = ci->home->data;
193 char *line, *l2, *start = NULL;
200 if (!ci->mark || !cd->stk)
203 m = mark_dup(ci->mark);
204 line = call_ret(str, ci->key, ci->home->parent, -1, m);
209 match = cd->stk->substr;
210 l2 = strsave(ci->home, line);
213 start = strcasestr(l2, match);
218 startlen = start - l2;
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.
225 hl = add_highlight(line, startlen, strlen(match), "fg:red", &num, NULL);
230 line = call_ret(str, ci->key, ci->home->parent,
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.
238 offset = call_comm(ci->key, ci->home->parent, &get_offset,
239 ci->num, ci->mark, NULL,
246 mark_to_mark(ci->mark, m);
251 hl = add_highlight(line, startlen, strlen(match), "fg:red", NULL, &offset);
253 ret = comm_call(ci->comm2, "callback:render", ci->focus,
261 DEF_CMD_CLOSED(complete_close)
263 struct complete_data *cd = ci->home->data;
264 struct stk *stk = cd->stk;
269 free((void*)t->substr);
279 static struct pane *complete_pane(struct pane *focus safe)
281 struct pane *complete;
282 struct complete_data *cd;
284 complete = pane_register(focus, 0, &complete_handle.c);
288 cd->stk = malloc(sizeof(cd->stk[0]));
289 cd->stk->prev = NULL;
290 cd->stk->substr = strdup("");
295 DEF_CMD(complete_clone)
297 struct pane *parent = ci->focus;
298 struct pane *complete;
300 complete = complete_pane(parent);
302 pane_clone_children(ci->home, complete);
306 DEF_CMD(complete_ignore_replace)
311 DEF_CMD(complete_escape)
313 /* submit the original prefix back*/
314 struct complete_data *cd = ci->home->data;
316 /* This pane might be closed before the reply string is used,
317 * so we need to save it.
319 call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
320 strsave(ci->home, cd->orig));
324 DEF_CMD(complete_char)
326 struct complete_data *cd = ci->home->data;
329 const char *suffix = ksuffix(ci, "doc:char-");
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,
345 struct complete_data *cd = ci->home->data;
346 struct stk *stk = cd->stk;
349 if (!stk || !stk->prev)
351 if (stk->substr[0] && !stk->prev->substr[0]) {
352 old = (void*)stk->substr;
353 old[strlen(old)-1] = 0;
356 free((void*)stk->substr);
359 call("Complete:prefix", ci->home, 0, NULL, NULL, 1,
364 static int csame(char a, char b)
373 static int common_len(const char *a safe, const char *b safe)
376 while (*a && csame(*a, *b)) {
384 static void adjust_pre(char *common safe, const char *new safe, int len)
386 int l = strlen(common);
389 while (l && len && csame(common[l-1], new[len-1])) {
395 memmove(common, common+l, newlen+1);
400 struct complete_data *cd safe;
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
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;
427 if (cd->prefix_only) {
429 if (strncmp(match, ss, len) == 0)
432 match = strcasestr(c, ss);
433 if (strncasecmp(c, ss, len) == 0) {
435 if (strncmp(c, ss, len) == 0)
437 } else if (strstr(c, ss))
442 /* should be impossible */
446 if (l && match[l-1] == '\n')
449 if (this_match > cb->best_match) {
450 /* Only use matches at least this good to calculate
453 cb->best_match = this_match;
456 free(cb->common_pre);
457 cb->common_pre = NULL;
461 if (this_match == cb->best_match) {
462 /* This match can be used for 'common' and
465 mark_free(cb->bestm);
467 cb->bestm = mark_dup(ci->mark);
470 cb->common = strndup(match, l);
472 cb->common[common_len(match, cb->common)] = 0;
473 /* If 'match' and 'common' disagree on case of
474 * 'prefix', use that of 'prefix'
476 if (memcmp(cb->common, match, len) != 0)
477 memcpy(cb->common, ss, len);
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;
484 adjust_pre(cb->common_pre, c, match-c);
490 DEF_CMD(complete_set_prefix)
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
500 struct pane *p = ci->home;
501 struct complete_data *cd = p->data;
508 /* Save a copy of the point so we can restore it if needed */
509 m = call_ret(mark, "doc:point", ci->focus);
517 cb.common_pre = NULL;
522 cd->prefix_only = !ci->num;
524 cb.ss = cd->stk->substr;
526 if (ci->str2 && (!cd->attr || strcmp(cd->attr, ci->str2) != 0)) {
528 cd->attr = strdup(ci->str2);
531 call_comm("Filter:set", ci->focus, &cb.c,
532 cd->prefix_only ? 3 : 2, NULL, cb.ss, 0, NULL, cd->attr);
536 call("Filter:set", ci->focus,
537 cd->prefix_only ? 3 : 2, NULL, cd->stk->substr,
540 call("Move-to", ci->focus, 0, m);
544 if (cb.common_pre && cb.common && cb.cnt && ci->str) {
546 strcat(cb.common_pre, cb.common);
547 stk = malloc(sizeof(*stk));
548 stk->substr = cb.common_pre;
551 cb.common_pre = NULL;
552 call("Filter:set", ci->focus,
553 cd->prefix_only ? 3 : 2, NULL, cd->stk->substr,
555 comm_call(ci->comm2, "callback:prefix", ci->focus, cb.cnt,
556 NULL, cd->stk->substr);
558 cd->orig = strdup(ci->str);
560 comm_call(ci->comm2, "callback:prefix", ci->focus, 0);
565 call("Move-to", ci->focus, 0, cb.bestm);
569 call("view:changed", ci->focus);
576 struct call_return *cr = container_of(ci->comm, struct call_return, c);
577 cr->s = ci->str ? strdup(ci->str) : NULL;
581 DEF_CMD(complete_return)
583 /* submit the selected entry to the popup */
584 struct call_return cr;
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,
601 if (l && cr.s[l-1] == '\n')
604 call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
610 static void register_map(void)
612 rc_map = key_alloc();
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);
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);
623 key_add(rc_map, "K:Enter", &complete_return);
625 key_add(rc_map, "Complete:prefix", &complete_set_prefix);
628 DEF_CMD(complete_attach)
630 struct pane *p = ci->focus;
631 struct pane *complete;
636 p = call_ret(pane, "attach-linefilter", p);
639 complete = complete_pane(p);
645 return comm_call(ci->comm2, "callback:attach", complete);
648 void edlib_init(struct pane *ed safe)
650 call_comm("global-set-command", ed, &complete_attach,
651 0, NULL, "attach-render-complete");