diff --git a/access.c b/access.c index e393c1e..a7cc1ea 100644 --- a/access.c +++ b/access.c @@ -1,6 +1,5 @@ /* access.c -- access testing and path searching ($Revision: 1.2 $) */ -#define REQUIRE_STAT 1 #define REQUIRE_PARAM 1 #include "es.h" @@ -40,7 +39,7 @@ static Boolean ingroupset(gidset_t gid) { return FALSE; } -static int testperm(struct stat *stat, unsigned int perm) { +extern int testperm(struct stat *stat, unsigned int perm) { unsigned int mask; static gidset_t uid, gid; static Boolean initialized = FALSE; diff --git a/es.h b/es.h index 83ab221..7396acb 100644 --- a/es.h +++ b/es.h @@ -198,6 +198,7 @@ extern void setnoexport(List *list); extern void addtolist(void *arg, char *key, void *value); extern List *listvars(Boolean internal); extern List *varswithprefix(char *prefix); +extern List *fnswithprefix(char *prefix); typedef struct Push Push; extern Push *pushlist; @@ -216,6 +217,7 @@ extern void printstatus(int pid, int status); /* access.c */ +extern int testperm(struct stat *stat, unsigned int perm); extern char *checkexecutable(char *file); @@ -283,7 +285,7 @@ extern void *erealloc(void *p, size_t n); extern void efree(void *p); extern void ewrite(int fd, const char *s, size_t n); extern long eread(int fd, char *buf, size_t n); -extern Boolean isabsolute(char *path); +extern Boolean isabsolute(const char *path); extern Boolean streq2(const char *s, const char *t1, const char *t2); diff --git a/input.c b/input.c index c07b116..a3dade6 100644 --- a/input.c +++ b/input.c @@ -529,9 +529,109 @@ static char *list_completion_function(const char *text, int state) { return result; } -char **builtin_completion(const char *text, int UNUSED start, int UNUSED end) { - char **matches = NULL; +enum st { + NORMAL, + START, /* start of a command */ + PIPESTART, /* just after a '|' */ + PIPESTARTBRACKET, /* the '|[' in 'a |[2] b' */ + LT /* the '<' in '<=word' */ +}; + +/* detect if we're currently at the start of a line. works ~90% well */ +static Boolean cmdstart(int point) { + int i; + Boolean quote = FALSE; + enum st state = START; + for (i = 0; i < point; i++) { + char c = rl_line_buffer[i]; + if (c == '\'') { + quote = !quote; + continue; + } + if (quote) continue; + + switch (state) { + case PIPESTARTBRACKET: + if (c == ']') + state = START; + break; + case LT: + if (c == '=') + state = START; + else + state = NORMAL; + break; + case PIPESTART: + if (c == '[') { + state = PIPESTARTBRACKET; + break; + } + state = START; /* does this handle || correctly? */ + /* fallthrough */ + case START: + if (c == ' ' || c == '\t' || c == '\n' || c == '!') + continue; + /* fallthrough */ + case NORMAL: + switch (c) { + case '&': case '{': case '`': + state = START; + break; + case '|': + state = PIPESTART; + break; + case '<': + state = LT; + break; + default: + /* fallthroughs make this useful */ + state = NORMAL; + break; + } + } + } + return state == START || state == PIPESTART; +} + +/* first-position completion. includes + * - built-ins: local let for fn %closure match + * - functions + * - if absolute (including home), absolute executable files + */ +static List *listexecutables(char *text) { + int i = 0; + char *s; + List *compl = NULL; + static char *builtins[] = {"local", "let", "for", "fn", "%closure", "match"}; + for (i = 0; i < 6; i++) + if (strneq(text, builtins[i], strlen(text))) + compl = mklist(mkstr(builtins[i]), compl); + + compl = append(fnswithprefix(text), compl); + + if (isabsolute(text) || *text == '~') { + i = 0; + /* goofy hack :) */ + while ((s = rl_filename_completion_function(text, i)) != NULL) { + struct stat st; + i = 1; + /* TODO: ~/foo doesn't stat(), so don't try */ + if (*s != '~') { + if (stat(s, &st) == -1) + continue; + /* 1 == EXEC */ + if (testperm(&st, 1) != 0) + continue; + } + /* TODO: recurse in directories? */ + compl = mklist(mkstr(s), compl); + } + } + return compl; +} +char **builtin_completion(const char *text, int start, int UNUSED end) { + /* variable or primitive completion */ if (*text == '$') { wordslistgen = varswithprefix; complprefix = "$"; @@ -543,14 +643,22 @@ char **builtin_completion(const char *text, int UNUSED start, int UNUSED end) { case '^': complprefix = "$^"; break; case '#': complprefix = "$#"; break; } - matches = rl_completion_matches(text, list_completion_function); + return rl_completion_matches(text, list_completion_function); } /* ~foo => username. ~foo/bar already gets completed as filename. */ - if (!matches && *text == '~' && !strchr(text, '/')) - matches = rl_completion_matches(text, rl_username_completion_function); + if (*text == '~' && !strchr(text, '/')) + return rl_completion_matches(text, rl_username_completion_function); + + /* first-word completion, which is ~special~ */ + if (cmdstart(start)) { + wordslistgen = listexecutables; + complprefix = ""; + rl_attempted_completion_over = 1; + return rl_completion_matches(text, list_completion_function); + } - return matches; + return NULL; /* fall back to normal filename completion */ } #endif /* HAVE_READLINE */ diff --git a/stdenv.h b/stdenv.h index 98bfbf8..a08e391 100644 --- a/stdenv.h +++ b/stdenv.h @@ -44,9 +44,7 @@ #include #endif -#if REQUIRE_STAT #include -#endif #if REQUIRE_DIRENT #if HAVE_DIRENT_H diff --git a/util.c b/util.c index bf1a53a..ebf7b48 100644 --- a/util.c +++ b/util.c @@ -34,7 +34,7 @@ extern void uerror(char *s) { } /* isabsolute -- test to see if pathname begins with "/", "./", or "../" */ -extern Boolean isabsolute(char *path) { +extern Boolean isabsolute(const char *path) { return path[0] == '/' || (path[0] == '.' && (path[1] == '/' || (path[1] == '.' && path[2] == '/'))); diff --git a/var.c b/var.c index bacc752..3376bca 100644 --- a/var.c +++ b/var.c @@ -357,6 +357,12 @@ static void listwithprefix(void *arg, char *key, void *value) { addtolist(arg, key, value); } +static void fnwithprefix(void *arg, char *key, void *value) { + if (strneq(key, "fn-", 3) && + strneq(key + 3, list_prefix, strlen(list_prefix))) + addtolist(arg, key + 3, value); +} + /* listvars -- return a list of all the (dynamic) variables */ extern List *listvars(Boolean internal) { Ref(List *, varlist, NULL); @@ -365,6 +371,15 @@ extern List *listvars(Boolean internal) { RefReturn(varlist); } +/* fnswithprefix -- return a list of all the (dynamic) functions + * matching the given prefix */ +extern List *fnswithprefix(char *prefix) { + Ref(List *, fnlist, NULL); + list_prefix = prefix; + dictforall(vars, fnwithprefix, &fnlist); + RefReturn(fnlist); +} + /* varswithprefix -- return a list of all the (dynamic) variables * matching the given prefix */ extern List *varswithprefix(char *prefix) {