diff --git a/src/launcher.c b/src/launcher.c index 738a5f2..4335b20 100644 --- a/src/launcher.c +++ b/src/launcher.c @@ -3,8 +3,10 @@ * For license, see COPYING. */ -#include #include +#include +#include +#include #include "wmfs.h" #include "event.h" @@ -12,6 +14,223 @@ #include "infobar.h" #include "config.h" +static int +fts_alphasort(const FTSENT **a, const FTSENT **b) +{ + return (strcmp((*a)->fts_name, (*b)->fts_name)); +} + +static char ** +complete_on_command(char *start) +{ + char **paths, *path, *p, **namelist = NULL; + int count; + FTS *tree; + FTSENT *node; + + if(!(path = getenv("PATH")) || !start) + return NULL; + + /* split PATH into paths */ + path = p = xstrdup(path); + + for(count = 1, p = path; strchr(p, ':'); ++p, ++count); + + paths = xcalloc(count, sizeof(*paths)); + + for(paths[0] = p = path, count = 1; (p = strchr(p, ':')); ++p, ++count) + { + paths[count] = p + 1; + *p = '\0'; + } + + paths[count] = NULL; + + if(!(tree = fts_open(paths, FTS_NOCHDIR, fts_alphasort))) + { + warn("fts_open"); + free(paths); + free(path); + return NULL; + } + + count = 0; + while((node = fts_read(tree))) + { + if(node->fts_level > 0) + fts_set(tree, node, FTS_SKIP); + + if(node->fts_level + && (node->fts_info & FTS_F) + && (node->fts_info & FTS_NS) + && (node->fts_statp->st_mode & S_IXOTH) + && !strncmp(node->fts_name, start, strlen(start))) + { + namelist = xrealloc(namelist, ++count, sizeof(*namelist)); + namelist[count - 1] = xstrdup(node->fts_name + strlen(start)); + } + } + + if(count) + { + namelist = xrealloc(namelist, ++count, sizeof(*namelist)); + namelist[count - 1] = NULL; + } + + if(fts_close(tree)) + warn("fts_close"); + + free(paths); + free(path); + + return namelist; +} + +/* + * Complete a filename or directory name. + * works like complete_on_command. + */ +static char ** +complete_on_files(char *start) +{ + char *p, *home, *path, *dirname = NULL, *paths[2], **namelist = NULL; + int count; + FTS *tree; + FTSENT *node; + + p = start; + + /* + * Search the directory to open and set + * the beginning of file to complete on pointer 'p'. + */ + if(*p == '\0' || !strrchr(p, '/')) + path = xstrdup("."); + else + { + if(!(home = getenv("HOME"))) + return NULL; + + /* remplace ~ by $HOME in dirname */ + if(!strncmp(p, "~/", 2) && home) + xasprintf(&dirname, "%s%s", home, p+1); + else + dirname = xstrdup(p); + + /* Set p to filename to be complete + * and path the directory containing the file + * /foooooo/baaaaaar/somethinglikethis + * <---- path - ---><------- p ------> + */ + p = strrchr(dirname, '/'); + if(p != dirname) + { + *(p++) = '\0'; + path = xstrdup(dirname); + } + else + { + path = xstrdup("/"); + p++; + } + } + + paths[0] = path; + paths[1] = NULL; + + if(!(tree = fts_open(paths, FTS_NOCHDIR, fts_alphasort))) + { + warn("fts_open"); + free(dirname); + free(path); + return NULL; + } + + count = 0; + while((node = fts_read(tree))) + { + if(node->fts_level > 0) + fts_set(tree, node, FTS_SKIP); + + if(node->fts_level && !strncmp(node->fts_name, p, strlen(p))) + { + namelist = xrealloc(namelist, ++count, sizeof(*namelist)); + namelist[count - 1] = xstrdup(node->fts_name + strlen(p)); + } + } + + if(count) + { + namelist = xrealloc(namelist, ++count, sizeof(*namelist)); + namelist[count - 1] = NULL; + } + + if(fts_close(tree)) + warn("fts_close"); + + free(dirname); + free(path); + + return namelist; +} + +static void +complete_cache_free(struct launcher_ccache *cache) +{ + int i; + + /* release memory */ + free(cache->start); + + if(cache->namelist) + { + for(i = 0; cache->namelist[i]; i++) + free(cache->namelist[i]); + free(cache->namelist); + } + + /* init */ + cache->hits = 0; + cache->start = NULL; + cache->namelist = NULL; +} + +static char * +complete(struct launcher_ccache *cache, char *start) +{ + char *p = NULL, *comp = NULL; + + if(!start || !cache) + return NULL; + + if((p = strrchr(start, ' '))) + p++; + else + p = start; + + if(cache->start && !strcmp(cache->start, start)) + { + if(cache->namelist && !cache->namelist[cache->hits]) + cache->hits = 0; + } + else + { + complete_cache_free(cache); + cache->start = xstrdup(start); + + if(p == start) + cache->namelist = complete_on_command(p); + else + cache->namelist = complete_on_files(p); + + } + + if(cache->namelist && cache->namelist[cache->hits]) + comp = cache->namelist[cache->hits]; + + return comp; +} + #define LAUNCHER_INIT_ELEM(width) \ SLIST_FOREACH(ib, &W->screen->infobars, next) \ { \ @@ -29,10 +248,11 @@ launcher_process(struct launcher *l) { struct infobar *ib; struct element *e; - bool loop = true; - char buf[512] = { 0 }; + struct launcher_ccache cache = {NULL, NULL, 0}; + bool loop = true, found = false, lastwastab = false; + char tmpbuf[512] = { 0 }, buf[512] = { 0 }; char tmp[32] = { 0 }; - char *p, *data, *arg, *cmd = xstrdup(l->command); + char *p, *data, *arg, *end, *cmd = xstrdup(l->command); int i, pos = 0, histpos = 0; void (*func)(Uicb); XEvent ev; @@ -140,16 +360,41 @@ launcher_process(struct launcher *l) loop = false; break; - /* TODO: Completion */ + /* Completion */ case XK_Tab: + buf[pos] = '\0'; + if(lastwastab) + ++cache.hits; + else + { + cache.hits = 0; + strncpy(tmpbuf, buf, sizeof(tmpbuf)); + } + + if(pos && (end = complete(&cache, tmpbuf))) + { + strncpy(buf, tmpbuf, sizeof(buf)); + strncat(buf, end, sizeof(buf)); + found = true; + } + + lastwastab = true; + + /* start a new round of tabbing */ + if(!found) + cache.hits = 0; + + pos = strlen(buf); break; case XK_BackSpace: + lastwastab = false; if(pos) buf[--pos] = '\0'; break; default: + lastwastab = false; strncat(buf, tmp, sizeof(tmp)); ++pos; break; @@ -174,6 +419,7 @@ launcher_process(struct launcher *l) XUngrabKeyboard(W->dpy, CurrentTime); + complete_cache_free(&cache); free(cmd); free(data); diff --git a/src/util.c b/src/util.c index 0786fe9..e20c684 100644 --- a/src/util.c +++ b/src/util.c @@ -46,6 +46,28 @@ xcalloc(size_t nmemb, size_t size) return ret; } +/** realloc with error support and size_t overflow check + * \param ptr old pointer + * \param nmemb number of objects + * \param size size of single object + * \return non null void pointer + */ +void * +xrealloc(void *ptr, size_t nmemb, size_t size) +{ + void *ret; + + if(SIZE_MAX / nmemb < size) + err(EXIT_FAILURE, "xrealloc(%p, %zu, %zu), " + "size_t overflow detected", ptr, nmemb, size); + + if((ret = realloc(ptr, nmemb * size)) == NULL) + err(EXIT_FAILURE, "realloc(%p, %zu)", ptr, nmemb * size); + + return ret; +} + + /** asprintf wrapper * \param strp target string * \param fmt format diff --git a/src/util.h b/src/util.h index 6e794f3..c2431e2 100644 --- a/src/util.h +++ b/src/util.h @@ -86,6 +86,7 @@ str_to_position(char *str) void *xmalloc(size_t nmemb, size_t size); void *xcalloc(size_t nmemb, size_t size); +void *xrealloc(void *ptr, size_t nmemb, size_t size); int xasprintf(char **strp, const char *fmt, ...); char *xstrdup(const char *str); pid_t spawn(const char *format, ...); diff --git a/src/wmfs.h b/src/wmfs.h index c0b28cf..fb0bcdf 100644 --- a/src/wmfs.h +++ b/src/wmfs.h @@ -292,6 +292,13 @@ struct launcher SLIST_ENTRY(launcher) next; }; +struct launcher_ccache +{ + char *start; + char **namelist; + size_t hits; +}; + struct _systray { struct geo geo;