commit 154fb2264f717aebe4ff149efce42411cefd583f
parent 6fe8c0e92443ce3898fa8c3ebd73f6976fe98eec
Author: Toby Goodwin <toby@paganbooks.eu>
Date: Sat, 28 Apr 2018 23:14:12 +0100
support completion through subdirs of path
Diffstat:
M | edit-readline.c | | | 109 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
M | rc.h | | | 5 | ++++- |
M | which.c | | | 16 | ++++++++-------- |
3 files changed, 104 insertions(+), 26 deletions(-)
diff --git a/edit-readline.c b/edit-readline.c
@@ -3,6 +3,7 @@
#include <errno.h>
#include <stdio.h>
#include <dirent.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <readline/readline.h>
#include <readline/history.h>
@@ -16,44 +17,118 @@ struct cookie {
char *buffer;
};
-/* can the name which we found in the first element of path be exec()d? */
-static bool is_command(List *path, char *name) {
+/* Join two strings with a "/" between them, into a malloc string */
+static char *dir_join(char *a, char *b) {
+ char *p;
+ if (!a) return strdup(b);
+ if (!b) return strdup(a);
+ p = ealloc(strlen(a) + strlen(b) + 2);
+ strcpy(p, a);
+ if (p[strlen(p) - 1] != '/')
+ strcat(p, "/");
+ strcat(p, b);
+ return p;
+}
+
+/* Suppose there is a subdirectory in one of the directories on $path,
+ * "/bin/sub/", and the user has typed "suTAB". We want to offer "sub/" as a
+ * completion, but we need to offer two things so it is not taken as the sole
+ * completion. We use "bodge" to hold "sub/" while we return "sub/..." the
+ * first time. Next time, we will notice that "bodge" is set, and return that
+ * instead. */
+static char *bodge;
+
+/* Decide if this directory entry is a completion candidate, either executable
+ * or a directory. "dname" is the absolute path of the directory, "name" is the
+ * current entry. "subdirs" is the name being completed up to and including the
+ * last slash (or NULL if there is no slash, "prefix" is the remainder of the
+ * name being completed, "len" is the length of "prefix".
+ */
+static char *entry(char *dname, char *name, char *subdirs,
+ char *prefix, size_t len) {
char *full;
- size_t len;
+ struct stat st;
+
+ if (strncmp(name, prefix, len) != 0)
+ return NULL;
+ if (streq(name, ".") || streq(name, ".."))
+ return NULL;
+ full = dir_join(dname, name);
+ if (rc_access(full, FALSE, &st)) {
+ efree(full);
+ return dir_join(subdirs, name);
+ }
+ efree(full);
+ if (S_ISDIR(st.st_mode)) {
+ char *dir_ret = ealloc(strlen(name) + 5);
+ char *r;
+ strcpy(dir_ret, name);
+ strcat(dir_ret, "/");
+ bodge = dir_join(subdirs, dir_ret);
+ strcat(dir_ret, "...");
+ r = dir_join(subdirs, dir_ret);
+ efree(dir_ret);
+ return r;
+ }
+ return NULL;
+}
- len = strlen(path->w) + strlen(name) + 2;
- full = nalloc(len);
- strcpy(full, path->w);
- strcat(full, "/");
- strcat(full, name);
- return rc_access(full, FALSE);
+/* Split a string "text" after the last "/" into "pre" and "post". If there is
+ * no "/", "pre" will be NULL. */
+void split_last_slash(const char *text, char **pre, char **post) {
+ char *last_slash = strrchr(text, '/');
+ if (last_slash) {
+ size_t l = last_slash + 1 - text;
+ *pre = ealloc(l + 1);
+ strncpy(*pre, text, l);
+ (*pre)[l] = '\0';
+ *post = last_slash + 1;
+ } else {
+ *pre = NULL;
+ *post = (char *)text;
+ }
}
static char *compl_extcmd(const char *text, int state) {
+ static char *dname, *prefix, *subdirs;
static DIR *d;
static List *path;
static size_t len;
if (!state) {
+ split_last_slash(text, &subdirs, &prefix);
d = NULL;
+ dname = NULL;
path = varlookup("path");
- len = strlen(text);
+ len = strlen(prefix);
+ bodge = NULL;
+ }
+ if (bodge) {
+ char *r = bodge;
+ bodge = NULL;
+ return r;
}
while (d || path) {
- if (!d)
- d = opendir(path->w);
- else {
+ if (!d) {
+ dname = dir_join(path->w, subdirs);
+ d = opendir(dname);
+ path = path->n;
+ if (!d) efree(dname);
+ } else {
struct dirent *e;
while ((e = readdir(d))) {
- if (strncmp(e->d_name, text, len) == 0 &&
- is_command(path, e->d_name))
- return strdup(e->d_name);
+ char *x;
+ x = entry(dname, e->d_name, subdirs,
+ prefix, len);
+ if (x)
+ return x;
}
closedir(d);
+ efree(dname);
d = NULL;
- path = path->n;
}
}
+ efree(subdirs);
return NULL;
}
diff --git a/rc.h b/rc.h
@@ -4,6 +4,9 @@
#include <assert.h>
+/* for struct stat */
+#include <sys/stat.h>
+
#define RC "rc: "
/* datatypes */
@@ -399,5 +402,5 @@ extern bool walk(Node *, bool);
extern bool cond;
/* which.c */
-extern bool rc_access(char *, bool);
+extern bool rc_access(char *, bool, struct stat *);
extern char *which(char *, bool);
diff --git a/which.c b/which.c
@@ -47,23 +47,22 @@ static int ingidset(gid_t g) {
Returns a bool instead of this -1 nonsense.
*/
-bool rc_access(char *path, bool verbose) {
- struct stat st;
+bool rc_access(char *path, bool verbose, struct stat *stp) {
int mask;
- if (stat(path, &st) != 0) {
+ if (stat(path, stp) != 0) {
if (verbose) /* verbose flag only set for absolute pathname */
uerror(path);
return FALSE;
}
if (uid == 0)
mask = X_ALL;
- else if (uid == st.st_uid)
+ else if (uid == stp->st_uid)
mask = X_USR;
- else if (gid == st.st_gid || ingidset(st.st_gid))
+ else if (gid == stp->st_gid || ingidset(stp->st_gid))
mask = X_GRP;
else
mask = X_OTH;
- if (((st.st_mode & S_IFMT) == S_IFREG) && (st.st_mode & mask))
+ if (((stp->st_mode & S_IFMT) == S_IFREG) && (stp->st_mode & mask))
return TRUE;
errno = EACCES;
if (verbose)
@@ -91,6 +90,7 @@ extern char *which(char *name, bool verbose) {
static size_t testlen = 0;
List *path;
int len;
+ struct stat st;
if (name == NULL) /* no filename? can happen with "> foo" as a command */
return NULL;
if (!initialized) {
@@ -114,7 +114,7 @@ extern char *which(char *name, bool verbose) {
#endif
}
if (isabsolute(name)) /* absolute pathname? */
- return rc_access(name, verbose) ? name : NULL;
+ return rc_access(name, verbose, &st) ? name : NULL;
len = strlen(name);
for (path = varlookup("path"); path != NULL; path = path->n) {
size_t need = strlen(path->w) + len + 2; /* one for null terminator, one for the '/' */
@@ -130,7 +130,7 @@ extern char *which(char *name, bool verbose) {
strcat(test, "/");
strcat(test, name);
}
- if (rc_access(test, FALSE))
+ if (rc_access(test, FALSE, &st))
return test;
}
if (verbose) {