/*
- * Copyright Neil Brown ©2015-2020 <neil@brown.name>
+ * Copyright Neil Brown ©2015-2023 <neil@brown.name>
* May be distributed under terms of GPLv2 - see file:COPYING
*
* render-complete - support string completion.
*
* This should be attached between render-lines and the pane which
- * provides the lines. It is given a prefix and it suppresses all
- * lines which don't start with the prefix.
- * All events are redirected to the controlling window (where the text
- * to be completed is being entered)
+ * provides the lines. It is given a string and it suppresses all
+ * lines which don't match the string. Matching can be case-insensitive,
+ * and may require the string to be at the start of the line.
+ *
+ * The linefilter module is used manage the selective display of lines.
+ * This module examine the results provided by linefilter and extends the
+ * string to the maximum that still matches the same set of lines.
+ * Keystrokes can extend or contract the match, which will cause display
+ * to be updated.
*
* This module doesn't hold any marks on any document. The marks
* held by the rendered should be sufficient.
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
+#define PANE_DATA_TYPE struct complete_data
#include "core.h"
#include "misc.h"
struct complete_data {
char *orig;
+ char *attr;
struct stk {
struct stk *prev;
const char *substr safe;
- } *stk safe;
+ } *stk;
int prefix_only;
};
+#include "core-pane.h"
static struct map *rc_map;
const char *prefix safe, *str;
};
-static const char *add_highlight_prefix(const char *orig, int start, int plen,
- const char *attr safe)
+static void strip_attrs(char *c safe)
+{
+ char *n = c;
+ if (*c == ack) {
+ for (; *c; c++) {
+ if (*c == ack || *c == etx)
+ continue;
+ if (*c != soh) {
+ *n++ = *c;
+ continue;
+ }
+ while (*c != stx)
+ c++;
+ }
+ } else {
+ for (; *c ; c++) {
+ if (*c == '<' && c[1] == '<') {
+ *n++ = *c++;
+ continue;
+ }
+ if (*c != '<') {
+ *n++ = *c;
+ continue;
+ }
+ while (*c != '>')
+ c++;
+ }
+ }
+ *n = 0;
+}
+
+static const char *add_highlight(const char *orig, int start, int len,
+ const char *attr safe, int *offset, int *cpos)
{
+ /* Create a copy of 'orig' with all non-attr chars from start for len
+ * given the extra 'attr'. start and len count non-attr chars.
+ * If offset!=NULL, stop when we get to that place in the result,
+ * and update *offset with that place in orig.
+ * If cpos, then when we reach cpos in orig, report len of result.
+ */
struct buf ret;
const char *c safe;
+ bool use_lt = True;
+ int cp = -1;
- if (orig == NULL)
+ if (!len)
+ return orig;
+
+ if (cpos) {
+ cp = *cpos;
+ *cpos = -1;
+ }
+
+ if (!len)
return orig;
+
+ if (orig == NULL)
+ return NULL;
buf_init(&ret);
c = orig;
- while (start > 0 && *c) {
- if (*c == '<')
- buf_append_byte(&ret, *c++);
- buf_append_byte(&ret, *c++);
- start -= 1;
+ if (*c == ack) {
+ use_lt = False;
+ buf_append_byte(&ret, ack);
+ c++;
}
- buf_concat(&ret, attr);
- while (plen > 0 && *c) {
- if (*c == '<')
+ while (*c && (!offset || ret.len < *offset)) {
+ if (cp >= 0 && (c-orig) >= cp && *cpos == -1)
+ *cpos = ret.len;
+ if ((use_lt && (*c != '<' || c[1] == '<')) ||
+ (!use_lt && (*c != ack && *c != soh && *c != etx))) {
+ /* This is regular text */
+ if (start > 0)
+ start -= 1;
+ else if (start == 0) {
+ if (use_lt) {
+ buf_append(&ret, '<');
+ buf_concat(&ret, attr);
+ buf_append(&ret, '>');
+ } else {
+ buf_append(&ret, soh);
+ buf_concat(&ret, attr);
+ buf_append(&ret, stx);
+ }
+ start = -1;
+ }
+ if (use_lt && *c == '<')
+ buf_append_byte(&ret, *c++);
buf_append_byte(&ret, *c++);
- buf_append_byte(&ret, *c++);
- plen -= 1;
+ if (start < 0 && len > 0) {
+ len -= 1;
+ if (len == 0) {
+ if (use_lt)
+ buf_concat(&ret, "</>");
+ else
+ buf_append(&ret, etx);
+ }
+ }
+ continue;
+ }
+ /* Not regular text. */
+ if (start < 0 && len > 0) {
+ /* Close the attr highlight */
+ start = 0;
+ if (use_lt)
+ buf_concat(&ret, "</>");
+ else
+ buf_append(&ret, etx);
+ }
+ if (!use_lt) {
+ buf_append(&ret, *c);
+ if (*c == ack || *c == etx) {
+ c++;
+ continue;
+ }
+ c++;
+ while (*c && *c != etx)
+ buf_append(&ret, *c++);
+ if (*c)
+ buf_append(&ret, *c++);
+ } else {
+ while (*c && *c != '>')
+ buf_append_byte(&ret, *c++);
+ if (*c)
+ buf_append(&ret, *c++);
+ }
}
- buf_concat(&ret, "</>");
- buf_concat(&ret, c);
+ if (offset)
+ *offset = c - orig;
return buf_final(&ret);
}
-DEF_CB(save_highlighted)
+DEF_CMD(get_offset)
{
- struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
- const char *start;
-
- if (!ci->str)
+ if (ci->num < 0)
return 1;
-
- start = strcasestr(ci->str, cb->prefix);
- if (!start)
- start = ci->str;
- cb->str = add_highlight_prefix(ci->str, start - ci->str, cb->plen, "<fg:red>");
- return 1;
+ else
+ return ci->num + 1;
}
DEF_CMD(render_complete_line)
{
struct complete_data *cd = ci->home->data;
- struct rlcb cb;
- int ret;
+ char *line, *l2, *start = NULL;
+ const char *hl;
+ const char *match;
+ int ret, startlen;
+ struct mark *m;
+ int offset = 0;
- if (!ci->mark)
+ if (!ci->mark || !cd->stk)
return Enoarg;
- cb.prefix = cd->stk->substr;
- cb.plen = strlen(cd->stk->substr);
- cb.str = NULL;
- cb.c = save_highlighted;
- ret = call_comm(ci->key, ci->home->parent, &cb.c, ci->num, ci->mark,
- NULL, 0, ci->mark2);
- if (ret < 0 || !cb.str)
- return ret;
-
- ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, cb.str);
- free((void*)cb.str);
+ m = mark_dup(ci->mark);
+ line = call_ret(str, ci->key, ci->home->parent, -1, m);
+ if (!line) {
+ mark_free(m);
+ return Efail;
+ }
+ match = cd->stk->substr;
+ l2 = strsave(ci->home, line);
+ if (l2){
+ strip_attrs(l2);
+ start = strcasestr(l2, match);
+ }
+ if (!start)
+ startlen = 0;
+ else
+ startlen = start - l2;
+ if (ci->num >= 0) {
+ /* Only want 'num' bytes from start, with ->mark positioned.
+ * So need to find how many bytes of 'line' produce num bytes
+ * of highlighted line.
+ */
+ int num = ci->num;
+ hl = add_highlight(line, startlen, strlen(match), "fg:red", &num, NULL);
+ if (hl != line)
+ free((void*)hl);
+ free(line);
+ mark_free(m);
+ line = call_ret(str, ci->key, ci->home->parent,
+ num, ci->mark);
+ } else if (ci->mark2) {
+ /* Only want up-to the cursor, which might be in the middle of
+ * the highlighted region. Now we know where that is, we can
+ * highlight whatever part is still visible.
+ */
+ mark_free(m);
+ offset = call_comm(ci->key, ci->home->parent, &get_offset,
+ ci->num, ci->mark, NULL,
+ 0, ci->mark2);
+ if (offset >= 1)
+ offset -= 1;
+ else
+ offset = -1;
+ } else {
+ mark_to_mark(ci->mark, m);
+ mark_free(m);
+ }
+ if (!line)
+ return Efail;
+ hl = add_highlight(line, startlen, strlen(match), "fg:red", NULL, &offset);
+
+ ret = comm_call(ci->comm2, "callback:render", ci->focus,
+ offset, NULL, hl);
+ if (hl != line)
+ free((void*)hl);
+ free(line);
return ret;
}
-DEF_CMD(complete_free)
+DEF_CMD_CLOSED(complete_close)
{
struct complete_data *cd = ci->home->data;
struct stk *stk = cd->stk;
free((void*)t->substr);
free(t);
}
+ cd->stk = NULL;
- unalloc(cd, pane);
+ free(cd->attr);
+ cd->attr = NULL;
return 1;
}
-
-static struct pane *complete_pane(struct pane *focus)
+static struct pane *complete_pane(struct pane *focus safe)
{
struct pane *complete;
struct complete_data *cd;
- alloc(cd, pane);
- complete = pane_register(focus, 0, &complete_handle.c, cd);
- if (!complete) {
- unalloc(cd, pane);
+ complete = pane_register(focus, 0, &complete_handle.c);
+ if (!complete)
return NULL;
- }
+ cd = complete->data;
cd->stk = malloc(sizeof(cd->stk[0]));
cd->stk->prev = NULL;
cd->stk->substr = strdup("");
{
struct complete_data *cd = ci->home->data;
char *np;
- int pl = strlen(cd->stk->substr);
+ int pl;
const char *suffix = ksuffix(ci, "doc:char-");
+ if (!cd->stk)
+ return Efail;
+ pl = strlen(cd->stk->substr);
np = malloc(pl + strlen(suffix) + 1);
strcpy(np, cd->stk->substr);
strcpy(np+pl, suffix);
- call("Complete:prefix", ci->focus, !cd->prefix_only, NULL, np);
+ call("Complete:prefix", ci->focus, !cd->prefix_only, NULL, np,
+ 0, NULL, cd->attr);
+ free(np);
return 1;
}
{
struct complete_data *cd = ci->home->data;
struct stk *stk = cd->stk;
+ char *old = NULL;
- if (!stk->prev)
+ if (!stk || !stk->prev)
return 1;
- cd->stk = stk->prev;
- free((void*)stk->substr);
- free(stk);
- call("Complete:prefix", ci->home);
+ if (stk->substr[0] && !stk->prev->substr[0]) {
+ old = (void*)stk->substr;
+ old[strlen(old)-1] = 0;
+ } else {
+ cd->stk = stk->prev;
+ free((void*)stk->substr);
+ free(stk);
+ }
+ call("Complete:prefix", ci->home, 0, NULL, NULL, 1,
+ NULL, cd->attr);
return 1;
}
cb->common = NULL;
free(cb->common_pre);
cb->common_pre = NULL;
+ cb->cnt = 0;
}
if (this_match == cb->best_match) {
cb->common_pre[match-c] = 0;
} else
adjust_pre(cb->common_pre, c, match-c);
+ cb->cnt += 1;
}
- cb->cnt += 1;
return 1;
}
* If there is no match, return -1.
* Otherwise return number of matches in ->num2 and
* the longest common prefix in ->str.
+ * If ci->num with ->str, allow substrings, else prefix-only
+ * if ci->num2, don't autocomplete, just display matches
*/
struct pane *p = ci->home;
struct complete_data *cd = p->data;
struct stk *stk;
struct mark *m;
+ if (!cd->stk)
+ return Efail;
/* Save a copy of the point so we can restore it if needed */
m = call_ret(mark, "doc:point", ci->focus);
if (m)
} else {
cb.ss = cd->stk->substr;
}
+ if (ci->str2 && (!cd->attr || strcmp(cd->attr, ci->str2) != 0)) {
+ free(cd->attr);
+ cd->attr = strdup(ci->str2);
+ }
call_comm("Filter:set", ci->focus, &cb.c,
- cd->prefix_only ? 3 : 2, NULL, cb.ss);
+ cd->prefix_only ? 3 : 2, NULL, cb.ss, 0, NULL, cd->attr);
if (cb.cnt <= 0) {
/* Revert */
call("Filter:set", ci->focus,
- cd->prefix_only ? 3 : 2, NULL, cd->stk->substr);
+ cd->prefix_only ? 3 : 2, NULL, cd->stk->substr,
+ 0, NULL, cd->attr);
if (m)
call("Move-to", ci->focus, 0, m);
}
mark_free(m);
if (cb.common_pre && cb.common && cb.cnt && ci->str) {
- strcat(cb.common_pre, cb.common);
+ if (ci->num2 == 0)
+ strcat(cb.common_pre, cb.common);
stk = malloc(sizeof(*stk));
stk->substr = cb.common_pre;
stk->prev = cd->stk;
cd->stk = stk;
cb.common_pre = NULL;
call("Filter:set", ci->focus,
- cd->prefix_only ? 3 : 2, NULL, cd->stk->substr);
+ cd->prefix_only ? 3 : 2, NULL, cd->stk->substr,
+ 0, NULL, cd->attr);
comm_call(ci->comm2, "callback:prefix", ci->focus, cb.cnt,
NULL, cd->stk->substr);
if (!cd->orig)
/* submit the selected entry to the popup */
struct call_return cr;
int l;
- char *c1, *c2;
if (!ci->mark)
return Enoarg;
cr.c = save_str;
cr.s = NULL;
+ /* Go to start of line */
+ home_call(ci->home, "doc:render-line-prev", ci->home, 0, ci->mark);
home_call(ci->home, "doc:render-line",
- ci->home, NO_NUMERIC, ci->mark, NULL, 0, NULL,
+ ci->home, -1, ci->mark, NULL, 0, NULL,
NULL, 0,0, &cr.c);
if (!cr.s)
return 1;
+ strip_attrs(cr.s);
l = strlen(cr.s);
if (l && cr.s[l-1] == '\n')
cr.s[l-1] = 0;
- c1 = c2 = cr.s;
- while (*c2) {
- if (*c2 != '<') {
- *c1++ = *c2++;
- continue;
- }
- c2 += 1;
- if (*c2 == '<') {
- *c1++ = *c2++;
- continue;
- }
- while (*c2 && c2[-1] != '>')
- c2++;
- }
- *c1 = 0;
call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
cr.s, 0);
rc_map = key_alloc();
key_add(rc_map, "doc:render-line", &render_complete_line);
- key_add(rc_map, "Free", &complete_free);
+ key_add(rc_map, "Close", &complete_close);
key_add(rc_map, "Clone", &complete_clone);
key_add(rc_map, "Replace", &complete_ignore_replace);