rc

[fork] interactive rc shell
Log | Files | Refs | README | LICENSE

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:
Medit-readline.c | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mrc.h | 5++++-
Mwhich.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) {