From owner-dev-commits-src-all@freebsd.org Mon Mar 29 09:22:26 2021 Return-Path: Delivered-To: dev-commits-src-all@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 6F5BF5C29E2; Mon, 29 Mar 2021 09:22:26 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4F86YG2n8sz4svC; Mon, 29 Mar 2021 09:22:26 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 5234F10EB8; Mon, 29 Mar 2021 09:22:26 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 12T9MQW7066525; Mon, 29 Mar 2021 09:22:26 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 12T9MQ7x066524; Mon, 29 Mar 2021 09:22:26 GMT (envelope-from git) Date: Mon, 29 Mar 2021 09:22:26 GMT Message-Id: <202103290922.12T9MQ7x066524@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Piotr Pawel Stefaniak Subject: git: b315a7296d2a - main - sh(1): autocomplete commands MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: pstef X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: b315a7296d2a69883c483d79dfcb3860a0428f21 Auto-Submitted: auto-generated X-BeenThere: dev-commits-src-all@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Commit messages for all branches of the src repository List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 29 Mar 2021 09:22:26 -0000 The branch main has been updated by pstef: URL: https://cgit.FreeBSD.org/src/commit/?id=b315a7296d2a69883c483d79dfcb3860a0428f21 commit b315a7296d2a69883c483d79dfcb3860a0428f21 Author: Piotr Pawel Stefaniak AuthorDate: 2021-03-28 09:58:20 +0000 Commit: Piotr Pawel Stefaniak CommitDate: 2021-03-29 09:14:27 +0000 sh(1): autocomplete commands Without this patch, sh can autocomplete file names but not commands from $PATH. Use libedit's facility to execute custom function for autocomplete, but yield to the library's standard autocomplete function when cursor is not at position 0. Reviewed by: bapt Differential Revision: https://reviews.freebsd.org/D29361 --- bin/sh/histedit.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++- bin/sh/myhistedit.h | 2 + lib/libedit/Makefile | 2 +- 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/bin/sh/histedit.c b/bin/sh/histedit.c index 4c596c82257e..45a821736bc0 100644 --- a/bin/sh/histedit.c +++ b/bin/sh/histedit.c @@ -39,6 +39,8 @@ static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; __FBSDID("$FreeBSD$"); #include +#include +#include #include #include #include @@ -72,6 +74,9 @@ static FILE *el_in, *el_out; static char *fc_replace(const char *, char *, char *); static int not_fcnumber(const char *); static int str_to_event(const char *, int); +static int comparator(const void *, const void *, void *); +static char **sh_matches(const char *, int, int); +static unsigned char sh_complete(EditLine *, int); /* * Set history and editing status. Called whenever the status may @@ -122,7 +127,7 @@ histedit(void) el_set(el, EL_PROMPT, getprompt); el_set(el, EL_ADDFN, "sh-complete", "Filename completion", - _el_fn_complete); + sh_complete); } else { bad: out2fmt_flush("sh: can't initialize editing\n"); @@ -519,3 +524,124 @@ bindcmd(int argc __unused, char **argv __unused) return (0); } #endif + +/* + * Comparator function for qsort(). The use of curpos here is to skip + * characters that we already know to compare equal (common prefix). + */ +static int +comparator(const void *a, const void *b, void *thunk) +{ + size_t curpos = (intptr_t)thunk; + return (strcmp(*(char *const *)a + curpos, + *(char *const *)b + curpos)); +} + +/* + * This function is passed to libedit's fn_complete2(). The library will + * use it instead of its standard function that finds matching files in + * current directory. If we're at the start of the line, we want to look + * for available commands from all paths in $PATH. + */ +static char +**sh_matches(const char *text, int start, int end) +{ + char *free_path = NULL, *path; + const char *dirname; + char **matches = NULL; + size_t i = 0, size = 16, j, k; + size_t curpos = end - start; + + if (start > 0 || memchr("/.~", text[0], 3) != NULL) + return (NULL); + if ((free_path = path = strdup(pathval())) == NULL) + goto out; + if ((matches = malloc(size * sizeof(matches[0]))) == NULL) + goto out; + while ((dirname = strsep(&path, ":")) != NULL) { + struct dirent *entry; + DIR *dir; + int dfd; + + dir = opendir(dirname[0] == '\0' ? "." : dirname); + if (dir == NULL) + continue; + if ((dfd = dirfd(dir)) == -1) { + closedir(dir); + continue; + } + while ((entry = readdir(dir)) != NULL) { + struct stat statb; + char **rmatches; + + if (strncmp(entry->d_name, text, curpos) != 0) + continue; + if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) { + if (fstatat(dfd, entry->d_name, &statb, 0) == -1) + continue; + if (!S_ISREG(statb.st_mode)) + continue; + } else if (entry->d_type != DT_REG) + continue; + matches[++i] = strdup(entry->d_name); + if (i < size - 1) + continue; + size *= 2; + rmatches = reallocarray(matches, size, sizeof(matches[0])); + if (rmatches == NULL) { + closedir(dir); + goto out; + } + matches = rmatches; + } + closedir(dir); + } +out: + free(free_path); + /* + * matches[0] is special: it's not a real matching file name but a common + * prefix for all matching names. It can't be null, unlike any other + * element of the array. When strings matches[0] and matches[1] compare + * equal and matches[2] is null that means to libedit that there is only + * a single match. It will then replace user input with possibly escaped + * string in matches[0] which is the reason to copy the full name of the + * only match. + */ + if (i == 0) { + free(matches); + return (NULL); + } else if (i == 1) { + matches[0] = strdup(matches[1]); + matches[2] = NULL; + if (matches[0] != NULL) + return (matches); + } else + matches[0] = strdup(text); + if (matches[0] == NULL) { + for (j = 1; j <= i; j++) + free(matches[j]); + free(matches); + return (NULL); + } + qsort_s(matches + 1, i, sizeof(matches[0]), comparator, + (void *)(intptr_t)curpos); + for (j = 1, k = 2; k <= i; k++) + if (strcmp(matches[j] + curpos, matches[k] + curpos) == 0) + free(matches[k]); + else + matches[++j] = matches[k]; + matches[j + 1] = NULL; + return (matches); +} + +/* + * This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to + * bind a key (tab by default) to execute the function. + */ +unsigned char +sh_complete(EditLine *sel, int ch __unused) +{ + return (unsigned char)fn_complete2(sel, NULL, sh_matches, + L" \t\n\"\\'`@$><=;|&{(", NULL, NULL, (size_t)100, + NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH); +} diff --git a/bin/sh/myhistedit.h b/bin/sh/myhistedit.h index a4c0b56fafd7..968d23c9c1f8 100644 --- a/bin/sh/myhistedit.h +++ b/bin/sh/myhistedit.h @@ -38,6 +38,8 @@ extern History *hist; extern EditLine *el; extern int displayhist; +#include + void histedit(void); void sethistsize(const char *); void setterm(const char *); diff --git a/lib/libedit/Makefile b/lib/libedit/Makefile index 963063ef90fa..e549ac15c000 100644 --- a/lib/libedit/Makefile +++ b/lib/libedit/Makefile @@ -65,7 +65,7 @@ MLINKS= \ editline.3 tok_wreset.3 \ editline.3 tok_wstr.3 -INCS= histedit.h +INCS= histedit.h filecomplete.h SRCS+= common.h emacs.h fcns.h func.h help.h vi.h CLEANFILES+= common.h emacs.h fcns.h func.h help.h vi.h