~kaction/config

1ab4be43ae3f278dc70cd8da11c4a06c69414dda — Dmitry Bogatov 2 years ago 8447bd1
nix: import old patch for rc shell

This patch adds programmable completion into rc(1) shell. I do not
expect this patch to be used anytime soon, but I love the idea.
M Tupfile => Tupfile +1 -0
@@ 27,6 27,7 @@ preload src/copy/prefix/config/mcabber
preload src/copy/prefix/config/mmh
preload src/copy/prefix/config/nixpkgs
preload src/copy/prefix/config/nixpkgs/override
preload src/copy/prefix/config/nixpkgs/patches
preload src/copy/prefix/config/proxychains
preload src/copy/prefix/config/vim
preload src/copy/prefix/config/vim/after

M src/copy/prefix/config/nixpkgs/config.nix => src/copy/prefix/config/nixpkgs/config.nix +3 -0
@@ 4,6 4,9 @@
      buildPhase = let config = ./override/dwm+config.h;
                   in "cp ${config} config.h; make";
    });
    rc-with-completion = pkgs.rc.overrideAttrs (old: rec {
      patches = [ ./patches/rc+external-completer.patch ];
    });
    world = pkgs.buildEnv {
      name = "world";
      paths = with pkgs;

A src/copy/prefix/config/nixpkgs/patches/rc+external-completer.patch => src/copy/prefix/config/nixpkgs/patches/rc+external-completer.patch +151 -0
@@ 0,0 1,151 @@
From: Dmitry Bogatov <KAction@gnu.org>
Date: Thu, 23 Mar 2017 15:37:20 +0300
Subject: [PATCH] New addon to setup external completion program. (Closes
 #287828)

Some time ago I worked on implementing programmable completion of rc(1).
rc(1) shell is much, much simple and elegant then dash(1) or bash(1).

Implementation consisted of this patch to rc(1) shell and external
program, named `sh.complete', that output suggested completions on
stdout.

While GNU Readline is far from being minimal, and probably hacking on
libedit can yield lesser overhead, the main problem is that
reimplementing `bash-completion' project and all its addons proved to be
much more complex, than expected.

sh.complete performed well in simple cases. In `bash-completion', these
cases are handled surprisingly well by parsing output of `command
--help'.  More complex cases, like git(1) or mmh(1) proved to be hard
not only due irregular command line syntax, but also because generating
completions has own, non-trivial logic.

Life is mess, and I start considering mess of bash scripts to deal with
it inevitable. Writing optimizing compiler for bash scripts to C seems
to be too hard.

In mean time, I can only hope, that approach, used by haskell library
`optparse-applicative', when program in high-level language implement
completion logic itself.

Sure, it introduce complexity and increases binary size, but completion
script is even more complexity -- actually, it reverse-engeniers
original program. And binary size...

	-rwxr-xr-x 1 root root 35424 Aŭg 29 20:20 /bin/true

---
 addon.c          | 76 ++++++++++++++++++++++++++++++++++++++++++++++++
 addon.h          |  4 ++-
 debian/changelog |  1 +
 3 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/addon.c b/addon.c
index 6e6c232..fb34e07 100644
--- a/addon.c
+++ b/addon.c
@@ -5,6 +5,7 @@
 
 #include "rc.h"
 #include "addon.h"
+#include <readline/readline.h>
 
 void b_sum(char **av) {
 	long sum = 0;
@@ -23,3 +24,78 @@ void b_prod(char **av) {
 	fprint(1, "%ld\n", sum);
 	set(TRUE);
 }
+
+/* Name of program used as external completer, suitable for popen(3).
+ * It is supposed to examine following environment variables:
+ *
+ *   - COMPLETION_LINE, containing current readline buffer
+ *   - COMPLETION_POINT, containing integer position of cursor in
+ *     readline buffer.
+ *   - COMPLETION_PARTIAL, containing partial word from point of view
+ *     of Readline library. While it should be possible to infer it
+ *     from previous two variables, it is usually fine to trust Readline.
+ *
+ * and output completion suggestions, one per line. Variables
+ * 'external_completer' and 'rl_completion_entry_function' are either
+ * both NULL or both non-NULL. This invariant is maintained by 'b_external'
+ * function.
+ */
+
+static const char *external_completer = NULL;
+
+static FILE *completion_subprocess;
+static char *completion_generator(const char *partial, int state) {
+	enum { overwrite = 1 };
+	char *line = NULL;
+	size_t n = 0;
+	ssize_t read;
+
+	/* We need to do it. By default, readline appends space after user
+	 * chooses completion. Usually, it is what user expects, since
+	 * she can immediately continue typing other arguments.
+	 * But it is not so in case of filenames arguments. If completion
+	 * is directory, slash should be appended after it, and no space,
+	 * so next TAB pressed completes inside that directory.
+	 *
+	 * This is common need and handled separately by readline, so
+	 * Bash, mksh and other shells behaves correctly.
+	 *
+	 * Since with external completer approach we, C code, have no idea
+	 * what completion actually is, the right thing to do would
+	 * be allow external completer handle space appending on its own,
+	 * and do not get in its way.
+	 */
+	rl_completion_suppress_append = 1;
+
+	if (state == 0) {
+		/* It is more then enough to keep single integer string representation */
+		char point[24];
+
+		snprintf(point, sizeof(point), "%d", rl_point);
+		setenv("COMPLETION_LINE", rl_line_buffer, overwrite);
+		setenv("COMPLETION_POINT", point, overwrite);
+		setenv("COMPLETION_PARTIAL", partial, overwrite);
+		completion_subprocess = popen("sh.complete", "r");
+		if (!completion_subprocess) {
+			return NULL;
+		}
+	}
+
+	read = getline(&line, &n, completion_subprocess);
+	if (read == -1) {
+		free(line);
+		pclose(completion_subprocess);
+		return NULL;
+	} else {
+		/* Remove trailing \n */
+		line[read - 1] = '\0';
+		return line;
+	}
+}
+
+void b_extern(char **av) {
+	const char *arg = av[1];
+
+	rl_completion_entry_function = arg ? &completion_generator : NULL;
+	external_completer = arg;
+}
diff --git a/addon.h b/addon.h
index 4a69375..7aac22a 100644
--- a/addon.h
+++ b/addon.h
@@ -23,9 +23,11 @@
 
 #define ADDONS \
 	{ b_sum,	"+" }, \
-	{ b_prod, "x" },
+	{ b_prod, "x" }, \
+	{ b_extern, "extern" }
 
 extern void b_sum(char **av);
 extern void b_prod(char **av);
+extern void b_extern(char **av);
 
 #endif