]> git.neil.brown.name Git - edlib.git/blobdiff - render-complete.c
TODO: clean out done items.
[edlib.git] / render-complete.c
index 9bc994afca44586e1280e2be803a7ddeb48a1677..38a654b35d70ec3412c78c6f120d01fc4961a0c9 100644 (file)
 /*
- * Copyright Neil Brown ©2015 <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 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.
  */
 
+#define _GNU_SOURCE for strcasestr
 #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 *prefix;
+       char *orig;
+       char *attr;
+       struct stk {
+               struct stk *prev;
+               const char *substr safe;
+       } *stk;
+       int prefix_only;
 };
+#include "core-pane.h"
+
+static struct map *rc_map;
+
+DEF_LOOKUP_CMD(complete_handle, rc_map);
 
 struct rlcb {
        struct command c;
-       int keep, plen, cmp;
-       char *prefix, *str;
+       int plen;
+       const char *prefix safe, *str;
 };
 
-static char *add_highlight_prefix(char *orig, int plen, char *attr)
+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);
-       buf_concat(&ret, attr);
-       while (plen > 0 && *orig) {
-               if (*orig == '<')
-                       buf_append_byte(&ret, *orig++);
-               buf_append_byte(&ret, *orig++);
-               plen -= 1;
+       c = orig;
+       if (*c == ack) {
+               use_lt = False;
+               buf_append_byte(&ret, ack);
+               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++);
+                       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, orig);
+       if (offset)
+               *offset = c - orig;
        return buf_final(&ret);
 }
 
-DEF_CMD(save_highlighted)
-{
-       struct call_return *cr = container_of(ci->comm, struct call_return, c);
-       cr->s = add_highlight_prefix(ci->str, cr->i, "<fg:red>");
-       return 1;
-}
-
-DEF_CMD(rcl_cb)
+DEF_CMD(get_offset)
 {
-       struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
-       if (ci->str == NULL)
-               cb->cmp = 0;
+       if (ci->num < 0)
+               return 1;
        else
-               cb->cmp = strncmp(ci->str, cb->prefix, cb->plen);
-       return 1;
+               return ci->num + 1;
 }
+
 DEF_CMD(render_complete_line)
 {
-       /* The first line *must* match the prefix.
-        * skip over any following lines that don't
-        */
-       struct cmd_info ci2 = {0};
        struct complete_data *cd = ci->home->data;
-       int plen = strlen(cd->prefix);
-       struct call_return cr;
-       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)
-               return -1;
-
-       ci2.key = ci->key;
-       ci2.mark = ci->mark;
-       ci2.mark2 = ci->mark2;
-       ci2.focus = ci->home->parent;
-       ci2.numeric = ci->numeric;
-       cr.c = save_highlighted;
-       cr.i = plen;
-       cr.s = NULL;
-       ci2.comm2 = &cr.c;
-       if (key_handle(&ci2) == 0)
-               return 0;
-
-       ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL, cr.s, 0);
-       if (ci->numeric != NO_NUMERIC)
-               return ret;
-       /* Need to continue over other matching lines */
-       ci2.mark = mark_dup(ci->mark, 1);
-       while (1) {
-               ci2.numeric = ci->numeric;
-               ci2.focus = ci->home->parent;
-               cb.c = rcl_cb;
-               cb.plen = plen;
-               cb.prefix = cd->prefix;
-               cb.cmp = 0;
-               ci2.comm2 = &cb.c;
-               key_handle(&ci2);
-               if (cb.cmp == 0)
-                       break;
-
-               /* have a non-match, so move the mark over it. */
-               mark_to_mark(ci->mark, ci2.mark);
+       if (!ci->mark || !cd->stk)
+               return Enoarg;
+
+       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);
        }
