]> git.neil.brown.name Git - edlib.git/blobdiff - render-complete.c
TODO: clean out done items.
[edlib.git] / render-complete.c
index df1e5409d2badb2e6a7c0a034cb4f1f0c72b3df0..38a654b35d70ec3412c78c6f120d01fc4961a0c9 100644 (file)
@@ -1,14 +1,19 @@
 /*
- * Copyright Neil Brown ©2015-2021 <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;
 
@@ -40,73 +48,217 @@ struct rlcb {
        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;
@@ -117,22 +269,22 @@ DEF_CMD(complete_free)
                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("");
@@ -173,13 +325,18 @@ DEF_CMD(complete_char)
 {
        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;
 }
 
@@ -189,7 +346,7 @@ DEF_CMD(complete_bs)
        struct stk *stk = cd->stk;
        char *old = NULL;
 
-       if (!stk->prev)
+       if (!stk || !stk->prev)
                return 1;
        if (stk->substr[0] && !stk->prev->substr[0]) {
                old = (void*)stk->substr;
@@ -199,7 +356,8 @@ DEF_CMD(complete_bs)
                free((void*)stk->substr);
                free(stk);
        }
-       call("Complete:prefix", ci->home, 0, NULL, NULL, 1);
+       call("Complete:prefix", ci->home, 0, NULL, NULL, 1,
+            NULL, cd->attr);
        return 1;
 }
 
@@ -297,6 +455,7 @@ DEF_CB(set_cb)
                cb->common = NULL;
                free(cb->common_pre);
                cb->common_pre = NULL;
+               cb->cnt = 0;
        }
 
        if (this_match == cb->best_match) {
@@ -323,8 +482,8 @@ DEF_CB(set_cb)
                        cb->common_pre[match-c] = 0;
                } else
                        adjust_pre(cb->common_pre, c, match-c);
+               cb->cnt += 1;
        }
-       cb->cnt += 1;
        return 1;
 }
 
@@ -344,6 +503,8 @@ DEF_CMD(complete_set_prefix)
        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)
@@ -362,14 +523,19 @@ DEF_CMD(complete_set_prefix)
        } 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);
        }
@@ -384,7 +550,8 @@ DEF_CMD(complete_set_prefix)
                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)
@@ -416,36 +583,23 @@ DEF_CMD(complete_return)
        /* 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);
@@ -458,7 +612,7 @@ static void register_map(void)
        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);