We add some infrastructure in order to run a shell command and get its
output which we then can insert into vis.

This infrastructure we use to execute a shell command which sends all
unique words of the current file to dmenu. The word selected in dmenu
is then inserted into vis at all cursor positions.
---
 config.def.h |   1 +
 main.c       | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vis.c        |   6 +++-
 vis.h        |   1 +
 4 files changed, 121 insertions(+), 1 deletion(-)

diff --git a/config.def.h b/config.def.h
index 8699136..9e52102 100644
--- a/config.def.h
+++ b/config.def.h
@@ -319,6 +319,7 @@ static const KeyBinding bindings_insert[] = {
        { "<C-d>",              ALIAS("<Escape><<i")                        },
        { "<C-i>",              ALIAS("<Tab>")                              },
        { "<C-j>",              ALIAS("<Enter>")                            },
+       { "<C-n>",              ACTION(FILE_TEXT_AUTOCOMPLETE)              },
        { "<C-m>",              ALIAS("<Enter>")                            },
        { "<C-o>",              ACTION(MODE_OPERATOR_PENDING)               },
        { "<C-r>",              ACTION(INSERT_REGISTER)                     },
diff --git a/main.c b/main.c
index d0c0c73..9e224d1 100644
--- a/main.c
+++ b/main.c
@@ -125,6 +125,12 @@ static const char *percent(Vis*, const char *keys, const 
Arg *arg);
 static const char *number_increment_decrement(Vis*, const char *keys, const 
Arg *arg);
 /* open a filename under cursor in same (!arg->b) or new (arg->b) window */
 static const char *open_file_under_cursor(Vis*, const char *keys, const Arg 
*arg);
+/* Insert text chosen in external file dialog at cursor position(s) */
+static void insert_dialog_selection(Vis*, const char *cmdline, ...);
+/* Get output of external command */
+static char *get_output_of_external_command(Vis*, const char *argv[]);
+/* Autocomplete input text at cursor based on the words in the current file */
+static const char *autocomplete_file_text(Vis*, const char *keys, const Arg 
*arg);
 
 enum {
        VIS_ACTION_EDITOR_SUSPEND,
@@ -307,6 +313,7 @@ enum {
        VIS_ACTION_NUMBER_DECREMENT,
        VIS_ACTION_OPEN_FILE_UNDER_CURSOR,
        VIS_ACTION_OPEN_FILE_UNDER_CURSOR_NEW_WINDOW,
+       VIS_ACTION_FILE_TEXT_AUTOCOMPLETE,
        VIS_ACTION_NOP,
 };
 
@@ -1211,6 +1218,11 @@ static const KeyAction vis_action[] = {
                "Open file under the cursor in a new window",
                open_file_under_cursor, { .b = true }
        },
+       [VIS_ACTION_FILE_TEXT_AUTOCOMPLETE] = {
+               "autocomplete-file-text",
+               "Autocomplete text in file",
+               autocomplete_file_text,
+       },
        [VIS_ACTION_NOP] = {
                "nop",
                "Ignore key, do nothing",
@@ -2093,6 +2105,108 @@ static const char *open_file_under_cursor(Vis *vis, 
const char *keys, const Arg
        return keys;
 }
 
+ssize_t read_buffer(void *context, char *data, size_t len) {
+       buffer_append(context, data, len);
+       return len;
+}
+
+static char *get_output_of_external_command(Vis *vis, const char *argv[]) {
+       char *out = NULL;
+       Buffer bufout, buferr;
+       buffer_init(&bufout);
+       buffer_init(&buferr);
+
+       Filerange empty = text_range_empty();
+       int status = vis_pipe(vis, &empty, argv, &bufout, read_buffer,
+                       &buferr, read_buffer);
+
+       if (status != 0) {
+               vis_info_show(vis, "Command failed %s", 
buffer_content0(&buferr));
+       } else {
+               out = malloc(bufout.len);
+               strncpy(out, buffer_content0(&bufout), buffer_length0(&bufout));
+               out[buffer_length0(&bufout)] = '\0';
+       }
+
+       buffer_release(&bufout);
+       buffer_release(&buferr);
+       return out;
+}
+
+// Caller has to free the allocated memory for the prefix
+static char *get_prefix_for_autocomplete(Vis *vis) {
+       View *view = vis_view(vis);
+       Cursor *c = view_cursors(view);
+       Text *txt = vis_text(vis);
+
+       Filerange r = text_object_word(txt, view_cursors_pos(c)-1);
+       if (!text_range_valid(&r))
+               return NULL;
+
+       char *prefix = text_bytes_alloc0(txt, r.start, text_range_size(&r));
+       char *check;
+       for (check = prefix; *check; check++) {
+               if (!isspace(*check))
+                       break;
+       }
+       if (*check == '\0') {
+               vis_info_show(vis, "Autocompletion without prefix input is not 
valid.");
+               free(prefix);
+               return NULL;
+       }
+
+       return prefix;
+}
+
+static const char *autocomplete_file_text(Vis *vis, const char *keys, const 
Arg *arg) {
+       Win *win = vis_window(vis);
+       const char *fn = vis_window_filename(win);
+
+       char *prefix = get_prefix_for_autocomplete(vis);
+       if (!prefix)
+               return keys;
+
+       // TODO: get menu/dialog program to use from config?
+       insert_dialog_selection(vis, "cat '%s' | tr \" ;:$<>#?{}()[],.'\" '\n' 
| grep \"^%s\" | sort | uniq | dmenu | tr -d '\n' | sed \"s/%s//\"", fn, 
prefix, prefix);
+
+       free(prefix);
+       return keys;
+}
+
+static void insert_dialog_selection(Vis *vis, const char *cmdline, ...) {
+       View *view = vis_view(vis);
+       Cursor *c = view_cursors(view);
+
+       va_list ap;
+       va_start(ap, cmdline);
+
+       char* cmd = malloc(4096);
+       size_t ret = vsnprintf(cmd, 4096, cmdline, ap);
+       if (ret == 4096) {
+               vis_info_show(vis, "Command line too long.");
+               return;
+       }
+       va_end(ap);
+
+       char *outtext = get_output_of_external_command(vis, (const 
char*[]){cmd, NULL});
+       if (outtext == NULL) {
+               vis_info_show(vis, "Autocompletion command failed. Command was 
%s", cmd);
+               free(cmd);
+               return;
+       }
+       size_t len = strlen(outtext);
+
+       for (; c; c = view_cursors_next(c)) {
+               size_t pos = view_cursors_pos(c);
+               vis_insert(vis, pos, outtext, len);
+               view_cursors_scroll_to(c, pos + len);
+       }
+
+       free(outtext);
+       free(cmd);
+       return;
+}
+
 static Vis *vis;
 
 static void signal_handler(int signum, siginfo_t *siginfo, void *context) {
diff --git a/vis.c b/vis.c
index 3f04c87..09f854f 100644
--- a/vis.c
+++ b/vis.c
@@ -1360,4 +1360,8 @@ Win *vis_window(Vis *vis) {
 
 bool vis_get_autoindent(const Vis *vis) {
        return vis->autoindent;
-}
\ No newline at end of file
+}
+
+const char *vis_window_filename(Win *win) {
+       return win->file->name;
+}
diff --git a/vis.h b/vis.h
index ab7eade..2878f09 100644
--- a/vis.h
+++ b/vis.h
@@ -447,6 +447,7 @@ Regex *vis_regex(Vis*, const char *pattern);
 Text *vis_text(Vis*);
 View *vis_view(Vis*);
 Win *vis_window(Vis*);
+const char *vis_window_filename(Win*);
 
 bool vis_theme_load(Vis*, const char *name);
 
-- 
2.8.2


Reply via email to