]> git.neil.brown.name Git - edlib.git/commitdiff
Add filename completion in find-file.
authorNeilBrown <neil@brown.name>
Mon, 23 Nov 2015 07:28:06 +0000 (18:28 +1100)
committerNeilBrown <neil@brown.name>
Mon, 23 Nov 2015 07:28:06 +0000 (18:28 +1100)
A new renderer "render-complete" shows a select set of
lines from a given buffer and allows one to be selected.
Put this in a pop-up with the target directory and the
given prefix, and nice things happen.

Signed-off-by: NeilBrown <neil@brown.name>
Makefile
doc-dir.c
lib-popup.c
mode-emacs.c
render-complete.c [new file with mode: 0644]

index c98716296ad2d76a3ef52e16cac08f95c60a6859..8bd215dbba37427e99ec8e2d7bb5915dc862b8ac 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ LIBOBJ = O/core-mark.o O/core-doc.o O/core-editor.o O/core-attr.o \
        O/core-keymap.o O/core-pane.o O/core-misc.o
 SHOBJ = O/doc-text.o O/doc-dir.o \
        O/render-text.o O/render-hex.o O/render-dir.o O/render-lines.o \
-       O/render-format.o \
+       O/render-format.o O/render-complete.o \
        O/lib-view.o O/lib-tile.o O/lib-popup.o O/lib-line-count.o O/lib-keymap.o \
        O/lib-search.o \
        O/mode-emacs.o \
index 0f25ee786b5a4b691a12ea6b86834c9caab76af3..dbbadc3b3e5f6d2673f7fd9179527cb5c5c94c0e 100644 (file)
--- a/doc-dir.c
+++ b/doc-dir.c
@@ -451,6 +451,12 @@ static char *dir_get_attr(struct doc *d, struct mark *m,
                }
                *c = 0;
                return de->nbuf;
+       } else if (strcmp(attr, "suffix") == 0) {
+               get_stat(dr, de);
+               if ((de->st.st_mode & S_IFMT) == S_IFDIR)
+                       return "/";
+               else
+                       return "";
        } else
                return attr_get_str(de->attrs, attr, -1);
 }
index a131beae4031cdab981933368ae72d40e739aab9..6b5fc5b721c1689a5f800da3df76236297f13ae8 100644 (file)
@@ -176,7 +176,7 @@ DEF_CMD(popup_attach)
                ppi->doc = pt->doc;
        }
        p = pane_attach(ppi->popup, "view", pt, NULL);
-       render_attach("lines", p);
+       render_attach(NULL, p);
        pane_focus(p);
        ci2.key = "local-set-key";
        ci2.focus = p;
index 6c88175cec701cd11081cca4b5eaf724ce7fe493..2d752ac982a77b0779868193c6fb2f323435e219 100644 (file)
@@ -296,12 +296,17 @@ DEF_CMD(emacs_findfile)
                }
                doc_set_name(d, "Find File");
                if (path) {
-                       struct cmd_info ci2 = {0};
                        ci2.key = "Replace";
                        ci2.focus = p;
                        ci2.str = path;
                        key_handle_focus(&ci2);
                }
+               memset(&ci2, 0, sizeof(ci2));
+               ci2.key = "local-set-key";
+               ci2.focus = p;
+               ci2.str = "emacs:file-complete";
+               ci2.str2 = "Tab";
+               key_handle_focus(&ci2);
                return 1;
        }
 
@@ -332,6 +337,76 @@ DEF_CMD(emacs_findfile)
        return 1;
 }
 