-       mark_free(ci2.mark);
+       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(rlcb)
-{
-       struct rlcb *cb = container_of(ci->comm, struct rlcb, c);
-       if (ci->str == NULL)
-               cb->cmp = -1;
-       else
-               cb->cmp = strncmp(ci->str, cb->prefix, cb->plen);
-       if (cb->cmp == 0 && cb->keep && ci->str)
-               cb->str = strdup(ci->str);
-       return 1;
-}
-DEF_CMD(render_complete_prev)
+DEF_CMD_CLOSED(complete_close)
 {
-       /* If ->numeric is 0 we just need 'start of line' so use
-        * underlying function.
-        * otherwise call repeatedly and then render the line and see if
-        * it matches the prefix.
-        */
-       struct cmd_info ci2 = {0}, ci3 = {0};
-       struct rlcb cb;
        struct complete_data *cd = ci->home->data;
-       int ret;
-
-       ci2.key = ci->key;
-       ci2.mark = ci->mark;
-       ci2.focus = ci->home->parent;
-       ci2.numeric = 0;
-
-       ci3.key = "render-line";
-       ci3.focus = ci->home->parent;
-       cb.c = rlcb;
-       cb.str = NULL;
-       cb.prefix = cd->prefix;
-       cb.plen = strlen(cb.prefix);
-       cb.cmp = 0;
-       ci3.comm2 = &cb.c;
-       while (1) {
-               ret = key_handle(&ci2);
-               if (ret <= 0 || ci->numeric == 0)
-                       /* Either hit start-of-file, or have what we need */
-                       break;
-               /* we must be looking at a possible option for the previous
-                * line
-                */
-               if (ci2.mark == ci->mark)
-                       ci2.mark = mark_dup(ci->mark, 1);
-               ci3.mark = mark_dup(ci2.mark, 1);
-               ci3.numeric = NO_NUMERIC;
-               cb.keep = ci2.numeric == 1 && ci->extra == 42;
-               cb.str = NULL;
-               if (key_handle(&ci3) != 1) {
-                       mark_free(ci3.mark);
-                       break;
-               }
-               mark_free(ci3.mark);
-               /* This is a horrible hack, but as it is entirely internal
-                * to this module it can say for now.
-                * Cast ci to remove any 'const' tag that I hope to add soon.
-                */
-               ((struct cmd_info*)ci)->str2 = cb.str;
+       struct stk *stk = cd->stk;
 
-               if (cb.cmp == 0 && ci2.numeric == 1)
-                       /* This is a valid new start-of-line */
-                       break;
-               /* step back once more */
-               ci2.numeric = 1;
-       }
-       if (ci2.mark != ci->mark) {
-               if (ret > 0)
-                       /* move ci->mark back to ci2.mark */
-                       mark_to_mark(ci->mark, ci2.mark);
-               mark_free(ci2.mark);
+       while (stk) {
+               struct stk *t = stk;
+               stk = stk->prev;
+               free((void*)t->substr);
+               free(t);
        }
-       return ret;
+       cd->stk = NULL;
+
+       free(cd->attr);
+       cd->attr = NULL;
+       return 1;
 }
 
-DEF_CMD(complete_close)
+static struct pane *complete_pane(struct pane *focus safe)
 {
-       struct pane *p = ci->home;
-       struct complete_data *cd = p->data;
-       free(cd->prefix);
-       free(cd);
-       return 1;
+       struct pane *complete;
+       struct complete_data *cd;
+
+       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("");
+       cd->prefix_only = 1;
+       return complete;
 }
 
-DEF_CMD(complete_attach);
 DEF_CMD(complete_clone)
 {
        struct pane *parent = ci->focus;
-       struct pane *p = ci->home, *c;
+       struct pane *complete;
+
+       complete = complete_pane(parent);
+       if (complete)
+               pane_clone_children(ci->home, complete);
+       return 1;
+}
 
-       complete_attach.func(ci);
-       c = pane_child(p);
-       if (c)
-               return pane_clone(c, parent->focus);
+DEF_CMD(complete_ignore_replace)
+{
        return 1;
 }
 
-DEF_CMD(complete_nomove)
+DEF_CMD(complete_escape)
 {
-       if (strcmp(ci->key, "Move-File") == 0)
-               return 0;
-       if (strcmp(ci->key, "Move-to") == 0)
-               return 0;
-       if (strcmp(ci->key, "Move-Line") == 0)
-               return 0;
+       /* submit the original prefix back*/
+       struct complete_data *cd = ci->home->data;
+
+       /* This pane might be closed before the reply string is used,
+        * so we need to save it.
+        */
+       call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
+            strsave(ci->home, cd->orig));
        return 1;
 }
 