+DEF_CMD(emacs_file_complete)
+{
+       /* Extract a directory name and a basename from the document.
+        * Find a document for the directory and attach as a completing
+        * popup menu
+        */
+       struct doc *doc = (*ci->pointp)->doc;
+       char *str = doc_getstr(doc, NULL, NULL);
+       char *d, *b, *c;
+       int fd;
+       struct pane *par, *pop;
+       struct cmd_info ci2 = {0};
+       struct point *pt, **ptp;
+
+       d = str;
+       while ((c = strstr(d, "//")) != NULL)
+               d = c+1;
+       b = strrchr(d, '/');
+       if (b) {
+               b += 1;
+               d = strndup(d, b-d);
+       } else {
+               b = d;
+               d = strdup(".");
+       }
+       fd = open(d, O_DIRECTORY|O_RDONLY);
+       if (fd < 0) {
+               free(d);
+               free(str);
+               return -1;
+       }
+       pt = (struct point *)doc_open(doc->ed, NULL, fd, d, NULL);
+       close(fd);
+       pop = pane_attach(ci->focus, "popup", pt, "DM1");
+       ptp = pane_point(pane_final_child(pop));
+       /* Want to work with the document pane */
+       par = container_of(ptp, struct pane, point);
+
+       attr_set_str(&par->attrs, "line-format", "%+name%suffix", -1);
+       attr_set_str(&par->attrs, "heading", "", -1);
+       attr_set_str(&par->attrs, "done-key", "Replace", -1);
+       render_attach("complete", par);
+       ci2.key = "Complete:prefix";
+       ci2.str = b;
+       ci2.focus = par;
+       key_handle_focus(&ci2);
+       free(d);
+       if (ci2.str && (strlen(ci2.str) <= strlen(b) && ci2.extra > 1)) {
+               /* We need the dropdown */
+               pane_damaged(par, DAMAGED_CONTENT);
+               free(str);
+               return 1;
+       }
+       if (ci2.str) {
+               /* add the extra chars from ci2.str */
+               char *c = ci2.str + strlen(b);
+               struct cmd_info ci3 = {0};
+               ci3.key = "Replace";
+               ci3.pointp = ci->pointp;
+               ci3.mark = mark_of_point(*ci->pointp);
+               ci3.numeric = 1;
+               ci3.focus = ci->focus;
+               ci3.str = c;
+               key_handle_focus(&ci3);
+       }
+       /* Now need to close the popup */
+       pane_close(pop);
+       return 1;
+}
+
 DEF_CMD(emacs_finddoc)
 {
        struct pane *p, *par;
@@ -552,5 +627,6 @@ void edlib_init(struct editor *ed)
        if (emacs_map == NULL)
                emacs_init();
        key_add(ed->commands, "mode-emacs", &mode_emacs);
+       key_add(ed->commands, "emacs:file-complete", &emacs_file_complete);
        emacs_search_init(ed);
 }
diff --git a/render-complete.c b/render-complete.c
new file mode 100644 (file)
index 0000000..91ec6ec
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * Copyright Neil Brown <neil@brown.name> 2015
+ * 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)
+ *
+ * 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 "core.h"
+
+struct complete_data {
+       char *prefix;
+};
+
+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;
+       struct doc *d = (*ci->pointp)->doc;
+       int plen;
+
+       ci2.key = ci->key;
+       ci2.mark = ci->mark;
+       ci2.pointp = ci->pointp;
+       ci2.focus = ci->home->parent;
+       ci2.numeric = ci->numeric;
+       if (key_handle(&ci2) == 0)
+               return 0;
+       ci->str = ci2.str;
+       if (ci->numeric != NO_NUMERIC)
+               return 1;
+       /* Need to continue over other matching lines */
+       plen = strlen(cd->prefix);
+       ci2.mark = mark_dup(ci->mark, 1);
+       while (1) {
+               ci2.numeric = ci->numeric;
+               ci2.focus = ci->home->parent;
+               key_handle(&ci2);
+               if (ci2.str == NULL ||
+                   strncmp(ci2.str, cd->prefix, plen) == 0)
+                       break;
+               /* have a match, so move the mark to here. */
+               mark_to_mark(d, ci->mark, ci2.mark);
+       }
+       mark_free(ci2.mark);
+       return 1;
+}
+
+DEF_CMD(render_complete_prev)
+{
+       /* 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 complete_data *cd = ci->home->data;
+       struct doc *d = (*ci->pointp)->doc;
+       int plen;
+       int ret;
+
+       ci2.key = ci->key;
+       ci2.mark = ci->mark;
+       ci2.pointp = ci->pointp;
+       ci2.focus = ci->home->parent;
+       ci2.numeric = 0;
+
+       ci3.key= "render-line";
+       ci3.focus = ci->home->parent;
+       ci3.pointp = ci->pointp;
+       while (1) {
+               int cmp;
+
+               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);
+               plen = strlen(cd->prefix);
+               ci3.mark = mark_dup(ci2.mark, 1);
+               ci3.numeric = NO_NUMERIC;
+               if (key_handle(&ci3) == 0) {
+                       mark_free(ci3.mark);
+                       break;
+               }
+               mark_free(ci3.mark);
+               cmp = strncmp(ci3.str, cd->prefix, plen);
+               if (cmp != 0 || ci2.numeric != 1 || ci->extra != 42)
+                       free(ci3.str);
+               else
+                       ci->str2 = ci3.str;
+               if (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(d, ci->mark, ci2.mark);
+               mark_free(ci2.mark);
+       }
+       return ret;
+}
+
+DEF_CMD(complete_close)
+{
+       struct pane *p = ci->home;
+       struct complete_data *cd = p->data;
+       free(cd->prefix);
+       free(cd);
+       return 1;
+}
+
+DEF_CMD(complete_attach);
+DEF_CMD(complete_clone)
+{
+       struct pane *parent = ci->focus;
+       struct pane *p = ci->home, *c;
+
+       ci->pointp = pane_point(parent);
+       complete_attach.func(ci);
+       c = pane_child(p);
+       if (c)
+               return pane_clone(c, parent->focus);
+       return 1;
+}
+
+DEF_CMD(complete_nomove)
+{
+       return 1;
+}
+
+DEF_CMD(complete_eol)
+{
+       int rpt = RPT_NUM(ci);
+
+       if (rpt >= -1 && rpt <= 1)
+               /* movement with the line */
+               return 1;
+       while (rpt < -1) {
+               struct cmd_info ci2 = {0};
+               ci2.key = "render-line-prev";
+               ci2.numeric = 1;
+               ci2.mark = ci->mark;
+               ci2.pointp = ci->pointp;
+               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};
+               ci2.key = "render-line";
+               ci2.numeric = NO_NUMERIC;
+               ci2.mark = ci->mark;
+               ci2.pointp = ci->pointp;
+               ci2.focus = ci->focus;
+               ci2.home = ci->home;
+               if (render_complete_line_func(&ci2) < 0)
+                       rpt = 1;
+               else
+                       free(ci2.str);
+               rpt -= 1;
+       }
+       return 1;
+}
+
+static int common_len(char *a, char *b)
+{
+       int len = 0;
+       while (*a && *a == *b) {
+               a += 1;
+               b += 1;
+               len += 1;
+       }
+       return len;
+}
+
+DEF_CMD(complete_set_prefix)
+{
+       /* Set the prefix, force a full refresh, and move point
+        * to the first match.
+        * If there is no match, return -1.
+        * Otherwise return number of matches in ->extra and
+        * the longest common prefix in ->str.
+        */
+       struct pane *p = ci->home;
+       struct complete_data *cd = p->data;
+       struct cmd_info ci2 = {0};
+       struct point **ptp = ci->pointp;
+       struct doc *d = (*ptp)->doc;
+       struct mark *m = mark_at_point(*ptp, MARK_UNGROUPED);
+       int cnt = 0;
+       char *common = NULL;
+
+       free(cd->prefix);
+       cd->prefix = strdup(ci->str);
+
+       while (mark_next(d, m) != WEOF)
+               ;
+       ci2.key = "render-line-prev";
+       ci2.numeric = 1;
+       ci2.mark = m;
+       ci2.pointp = ptp;
+       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;
+       }
+       ci->extra = cnt;
+       ci->str = common;
+       point_to_mark(*ptp, m);
+       mark_free(m);
+       memset(&ci2, 0, sizeof(ci2));
+       ci2.key = "render-lines:redraw";
+       ci2.focus = ci->focus;
+       key_handle_focus(&ci2);
+       return 1;
+}
+
+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};
+       char *str;
+       int l;
+
+       ci2.key = "render-line";
+       ci2.focus = ci->home;
+       ci2.home = ci->home;
+       ci2.mark = mark_of_point(*ci->pointp);
+       ci2.pointp = ci->pointp;
+       ci2.numeric = NO_NUMERIC;
+       render_complete_line_func(&ci2);
+       str = ci2.str;
+       l = strlen(str);
+       if (l && str[l-1] == '\n')
+               str[l-1] = 0;
+
+       memset(&ci2, 0, sizeof(ci2));
+       ci2.key = ci->key;
+       ci2.focus = ci->home->parent;
+       ci2.str = str + strlen(cd->prefix);
+       ci2.numeric = NO_NUMERIC;
+       key_handle(&ci2);
+       free(str);
+       return 1;
+}
+
+static struct map *rc_map;
+
+DEF_CMD(complete_handle)
+{
+       return key_lookup(rc_map, ci);
+}
+
+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, "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, "popup:Return", &complete_return);
+
+       key_add(rc_map, "Complete:prefix", &complete_set_prefix);
+}
+
+REDEF_CMD(complete_attach)
+{
+       struct pane *complete;
+       struct pane *lines;
+       struct pane *parent;
+       struct complete_data *cd;
+       struct cmd_info ci2 = {0};
+
+       /* Need to interpose a new pane between the 'render-lines' pane,
+        * which we assume is 'ci->focus' and its parent, so we can
+        * re-interpret lines.
+        * Find the 'render-line-prev' pane by sending a render-lines request
+        * and grabbing 'home'
+        */
+       ci2.key = "render-line-prev";
+       ci2.pointp = ci->pointp;
+       ci2.numeric = 0;
+       ci2.mark = mark_at_point(*ci->pointp, MARK_UNGROUPED);
+       ci2.focus = ci->focus;
+       key_handle_focus(&ci2);
+       parent = ci2.home;
+       lines = pane_child(parent);
+       mark_free(ci2.mark);
+
+
+       cd = calloc(1, sizeof(*cd));
+       complete = pane_register(parent, 0, &complete_handle, cd, NULL);
+       if (!complete) {
+               free(cd);
+               return -1;
+       }
+       pane_reparent(lines, complete);
+       pane_check_size(complete);
+       cd->prefix = strdup("");
+
+       if (!rc_map)
+               register_map();
+       ci->focus = complete;
+       return 1;
+}
+
+void edlib_init(struct editor *ed)
+{
+       key_add(ed->commands, "render-complete-attach", &complete_attach);
+}