-DEF_CMD(eol_cb)
+DEF_CMD(complete_char)
 {
-       /* don't save anything */
+       struct complete_data *cd = ci->home->data;
+       char *np;
+       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,
+            0, NULL, cd->attr);
+       free(np);
        return 1;
 }
 
-DEF_CMD(complete_eol)
+DEF_CMD(complete_bs)
 {
-       int rpt = RPT_NUM(ci);
+       struct complete_data *cd = ci->home->data;
+       struct stk *stk = cd->stk;
+       char *old = NULL;
 
-       if (rpt >= -1 && rpt <= 1)
-               /* movement within the line */
+       if (!stk || !stk->prev)
                return 1;
-       while (rpt < -1) {
-               struct cmd_info ci2 = {0};
-               ci2.key = "render-line-prev";
-               ci2.numeric = 1;
-               ci2.mark = ci->mark;
-               ci2.focus = ci->focus;
-               ci2.home = ci->home;
-               if (render_complete_prev_func(&ci2) < 0)
-                       rpt = -1;
-               rpt += 1;
-       }
-       while (rpt > 1) {
-               struct cmd_info ci2 = {0};
-               struct call_return cr;
-               ci2.key = "render-line";
-               ci2.numeric = NO_NUMERIC;
-               ci2.mark = ci->mark;
-               ci2.focus = ci->focus;
-               ci2.home = ci->home;
-               cr.c = eol_cb;
-               ci2.comm2 = &cr.c;
-               if (render_complete_line_func(&ci2) <= 0)
-                       rpt = 1;
-               rpt -= 1;
+       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;
 }
 
-static int common_len(char *a, char *b)
+static int csame(char a, char b)
+{
+       if (isupper(a))
+               a = tolower(a);
+       if (isupper(b))
+               b = tolower(b);
+       return a == b;
+}
+
+static int common_len(const char *a safe, const char *b safe)
 {
        int len = 0;
-       while (*a && *a == *b) {
+       while (*a && csame(*a, *b)) {
                a += 1;
                b += 1;
                len += 1;
@@ -276,53 +381,197 @@ static int common_len(char *a, char *b)
        return len;
 }
 
+static void adjust_pre(char *common safe, const char *new safe, int len)
+{
+       int l = strlen(common);
+       int newlen = 0;
+
+       while (l && len && csame(common[l-1], new[len-1])) {
+               l -= 1;
+               len -= 1;
+               newlen += 1;
+       }
+       if (l)
+               memmove(common, common+l, newlen+1);
+}
+
+struct setcb {
+       struct command c;
+       struct complete_data *cd safe;
+       const char *ss safe;
+       int best_match;
+       char *common;
+       /* common_pre is the longest common prefix to 'common' that
+        * appears in all matches in which 'common' appears.  It is
+        * allocated with enough space to append 'common' after the
+        * prefix.
+        */
+       char *common_pre;
+       struct mark *bestm;
+       int cnt;
+};
+
+DEF_CB(set_cb)
+{
+       struct setcb *cb = container_of(ci->comm, struct setcb, c);
+       struct complete_data *cd = cb->cd;
+       const char *ss = cb->ss;
+       int len = strlen(ss);
+       const char *c = ci->str;
+       const char *match;
+       int this_match = 0;
+       int l;
+
+       if (!c)
+               return Enoarg;
+       if (cd->prefix_only) {
+               match = c;
+               if (strncmp(match, ss, len) == 0)
+                       this_match += 1;
+       } else {
+               match = strcasestr(c, ss);
+               if (strncasecmp(c, ss, len) == 0) {
+                       this_match += 1;
+                       if (strncmp(c, ss, len) == 0)
+                               this_match += 1;
+               } else if (strstr(c, ss))
+                       this_match += 1;
+       }
+
+       if (!match)
+               /* should be impossible */
+               return 1;
+
+       l = strlen(match);
+       if (l && match[l-1] == '\n')
+               l -= 1;
+
+       if (this_match > cb->best_match) {
+               /* Only use matches at least this good to calculate
+                * 'common'
+                */
+               cb->best_match = this_match;
+               free(cb->common);
+               cb->common = NULL;
+               free(cb->common_pre);
+               cb->common_pre = NULL;
+               cb->cnt = 0;
+       }
+
+       if (this_match == cb->best_match) {
+               /* This match can be used for 'common' and
+                * initial cursor
+                */
+               mark_free(cb->bestm);
+               if (ci->mark)
+                       cb->bestm = mark_dup(ci->mark);
+
+               if (!cb->common) {
+                       cb->common = strndup(match, l);
+               } else {
+                       cb->common[common_len(match, cb->common)] = 0;
+                       /* If 'match' and 'common' disagree on case of
+                        * 'prefix', use that of 'prefix'
+                        */
+                       if (memcmp(cb->common, match, len) != 0)
+                               memcpy(cb->common, ss, len);
+               }
+               if (!cb->common_pre) {
+                       cb->common_pre = strndup(c, l + match-c);
+                       strncpy(cb->common_pre, c, match-c);
+                       cb->common_pre[match-c] = 0;
+               } else
+                       adjust_pre(cb->common_pre, c, match-c);
+               cb->cnt += 1;
+       }
+       return 1;
+}
+
 DEF_CMD(complete_set_prefix)
 {
        /* Set the prefix, force a full refresh, and move point
-        * to the first match.
+        * to the first match at start-of-line, or first match
         * If there is no match, return -1.
-        * Otherwise return number of matches in ->extra and
+        * 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 cmd_info ci2 = {0};
+       struct setcb cb;
+       struct stk *stk;
        struct mark *m;
-       int cnt = 0;
-       char *common = NULL;
-
-       free(cd->prefix);
-       cd->prefix = strdup(ci->str);
-
-       m = mark_at_point(ci->focus, NULL, MARK_UNGROUPED);
-       call3("Move-File", ci->focus, 1, m);
-
-       ci2.key = "render-line-prev";
-       ci2.numeric = 1;
-       ci2.mark = m;
-       ci2.focus = p;
-       ci2.home = p;
-       ci2.extra = 42; /* request copy of line in str2 */
-       while (render_complete_prev_func(&ci2) > 0) {
-               char *c = ci2.str2;
-               int l = strlen(c);
-               if (c[l-1] == '\n')
-                       l -= 1;
-               if (common == NULL)
-                       common = strndup(c, l);
-               else
-                       common[common_len(c, common)] = 0;
-               cnt += 1;
+
+       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)
+               m = mark_dup(m);
+
+       cb.c = set_cb;
+       cb.cd = cd;
+       cb.best_match = 0;
+       cb.common = NULL;
+       cb.common_pre = NULL;
+       cb.bestm = NULL;
+       cb.cnt = 0;
+       if (ci->str) {
+               cb.ss = ci->str;
+               cd->prefix_only = !ci->num;
+       } 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, 0, NULL, cd->attr);
+
+       if (cb.cnt <= 0) {
+               /* Revert */
+               call("Filter:set", ci->focus,
+                    cd->prefix_only ? 3 : 2, NULL, cd->stk->substr,
+                    0, NULL, cd->attr);
+               if (m)
+                       call("Move-to", ci->focus, 0, m);
        }
-       comm_call(ci->comm2, "callback:prefix", ci->focus, 0, NULL, common, 0);
-       free(common);
-       call3("Move-to", ci->focus, 0, m);
        mark_free(m);
-       call3("render-lines:redraw", ci->focus, 0, NULL);
-       return cnt + 1;
+
+       if (cb.common_pre && cb.common && cb.cnt && ci->str) {
+               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,
+                         0, NULL, cd->attr);
+               comm_call(ci->comm2, "callback:prefix", ci->focus, cb.cnt,
+                         NULL, cd->stk->substr);
+               if (!cd->orig)
+                       cd->orig = strdup(ci->str);
+       } else {
+               comm_call(ci->comm2, "callback:prefix", ci->focus, 0);
+       }
+       free(cb.common);
+       free(cb.common_pre);
+       if (cb.bestm) {
+               call("Move-to", ci->focus, 0, cb.bestm);
+               mark_free(cb.bestm);
+       }
+
+       call("view:changed", ci->focus);
+
+       return cb.cnt + 1;
 }
 
-DEF_CMD(save_str)
+DEF_CB(save_str)
 {
        struct call_return *cr = container_of(ci->comm, struct call_return, c);
        cr->s = ci->str ? strdup(ci->str) : NULL;
@@ -332,95 +581,72 @@ DEF_CMD(save_str)
 DEF_CMD(complete_return)
 {
        /* submit the selected entry to the popup */
-       struct pane *p = ci->home;
-       struct complete_data *cd = p->data;
-       struct cmd_info ci2 = {0};
        struct call_return cr;
        int l;
-       char *c1, *c2;
 
-       ci2.key = "render-line";
-       ci2.focus = ci->home;
-       ci2.home = ci->home;
-       ci2.mark = ci->mark;
-       ci2.numeric = NO_NUMERIC;
+       if (!ci->mark)
+               return Enoarg;
+
        cr.c = save_str;
        cr.s = NULL;
-       ci2.comm2 = &cr.c;
-       render_complete_line_func(&ci2);
+       /* 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, -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;
-
-       memset(&ci2, 0, sizeof(ci2));
-       ci2.key = ci->key;
-       ci2.focus = ci->home->parent;
-       ci2.str = cr.s + strlen(cd->prefix);
-       ci2.numeric = NO_NUMERIC;
-       key_handle(&ci2);
+
+       call("popup:close", ci->home->parent, NO_NUMERIC, NULL,
+            cr.s, 0);
        free(cr.s);
        return 1;
 }
 
-static struct map *rc_map;
-
-DEF_LOOKUP_CMD(complete_handle, rc_map)
-
 static void register_map(void)
 {
        rc_map = key_alloc();
 
-       key_add(rc_map, "render-line", &render_complete_line);
-       key_add(rc_map, "render-line-prev", &render_complete_prev);
+       key_add(rc_map, "doc:render-line", &render_complete_line);
        key_add(rc_map, "Close", &complete_close);
        key_add(rc_map, "Clone", &complete_clone);
 
-       key_add_range(rc_map, "Move-", "Move-\377", &complete_nomove);
-       key_add(rc_map, "Move-EOL", &complete_eol);
+       key_add(rc_map, "Replace", &complete_ignore_replace);
+       key_add(rc_map, "K:ESC", &complete_escape);
+       key_add_range(rc_map, "doc:char- ", "doc:char-~", &complete_char);
+       key_add(rc_map, "K:Backspace", &complete_bs);
 
-       key_add(rc_map, "popup:Return", &complete_return);
+       key_add(rc_map, "K:Enter", &complete_return);
 
        key_add(rc_map, "Complete:prefix", &complete_set_prefix);
 }
 
-REDEF_CMD(complete_attach)
+DEF_CMD(complete_attach)
 {
+       struct pane *p = ci->focus;
        struct pane *complete;
-       struct complete_data *cd;
 
        if (!rc_map)
                register_map();
 
-       cd = calloc(1, sizeof(*cd));
-       complete = pane_register(ci->focus, 0, &complete_handle.c, cd, NULL);
+       p = call_ret(pane, "attach-linefilter", p);
+       if (!p)
+               return Efail;
+       complete = complete_pane(p);
        if (!complete) {
-               free(cd);
-               return -1;
+               pane_close(p);
+               return Efail;
        }
-       pane_check_size(complete);
-       cd->prefix = strdup("");
 
-       return comm_call(ci->comm2, "callback:attach", complete, 0, NULL, NULL, 0);
+       return comm_call(ci->comm2, "callback:attach", complete);
 }
 
-void edlib_init(struct editor *ed)
+void edlib_init(struct pane *ed safe)
 {
-       key_add(ed->commands, "render-complete-attach", &complete_attach);
+       call_comm("global-set-command", ed, &complete_attach,
+                 0, NULL, "attach-render-complete");
 }