1*4ee54736Schristos /* $NetBSD: filecomplete.c,v 1.73 2023/04/25 17:51:32 christos Exp $ */
241a59814Sdsl
341a59814Sdsl /*-
441a59814Sdsl * Copyright (c) 1997 The NetBSD Foundation, Inc.
541a59814Sdsl * All rights reserved.
641a59814Sdsl *
741a59814Sdsl * This code is derived from software contributed to The NetBSD Foundation
841a59814Sdsl * by Jaromir Dolecek.
941a59814Sdsl *
1041a59814Sdsl * Redistribution and use in source and binary forms, with or without
1141a59814Sdsl * modification, are permitted provided that the following conditions
1241a59814Sdsl * are met:
1341a59814Sdsl * 1. Redistributions of source code must retain the above copyright
1441a59814Sdsl * notice, this list of conditions and the following disclaimer.
1541a59814Sdsl * 2. Redistributions in binary form must reproduce the above copyright
1641a59814Sdsl * notice, this list of conditions and the following disclaimer in the
1741a59814Sdsl * documentation and/or other materials provided with the distribution.
1841a59814Sdsl *
1941a59814Sdsl * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2041a59814Sdsl * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2141a59814Sdsl * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2241a59814Sdsl * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2341a59814Sdsl * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2441a59814Sdsl * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2541a59814Sdsl * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2641a59814Sdsl * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2741a59814Sdsl * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2841a59814Sdsl * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2941a59814Sdsl * POSSIBILITY OF SUCH DAMAGE.
3041a59814Sdsl */
3141a59814Sdsl
3241a59814Sdsl #include "config.h"
3341a59814Sdsl #if !defined(lint) && !defined(SCCSID)
34*4ee54736Schristos __RCSID("$NetBSD: filecomplete.c,v 1.73 2023/04/25 17:51:32 christos Exp $");
3541a59814Sdsl #endif /* not lint && not SCCSID */
3641a59814Sdsl
3741a59814Sdsl #include <sys/types.h>
3841a59814Sdsl #include <sys/stat.h>
3941a59814Sdsl #include <dirent.h>
4041a59814Sdsl #include <errno.h>
4141a59814Sdsl #include <fcntl.h>
4222383670Schristos #include <limits.h>
4322383670Schristos #include <pwd.h>
4422383670Schristos #include <stdio.h>
4522383670Schristos #include <stdlib.h>
4622383670Schristos #include <string.h>
4722383670Schristos #include <unistd.h>
481237974aSchristos
4941a59814Sdsl #include "el.h"
5041a59814Sdsl #include "filecomplete.h"
5141a59814Sdsl
520594af80Schristos static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{(";
5341a59814Sdsl
5441a59814Sdsl /********************************/
5541a59814Sdsl /* completion functions */
5641a59814Sdsl
5741a59814Sdsl /*
5841a59814Sdsl * does tilde expansion of strings of type ``~user/foo''
5941a59814Sdsl * if ``user'' isn't valid user name or ``txt'' doesn't start
6041a59814Sdsl * w/ '~', returns pointer to strdup()ed copy of ``txt''
6141a59814Sdsl *
6241482d77Sriz * it's the caller's responsibility to free() the returned string
6341a59814Sdsl */
6441a59814Sdsl char *
fn_tilde_expand(const char * txt)6519c38590Schristos fn_tilde_expand(const char *txt)
6641a59814Sdsl {
6756eac9eaSchristos #if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT)
6856eac9eaSchristos struct passwd pwres;
6956eac9eaSchristos char pwbuf[1024];
7056eac9eaSchristos #endif
7156eac9eaSchristos struct passwd *pass;
725679adf8Schristos const char *pos;
7341a59814Sdsl char *temp;
7441a59814Sdsl size_t len = 0;
7541a59814Sdsl
7641a59814Sdsl if (txt[0] != '~')
77b71bed95Schristos return strdup(txt);
7841a59814Sdsl
795679adf8Schristos pos = strchr(txt + 1, '/');
805679adf8Schristos if (pos == NULL) {
8141a59814Sdsl temp = strdup(txt + 1);
8241a59814Sdsl if (temp == NULL)
8341a59814Sdsl return NULL;
8441a59814Sdsl } else {
853d802cf5Schristos /* text until string after slash */
865679adf8Schristos len = (size_t)(pos - txt + 1);
87113f06a3Schristos temp = el_calloc(len, sizeof(*temp));
8841a59814Sdsl if (temp == NULL)
8941a59814Sdsl return NULL;
90a47ebb18Schristos (void)strlcpy(temp, txt + 1, len - 1);
9141a59814Sdsl }
922e685adeSdsl if (temp[0] == 0) {
93b2105969Schristos #ifdef HAVE_GETPW_R_POSIX
94b2105969Schristos if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf),
95b2105969Schristos &pass) != 0)
962e685adeSdsl pass = NULL;
97b2105969Schristos #elif HAVE_GETPW_R_DRAFT
98b2105969Schristos pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf));
99b2105969Schristos #else
100b2105969Schristos pass = getpwuid(getuid());
101b2105969Schristos #endif
1022e685adeSdsl } else {
103b2105969Schristos #ifdef HAVE_GETPW_R_POSIX
10441a59814Sdsl if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
10541a59814Sdsl pass = NULL;
106b2105969Schristos #elif HAVE_GETPW_R_DRAFT
107b2105969Schristos pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf));
108b2105969Schristos #else
109b2105969Schristos pass = getpwnam(temp);
110b2105969Schristos #endif
1112e685adeSdsl }
112a13cd756Schristos el_free(temp); /* value no more needed */
11341a59814Sdsl if (pass == NULL)
114b71bed95Schristos return strdup(txt);
11541a59814Sdsl
11641a59814Sdsl /* update pointer txt to point at string immedially following */
11741a59814Sdsl /* first slash */
11841a59814Sdsl txt += len;
11941a59814Sdsl
120a13cd756Schristos len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1;
121113f06a3Schristos temp = el_calloc(len, sizeof(*temp));
12241a59814Sdsl if (temp == NULL)
12341a59814Sdsl return NULL;
124a13cd756Schristos (void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt);
12541a59814Sdsl
126b71bed95Schristos return temp;
12741a59814Sdsl }
12841a59814Sdsl
129fa615556Sabhinav static int
needs_escaping(wchar_t c)130d1647d9cSchristos needs_escaping(wchar_t c)
131fa615556Sabhinav {
132fa615556Sabhinav switch (c) {
133fa615556Sabhinav case '\'':
134fa615556Sabhinav case '"':
135fa615556Sabhinav case '(':
136fa615556Sabhinav case ')':
137fa615556Sabhinav case '\\':
138fa615556Sabhinav case '<':
139fa615556Sabhinav case '>':
140fa615556Sabhinav case '$':
141fa615556Sabhinav case '#':
142fa615556Sabhinav case ' ':
143fa615556Sabhinav case '\n':
144fa615556Sabhinav case '\t':
145fa615556Sabhinav case '?':
146fa615556Sabhinav case ';':
147fa615556Sabhinav case '`':
148fa615556Sabhinav case '@':
149fa615556Sabhinav case '=':
150fa615556Sabhinav case '|':
151fa615556Sabhinav case '{':
152fa615556Sabhinav case '}':
153fa615556Sabhinav case '&':
154b36e4c8fSabhinav case '*':
155b36e4c8fSabhinav case '[':
156fa615556Sabhinav return 1;
157fa615556Sabhinav default:
158fa615556Sabhinav return 0;
159fa615556Sabhinav }
160fa615556Sabhinav }
161fa615556Sabhinav
1627512edc2Schristos static int
needs_dquote_escaping(char c)1637512edc2Schristos needs_dquote_escaping(char c)
1647512edc2Schristos {
1657512edc2Schristos switch (c) {
1667512edc2Schristos case '"':
1677512edc2Schristos case '\\':
1687512edc2Schristos case '`':
1697512edc2Schristos case '$':
1707512edc2Schristos return 1;
1717512edc2Schristos default:
1727512edc2Schristos return 0;
1737512edc2Schristos }
1747512edc2Schristos }
1757512edc2Schristos
176c93cba3aSabhinav
177c93cba3aSabhinav static wchar_t *
unescape_string(const wchar_t * string,size_t length)178c93cba3aSabhinav unescape_string(const wchar_t *string, size_t length)
179c93cba3aSabhinav {
18063147cb5Sabhinav size_t i;
18163147cb5Sabhinav size_t j = 0;
182113f06a3Schristos wchar_t *unescaped = el_calloc(length + 1, sizeof(*string));
183c93cba3aSabhinav if (unescaped == NULL)
184c93cba3aSabhinav return NULL;
18563147cb5Sabhinav for (i = 0; i < length ; i++) {
186c93cba3aSabhinav if (string[i] == '\\')
187c93cba3aSabhinav continue;
188c93cba3aSabhinav unescaped[j++] = string[i];
189c93cba3aSabhinav }
190c93cba3aSabhinav unescaped[j] = 0;
191c93cba3aSabhinav return unescaped;
192c93cba3aSabhinav }
193c93cba3aSabhinav
194fa615556Sabhinav static char *
escape_filename(EditLine * el,const char * filename,int single_match,const char * (* app_func)(const char *))195c3d00a7dSabhinav escape_filename(EditLine * el, const char *filename, int single_match,
196c3d00a7dSabhinav const char *(*app_func)(const char *))
197fa615556Sabhinav {
198fa615556Sabhinav size_t original_len = 0;
199fa615556Sabhinav size_t escaped_character_count = 0;
200fa615556Sabhinav size_t offset = 0;
201fa615556Sabhinav size_t newlen;
202fa615556Sabhinav const char *s;
203fa615556Sabhinav char c;
204fa615556Sabhinav size_t s_quoted = 0; /* does the input contain a single quote */
205fa615556Sabhinav size_t d_quoted = 0; /* does the input contain a double quote */
206fa615556Sabhinav char *escaped_str;
207fa615556Sabhinav wchar_t *temp = el->el_line.buffer;
208c3d00a7dSabhinav const char *append_char = NULL;
2097512edc2Schristos
210c93cba3aSabhinav if (filename == NULL)
211c93cba3aSabhinav return NULL;
212fa615556Sabhinav
213fa615556Sabhinav while (temp != el->el_line.cursor) {
214fa615556Sabhinav /*
2157512edc2Schristos * If we see a single quote but have not seen a double quote
2165679adf8Schristos * so far set/unset s_quote, unless it is already quoted
217fa615556Sabhinav */
2185679adf8Schristos if (temp[0] == '\'' && !d_quoted &&
2195679adf8Schristos (temp == el->el_line.buffer || temp[-1] != '\\'))
220fa615556Sabhinav s_quoted = !s_quoted;
221fa615556Sabhinav /*
222fa615556Sabhinav * vice versa to the above condition
223fa615556Sabhinav */
224fa615556Sabhinav else if (temp[0] == '"' && !s_quoted)
225fa615556Sabhinav d_quoted = !d_quoted;
226fa615556Sabhinav temp++;
227fa615556Sabhinav }
228fa615556Sabhinav
229fa615556Sabhinav /* Count number of special characters so that we can calculate
230fa615556Sabhinav * number of extra bytes needed in the new string
231fa615556Sabhinav */
232fa615556Sabhinav for (s = filename; *s; s++, original_len++) {
233fa615556Sabhinav c = *s;
234fa615556Sabhinav /* Inside a single quote only single quotes need escaping */
235fa615556Sabhinav if (s_quoted && c == '\'') {
236fa615556Sabhinav escaped_character_count += 3;
237fa615556Sabhinav continue;
238fa615556Sabhinav }
239fa615556Sabhinav /* Inside double quotes only ", \, ` and $ need escaping */
2407512edc2Schristos if (d_quoted && needs_dquote_escaping(c)) {
241fa615556Sabhinav escaped_character_count++;
242fa615556Sabhinav continue;
243fa615556Sabhinav }
244fa615556Sabhinav if (!s_quoted && !d_quoted && needs_escaping(c))
245fa615556Sabhinav escaped_character_count++;
246fa615556Sabhinav }
247fa615556Sabhinav
248fa615556Sabhinav newlen = original_len + escaped_character_count + 1;
2497512edc2Schristos if (s_quoted || d_quoted)
2507512edc2Schristos newlen++;
2517512edc2Schristos
252c3d00a7dSabhinav if (single_match && app_func)
253c3d00a7dSabhinav newlen++;
254c3d00a7dSabhinav
255fa615556Sabhinav if ((escaped_str = el_malloc(newlen)) == NULL)
256fa615556Sabhinav return NULL;
257fa615556Sabhinav
258fa615556Sabhinav for (s = filename; *s; s++) {
259fa615556Sabhinav c = *s;
260fa615556Sabhinav if (!needs_escaping(c)) {
261fa615556Sabhinav /* no escaping is required continue as usual */
262fa615556Sabhinav escaped_str[offset++] = c;
263fa615556Sabhinav continue;
264fa615556Sabhinav }
265fa615556Sabhinav
266fa615556Sabhinav /* single quotes inside single quotes require special handling */
267fa615556Sabhinav if (c == '\'' && s_quoted) {
268fa615556Sabhinav escaped_str[offset++] = '\'';
269fa615556Sabhinav escaped_str[offset++] = '\\';
270fa615556Sabhinav escaped_str[offset++] = '\'';
271fa615556Sabhinav escaped_str[offset++] = '\'';
272fa615556Sabhinav continue;
273fa615556Sabhinav }
274fa615556Sabhinav
275fa615556Sabhinav /* Otherwise no escaping needed inside single quotes */
276fa615556Sabhinav if (s_quoted) {
277fa615556Sabhinav escaped_str[offset++] = c;
278fa615556Sabhinav continue;
279fa615556Sabhinav }
280fa615556Sabhinav
281fa615556Sabhinav /* No escaping needed inside a double quoted string either
282fa615556Sabhinav * unless we see a '$', '\', '`', or '"' (itself)
283fa615556Sabhinav */
2847512edc2Schristos if (d_quoted && !needs_dquote_escaping(c)) {
285fa615556Sabhinav escaped_str[offset++] = c;
286fa615556Sabhinav continue;
287fa615556Sabhinav }
288fa615556Sabhinav
289fa615556Sabhinav /* If we reach here that means escaping is actually needed */
290fa615556Sabhinav escaped_str[offset++] = '\\';
291fa615556Sabhinav escaped_str[offset++] = c;
292fa615556Sabhinav }
293fa615556Sabhinav
294c3d00a7dSabhinav if (single_match && app_func) {
295c3d00a7dSabhinav escaped_str[offset] = 0;
296b9455193Schristos append_char = app_func(filename);
297c3d00a7dSabhinav /* we want to append space only if we are not inside quotes */
298c3d00a7dSabhinav if (append_char[0] == ' ') {
299c3d00a7dSabhinav if (!s_quoted && !d_quoted)
300c3d00a7dSabhinav escaped_str[offset++] = append_char[0];
301c3d00a7dSabhinav } else
302c3d00a7dSabhinav escaped_str[offset++] = append_char[0];
303c3d00a7dSabhinav }
304c3d00a7dSabhinav
305c3d00a7dSabhinav /* close the quotes if single match and the match is not a directory */
306c3d00a7dSabhinav if (single_match && (append_char && append_char[0] == ' ')) {
307fa615556Sabhinav if (s_quoted)
308fa615556Sabhinav escaped_str[offset++] = '\'';
309fa615556Sabhinav else if (d_quoted)
310fa615556Sabhinav escaped_str[offset++] = '"';
311c3d00a7dSabhinav }
312fa615556Sabhinav
313fa615556Sabhinav escaped_str[offset] = 0;
314fa615556Sabhinav return escaped_str;
315fa615556Sabhinav }
31641a59814Sdsl
31741a59814Sdsl /*
31841a59814Sdsl * return first found file name starting by the ``text'' or NULL if no
31941a59814Sdsl * such file can be found
32041a59814Sdsl * value of ``state'' is ignored
32141a59814Sdsl *
322f0a7346dSsnj * it's the caller's responsibility to free the returned string
32341a59814Sdsl */
3246ddc453eSdsl char *
fn_filename_completion_function(const char * text,int state)32519c38590Schristos fn_filename_completion_function(const char *text, int state)
32641a59814Sdsl {
32741a59814Sdsl static DIR *dir = NULL;
3282e685adeSdsl static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
32941a59814Sdsl static size_t filename_len = 0;
33041a59814Sdsl struct dirent *entry;
33141a59814Sdsl char *temp;
3325679adf8Schristos const char *pos;
33341a59814Sdsl size_t len;
33441a59814Sdsl
33541a59814Sdsl if (state == 0 || dir == NULL) {
3365679adf8Schristos pos = strrchr(text, '/');
3375679adf8Schristos if (pos) {
33841a59814Sdsl char *nptr;
3395679adf8Schristos pos++;
3405679adf8Schristos nptr = el_realloc(filename, (strlen(pos) + 1) *
341a13cd756Schristos sizeof(*nptr));
34241a59814Sdsl if (nptr == NULL) {
343a13cd756Schristos el_free(filename);
344cbd798c9Schristos filename = NULL;
34541a59814Sdsl return NULL;
34641a59814Sdsl }
34741a59814Sdsl filename = nptr;
3485679adf8Schristos (void)strcpy(filename, pos);
3495679adf8Schristos len = (size_t)(pos - text); /* including last slash */
350cbd798c9Schristos
351a13cd756Schristos nptr = el_realloc(dirname, (len + 1) *
352a13cd756Schristos sizeof(*nptr));
35341a59814Sdsl if (nptr == NULL) {
354a13cd756Schristos el_free(dirname);
355cbd798c9Schristos dirname = NULL;
35641a59814Sdsl return NULL;
35741a59814Sdsl }
35841a59814Sdsl dirname = nptr;
359a47ebb18Schristos (void)strlcpy(dirname, text, len + 1);
36041a59814Sdsl } else {
361a13cd756Schristos el_free(filename);
36241a59814Sdsl if (*text == 0)
36341a59814Sdsl filename = NULL;
36441a59814Sdsl else {
36541a59814Sdsl filename = strdup(text);
36641a59814Sdsl if (filename == NULL)
36741a59814Sdsl return NULL;
36841a59814Sdsl }
369a13cd756Schristos el_free(dirname);
37041a59814Sdsl dirname = NULL;
37141a59814Sdsl }
37241a59814Sdsl
37341a59814Sdsl if (dir != NULL) {
37441a59814Sdsl (void)closedir(dir);
37541a59814Sdsl dir = NULL;
37641a59814Sdsl }
3772e685adeSdsl
3782e685adeSdsl /* support for ``~user'' syntax */
379cbd798c9Schristos
380a13cd756Schristos el_free(dirpath);
381cbd798c9Schristos dirpath = NULL;
382cbd798c9Schristos if (dirname == NULL) {
383cbd798c9Schristos if ((dirname = strdup("")) == NULL)
384182beb15Schristos return NULL;
385cbd798c9Schristos dirpath = strdup("./");
386cbd798c9Schristos } else if (*dirname == '~')
38719c38590Schristos dirpath = fn_tilde_expand(dirname);
388182beb15Schristos else
389182beb15Schristos dirpath = strdup(dirname);
390182beb15Schristos
3912e685adeSdsl if (dirpath == NULL)
3922e685adeSdsl return NULL;
393182beb15Schristos
3942e685adeSdsl dir = opendir(dirpath);
39541a59814Sdsl if (!dir)
396b71bed95Schristos return NULL; /* cannot open the directory */
3972e685adeSdsl
3982e685adeSdsl /* will be used in cycle */
3992e685adeSdsl filename_len = filename ? strlen(filename) : 0;
40041a59814Sdsl }
40141a59814Sdsl
40241a59814Sdsl /* find the match */
40341a59814Sdsl while ((entry = readdir(dir)) != NULL) {
40441a59814Sdsl /* skip . and .. */
40541a59814Sdsl if (entry->d_name[0] == '.' && (!entry->d_name[1]
40641a59814Sdsl || (entry->d_name[1] == '.' && !entry->d_name[2])))
40741a59814Sdsl continue;
40841a59814Sdsl if (filename_len == 0)
40941a59814Sdsl break;
41041a59814Sdsl /* otherwise, get first entry where first */
41141a59814Sdsl /* filename_len characters are equal */
41241a59814Sdsl if (entry->d_name[0] == filename[0]
41369a442faSapb #if HAVE_STRUCT_DIRENT_D_NAMLEN
41441a59814Sdsl && entry->d_namlen >= filename_len
41569a442faSapb #else
41669a442faSapb && strlen(entry->d_name) >= filename_len
41741a59814Sdsl #endif
41841a59814Sdsl && strncmp(entry->d_name, filename,
41941a59814Sdsl filename_len) == 0)
42041a59814Sdsl break;
42141a59814Sdsl }
42241a59814Sdsl
42341a59814Sdsl if (entry) { /* match found */
4242e685adeSdsl
42569a442faSapb #if HAVE_STRUCT_DIRENT_D_NAMLEN
4262e685adeSdsl len = entry->d_namlen;
42769a442faSapb #else
42869a442faSapb len = strlen(entry->d_name);
42941a59814Sdsl #endif
43041a59814Sdsl
431a13cd756Schristos len = strlen(dirname) + len + 1;
432113f06a3Schristos temp = el_calloc(len, sizeof(*temp));
4332e685adeSdsl if (temp == NULL)
4342e685adeSdsl return NULL;
435a13cd756Schristos (void)snprintf(temp, len, "%s%s", dirname, entry->d_name);
43641a59814Sdsl } else {
43741a59814Sdsl (void)closedir(dir);
43841a59814Sdsl dir = NULL;
43941a59814Sdsl temp = NULL;
44041a59814Sdsl }
44141a59814Sdsl
442b71bed95Schristos return temp;
44341a59814Sdsl }
44441a59814Sdsl
44541a59814Sdsl
4463cfbfdb2Schristos static const char *
append_char_function(const char * name)4473cfbfdb2Schristos append_char_function(const char *name)
4483cfbfdb2Schristos {
4493cfbfdb2Schristos struct stat stbuf;
45019c38590Schristos char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
4513cfbfdb2Schristos const char *rs = " ";
45241a59814Sdsl
4533cfbfdb2Schristos if (stat(expname ? expname : name, &stbuf) == -1)
4543cfbfdb2Schristos goto out;
4553cfbfdb2Schristos if (S_ISDIR(stbuf.st_mode))
4563cfbfdb2Schristos rs = "/";
4573cfbfdb2Schristos out:
4583cfbfdb2Schristos if (expname)
459a13cd756Schristos el_free(expname);
4603cfbfdb2Schristos return rs;
4613cfbfdb2Schristos }
462*4ee54736Schristos
46341a59814Sdsl /*
46441a59814Sdsl * returns list of completions for text given
46533b05629Schristos * non-static for readline.
46641a59814Sdsl */
46733b05629Schristos char **
completion_matches(const char * text,char * (* genfunc)(const char *,int))46841a59814Sdsl completion_matches(const char *text, char *(*genfunc)(const char *, int))
46941a59814Sdsl {
47041a59814Sdsl char **match_list = NULL, *retstr, *prevstr;
47141a59814Sdsl size_t match_list_len, max_equal, which, i;
47241a59814Sdsl size_t matches;
47341a59814Sdsl
47441a59814Sdsl matches = 0;
47541a59814Sdsl match_list_len = 1;
47641a59814Sdsl while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
47741a59814Sdsl /* allow for list terminator here */
47841a59814Sdsl if (matches + 3 >= match_list_len) {
47941a59814Sdsl char **nmatch_list;
48041a59814Sdsl while (matches + 3 >= match_list_len)
48141a59814Sdsl match_list_len <<= 1;
482a13cd756Schristos nmatch_list = el_realloc(match_list,
483a13cd756Schristos match_list_len * sizeof(*nmatch_list));
48441a59814Sdsl if (nmatch_list == NULL) {
485a13cd756Schristos el_free(match_list);
48641a59814Sdsl return NULL;
48741a59814Sdsl }
48841a59814Sdsl match_list = nmatch_list;
48941a59814Sdsl
49041a59814Sdsl }
49141a59814Sdsl match_list[++matches] = retstr;
49241a59814Sdsl }
49341a59814Sdsl
49441a59814Sdsl if (!match_list)
49541a59814Sdsl return NULL; /* nothing found */
49641a59814Sdsl
49741a59814Sdsl /* find least denominator and insert it to match_list[0] */
49841a59814Sdsl which = 2;
49941a59814Sdsl prevstr = match_list[1];
50041a59814Sdsl max_equal = strlen(prevstr);
50141a59814Sdsl for (; which <= matches; which++) {
50241a59814Sdsl for (i = 0; i < max_equal &&
50341a59814Sdsl prevstr[i] == match_list[which][i]; i++)
50441a59814Sdsl continue;
50541a59814Sdsl max_equal = i;
50641a59814Sdsl }
50741a59814Sdsl
508113f06a3Schristos retstr = el_calloc(max_equal + 1, sizeof(*retstr));
50941a59814Sdsl if (retstr == NULL) {
510a13cd756Schristos el_free(match_list);
51141a59814Sdsl return NULL;
51241a59814Sdsl }
513a47ebb18Schristos (void)strlcpy(retstr, match_list[1], max_equal + 1);
51441a59814Sdsl match_list[0] = retstr;
51541a59814Sdsl
51641a59814Sdsl /* add NULL as last pointer to the array */
5172b8aaed8Splunky match_list[matches + 1] = NULL;
51841a59814Sdsl
519b71bed95Schristos return match_list;
52041a59814Sdsl }
52141a59814Sdsl
52241a59814Sdsl /*
52341a59814Sdsl * Sort function for qsort(). Just wrapper around strcasecmp().
52441a59814Sdsl */
52541a59814Sdsl static int
_fn_qsort_string_compare(const void * i1,const void * i2)52641a59814Sdsl _fn_qsort_string_compare(const void *i1, const void *i2)
52741a59814Sdsl {
52841a59814Sdsl const char *s1 = ((const char * const *)i1)[0];
52941a59814Sdsl const char *s2 = ((const char * const *)i2)[0];
53041a59814Sdsl
53141a59814Sdsl return strcasecmp(s1, s2);
53241a59814Sdsl }
53341a59814Sdsl
53441a59814Sdsl /*
53541a59814Sdsl * Display list of strings in columnar format on readline's output stream.
53692417c82Sdholland * 'matches' is list of strings, 'num' is number of strings in 'matches',
53792417c82Sdholland * 'width' is maximum length of string in 'matches'.
53892417c82Sdholland *
539eb1ab8eeSdholland * matches[0] is not one of the match strings, but it is counted in
540eb1ab8eeSdholland * num, so the strings are matches[1] *through* matches[num-1].
54141a59814Sdsl */
54241a59814Sdsl void
fn_display_match_list(EditLine * el,char ** matches,size_t num,size_t width,const char * (* app_func)(const char *))5433a89c576Sabhinav fn_display_match_list(EditLine * el, char **matches, size_t num, size_t width,
5443a89c576Sabhinav const char *(*app_func) (const char *))
54541a59814Sdsl {
54692417c82Sdholland size_t line, lines, col, cols, thisguy;
547b2105969Schristos int screenwidth = el->el_terminal.t_size.h;
5483a89c576Sabhinav if (app_func == NULL)
5493a89c576Sabhinav app_func = append_char_function;
55041a59814Sdsl
55192417c82Sdholland /* Ignore matches[0]. Avoid 1-based array logic below. */
55292417c82Sdholland matches++;
553eb1ab8eeSdholland num--;
55492417c82Sdholland
55541a59814Sdsl /*
55692417c82Sdholland * Find out how many entries can be put on one line; count
55792417c82Sdholland * with one space between strings the same way it's printed.
55841a59814Sdsl */
559272963aeSchristos cols = (size_t)screenwidth / (width + 2);
56092417c82Sdholland if (cols == 0)
56192417c82Sdholland cols = 1;
56241a59814Sdsl
56392417c82Sdholland /* how many lines of output, rounded up */
56492417c82Sdholland lines = (num + cols - 1) / cols;
56541a59814Sdsl
56692417c82Sdholland /* Sort the items. */
56792417c82Sdholland qsort(matches, num, sizeof(char *), _fn_qsort_string_compare);
56841a59814Sdsl
56992417c82Sdholland /*
57092417c82Sdholland * On the ith line print elements i, i+lines, i+lines*2, etc.
57192417c82Sdholland */
57292417c82Sdholland for (line = 0; line < lines; line++) {
57392417c82Sdholland for (col = 0; col < cols; col++) {
57492417c82Sdholland thisguy = line + col * lines;
57592417c82Sdholland if (thisguy >= num)
57692417c82Sdholland break;
5773a89c576Sabhinav (void)fprintf(el->el_outfile, "%s%s%s",
5783a89c576Sabhinav col == 0 ? "" : " ", matches[thisguy],
579272963aeSchristos (*app_func)(matches[thisguy]));
5803a89c576Sabhinav (void)fprintf(el->el_outfile, "%-*s",
5813a89c576Sabhinav (int) (width - strlen(matches[thisguy])), "");
582b90e7198Schristos }
58341a59814Sdsl (void)fprintf(el->el_outfile, "\n");
58441a59814Sdsl }
58541a59814Sdsl }
58641a59814Sdsl
5872afc179cSabhinav static wchar_t *
find_word_to_complete(const wchar_t * cursor,const wchar_t * buffer,const wchar_t * word_break,const wchar_t * special_prefixes,size_t * length,int do_unescape)5882afc179cSabhinav find_word_to_complete(const wchar_t * cursor, const wchar_t * buffer,
5892d114b3cSabhinav const wchar_t * word_break, const wchar_t * special_prefixes, size_t * length,
5902d114b3cSabhinav int do_unescape)
5912afc179cSabhinav {
5922afc179cSabhinav /* We now look backwards for the start of a filename/variable word */
5932afc179cSabhinav const wchar_t *ctemp = cursor;
5942d114b3cSabhinav wchar_t *temp;
5952afc179cSabhinav size_t len;
5962afc179cSabhinav
5972afc179cSabhinav /* if the cursor is placed at a slash or a quote, we need to find the
5982afc179cSabhinav * word before it
5992afc179cSabhinav */
6002afc179cSabhinav if (ctemp > buffer) {
6012afc179cSabhinav switch (ctemp[-1]) {
6022afc179cSabhinav case '\\':
6032afc179cSabhinav case '\'':
6042afc179cSabhinav case '"':
6052afc179cSabhinav ctemp--;
6062afc179cSabhinav break;
6072afc179cSabhinav default:
608c93cba3aSabhinav break;
6092afc179cSabhinav }
610c93cba3aSabhinav }
611c93cba3aSabhinav
612c93cba3aSabhinav for (;;) {
613c93cba3aSabhinav if (ctemp <= buffer)
614c93cba3aSabhinav break;
615d1647d9cSchristos if (ctemp - buffer >= 2 && ctemp[-2] == '\\' &&
616d1647d9cSchristos needs_escaping(ctemp[-1])) {
617c93cba3aSabhinav ctemp -= 2;
618c93cba3aSabhinav continue;
61952de8937Stih }
620d1647d9cSchristos if (wcschr(word_break, ctemp[-1]))
621c93cba3aSabhinav break;
622c93cba3aSabhinav if (special_prefixes && wcschr(special_prefixes, ctemp[-1]))
623c93cba3aSabhinav break;
6242afc179cSabhinav ctemp--;
625c93cba3aSabhinav }
6262afc179cSabhinav
627c93cba3aSabhinav len = (size_t) (cursor - ctemp);
628c3d00a7dSabhinav if (len == 1 && (ctemp[0] == '\'' || ctemp[0] == '"')) {
629c3d00a7dSabhinav len = 0;
630c3d00a7dSabhinav ctemp++;
631c3d00a7dSabhinav }
6322afc179cSabhinav *length = len;
6332d114b3cSabhinav if (do_unescape) {
634c93cba3aSabhinav wchar_t *unescaped_word = unescape_string(ctemp, len);
635c93cba3aSabhinav if (unescaped_word == NULL)
636c93cba3aSabhinav return NULL;
637c93cba3aSabhinav return unescaped_word;
6382afc179cSabhinav }
6392d114b3cSabhinav temp = el_malloc((len + 1) * sizeof(*temp));
64021a4b1ccSchristos if (temp == NULL)
64121a4b1ccSchristos return NULL;
6422d114b3cSabhinav (void) wcsncpy(temp, ctemp, len);
6432d114b3cSabhinav temp[len] = '\0';
6442d114b3cSabhinav return temp;
6452d114b3cSabhinav }
6462afc179cSabhinav
64741a59814Sdsl /*
64841a59814Sdsl * Complete the word at or before point,
64941a59814Sdsl * 'what_to_do' says what to do with the completion.
65041a59814Sdsl * \t means do standard completion.
65141a59814Sdsl * `?' means list the possible completions.
65241a59814Sdsl * `*' means insert all of the possible completions.
65341a59814Sdsl * `!' means to do standard completion, and list all possible completions if
65441a59814Sdsl * there is more than one.
65541a59814Sdsl *
65641a59814Sdsl * Note: '*' support is not implemented
65741a59814Sdsl * '!' could never be invoked
65841a59814Sdsl */
65941a59814Sdsl int
fn_complete2(EditLine * el,char * (* complete_func)(const char *,int),char ** (* attempted_completion_function)(const char *,int,int),const wchar_t * word_break,const wchar_t * special_prefixes,const char * (* app_func)(const char *),size_t query_items,int * completion_type,int * over,int * point,int * end,unsigned int flags)66065371df8Schristos fn_complete2(EditLine *el,
66165371df8Schristos char *(*complete_func)(const char *, int),
66241a59814Sdsl char **(*attempted_completion_function)(const char *, int, int),
6630594af80Schristos const wchar_t *word_break, const wchar_t *special_prefixes,
6647939d24eSchristos const char *(*app_func)(const char *), size_t query_items,
66565371df8Schristos int *completion_type, int *over, int *point, int *end,
66665371df8Schristos unsigned int flags)
66741a59814Sdsl {
6680aefc7f9Schristos const LineInfoW *li;
6690594af80Schristos wchar_t *temp;
67034e53048Schristos char **matches;
671c93cba3aSabhinav char *completion;
67241a59814Sdsl size_t len;
67341a59814Sdsl int what_to_do = '\t';
67472301cb0Schristos int retval = CC_NORM;
675ba06fc1cSchristos int do_unescape = flags & FN_QUOTE_MATCH;
67641a59814Sdsl
67741a59814Sdsl if (el->el_state.lastcmd == el->el_state.thiscmd)
67841a59814Sdsl what_to_do = '?';
67941a59814Sdsl
68041a59814Sdsl /* readline's rl_complete() has to be told what we did... */
68141a59814Sdsl if (completion_type != NULL)
68241a59814Sdsl *completion_type = what_to_do;
68341a59814Sdsl
68465371df8Schristos if (!complete_func)
68565371df8Schristos complete_func = fn_filename_completion_function;
6863cfbfdb2Schristos if (!app_func)
6873cfbfdb2Schristos app_func = append_char_function;
68841a59814Sdsl
6890aefc7f9Schristos li = el_wline(el);
6902afc179cSabhinav temp = find_word_to_complete(li->cursor,
6912d114b3cSabhinav li->buffer, word_break, special_prefixes, &len, do_unescape);
6923c9fe72fSabhinav if (temp == NULL)
6933c9fe72fSabhinav goto out;
69441a59814Sdsl
69541a59814Sdsl /* these can be used by function called in completion_matches() */
69641a59814Sdsl /* or (*attempted_completion_function)() */
6972dd09931Schristos if (point != NULL)
6985c894153Schristos *point = (int)(li->cursor - li->buffer);
69941a59814Sdsl if (end != NULL)
7005c894153Schristos *end = (int)(li->lastchar - li->buffer);
70141a59814Sdsl
70241a59814Sdsl if (attempted_completion_function) {
7035c894153Schristos int cur_off = (int)(li->cursor - li->buffer);
7043d802cf5Schristos matches = (*attempted_completion_function)(
7053d802cf5Schristos ct_encode_string(temp, &el->el_scratch),
7063d802cf5Schristos cur_off - (int)len, cur_off);
70741a59814Sdsl } else
7082dd09931Schristos matches = NULL;
70941a59814Sdsl if (!attempted_completion_function ||
710262f96a2Schristos (over != NULL && !*over && !matches))
7113d802cf5Schristos matches = completion_matches(
71265371df8Schristos ct_encode_string(temp, &el->el_scratch), complete_func);
71341a59814Sdsl
71441a59814Sdsl if (over != NULL)
71541a59814Sdsl *over = 0;
71641a59814Sdsl
7175e9cbb11Schristos if (matches == NULL) {
7185e9cbb11Schristos goto out;
7195e9cbb11Schristos }
72072301cb0Schristos int i;
7215c894153Schristos size_t matches_num, maxlen, match_len, match_display=1;
722fa615556Sabhinav int single_match = matches[2] == NULL &&
723fa615556Sabhinav (matches[1] == NULL || strcmp(matches[0], matches[1]) == 0);
72441a59814Sdsl
72572301cb0Schristos retval = CC_REFRESH;
726fa615556Sabhinav
727fa615556Sabhinav if (matches[0][0] != '\0') {
728fa615556Sabhinav el_deletestr(el, (int)len);
72965371df8Schristos if (flags & FN_QUOTE_MATCH)
730c3d00a7dSabhinav completion = escape_filename(el, matches[0],
731c3d00a7dSabhinav single_match, app_func);
732e09538bdSabhinav else
733e09538bdSabhinav completion = strdup(matches[0]);
734e09538bdSabhinav if (completion == NULL)
73520fa0b90Schristos goto out2;
7365e9cbb11Schristos
7375e9cbb11Schristos /*
7385e9cbb11Schristos * Replace the completed string with the common part of
7395e9cbb11Schristos * all possible matches if there is a possible completion.
7405e9cbb11Schristos */
7415e9cbb11Schristos el_winsertstr(el,
7425e9cbb11Schristos ct_decode_string(completion, &el->el_scratch));
7435e9cbb11Schristos
744ba06fc1cSchristos if (single_match && attempted_completion_function &&
745ba06fc1cSchristos !(flags & FN_QUOTE_MATCH))
746ba06fc1cSchristos {
7475e9cbb11Schristos /*
7485e9cbb11Schristos * We found an exact match. Add a space after
7495e9cbb11Schristos * it, unless we do filename completion and the
750c3d00a7dSabhinav * object is a directory. Also do necessary
751c3d00a7dSabhinav * escape quoting
752c93cba3aSabhinav */
7535e9cbb11Schristos el_winsertstr(el, ct_decode_string(
7545e9cbb11Schristos (*app_func)(completion), &el->el_scratch));
75541a59814Sdsl }
756c93cba3aSabhinav free(completion);
757fa615556Sabhinav }
75841a59814Sdsl
75941a59814Sdsl
760fa615556Sabhinav if (!single_match && (what_to_do == '!' || what_to_do == '?')) {
76141a59814Sdsl /*
76241a59814Sdsl * More than one match and requested to list possible
76341a59814Sdsl * matches.
76441a59814Sdsl */
76541a59814Sdsl
76641a59814Sdsl for(i = 1, maxlen = 0; matches[i]; i++) {
76741a59814Sdsl match_len = strlen(matches[i]);
76841a59814Sdsl if (match_len > maxlen)
76941a59814Sdsl maxlen = match_len;
77041a59814Sdsl }
77192417c82Sdholland /* matches[1] through matches[i-1] are available */
7723d802cf5Schristos matches_num = (size_t)(i - 1);
77341a59814Sdsl
77441a59814Sdsl /* newline to get on next line from command line */
77541a59814Sdsl (void)fprintf(el->el_outfile, "\n");
77641a59814Sdsl
77741a59814Sdsl /*
77841a59814Sdsl * If there are too many items, ask user for display
77941a59814Sdsl * confirmation.
78041a59814Sdsl */
78141a59814Sdsl if (matches_num > query_items) {
78241a59814Sdsl (void)fprintf(el->el_outfile,
7835c894153Schristos "Display all %zu possibilities? (y or n) ",
78441a59814Sdsl matches_num);
78541a59814Sdsl (void)fflush(el->el_outfile);
78641a59814Sdsl if (getc(stdin) != 'y')
78741a59814Sdsl match_display = 0;
78841a59814Sdsl (void)fprintf(el->el_outfile, "\n");
78941a59814Sdsl }
79041a59814Sdsl
791eb1ab8eeSdholland if (match_display) {
792eb1ab8eeSdholland /*
793eb1ab8eeSdholland * Interface of this function requires the
794eb1ab8eeSdholland * strings be matches[1..num-1] for compat.
795eb1ab8eeSdholland * We have matches_num strings not counting
796eb1ab8eeSdholland * the prefix in matches[0], so we need to
797eb1ab8eeSdholland * add 1 to matches_num for the call.
798eb1ab8eeSdholland */
799eb1ab8eeSdholland fn_display_match_list(el, matches,
8003a89c576Sabhinav matches_num+1, maxlen, app_func);
801eb1ab8eeSdholland }
80241a59814Sdsl retval = CC_REDISPLAY;
80341a59814Sdsl } else if (matches[0][0]) {
80441a59814Sdsl /*
80541a59814Sdsl * There was some common match, but the name was
80641a59814Sdsl * not complete enough. Next tab will print possible
80741a59814Sdsl * completions.
80841a59814Sdsl */
80941a59814Sdsl el_beep(el);
81041a59814Sdsl } else {
81141a59814Sdsl /* lcd is not a valid object - further specification */
81241a59814Sdsl /* is needed */
81341a59814Sdsl el_beep(el);
81441a59814Sdsl retval = CC_NORM;
81541a59814Sdsl }
81641a59814Sdsl
81741a59814Sdsl /* free elements of array and the array itself */
81820fa0b90Schristos out2:
81941a59814Sdsl for (i = 0; matches[i]; i++)
820a13cd756Schristos el_free(matches[i]);
821a13cd756Schristos el_free(matches);
82272301cb0Schristos matches = NULL;
8233c9fe72fSabhinav
8243c9fe72fSabhinav out:
825a13cd756Schristos el_free(temp);
82672301cb0Schristos return retval;
82741a59814Sdsl }
82841a59814Sdsl
82965371df8Schristos int
fn_complete(EditLine * el,char * (* complete_func)(const char *,int),char ** (* attempted_completion_function)(const char *,int,int),const wchar_t * word_break,const wchar_t * special_prefixes,const char * (* app_func)(const char *),size_t query_items,int * completion_type,int * over,int * point,int * end)83065371df8Schristos fn_complete(EditLine *el,
83165371df8Schristos char *(*complete_func)(const char *, int),
83265371df8Schristos char **(*attempted_completion_function)(const char *, int, int),
83365371df8Schristos const wchar_t *word_break, const wchar_t *special_prefixes,
83465371df8Schristos const char *(*app_func)(const char *), size_t query_items,
83565371df8Schristos int *completion_type, int *over, int *point, int *end)
83665371df8Schristos {
83765371df8Schristos return fn_complete2(el, complete_func, attempted_completion_function,
83865371df8Schristos word_break, special_prefixes, app_func, query_items,
83965371df8Schristos completion_type, over, point, end,
84065371df8Schristos attempted_completion_function ? 0 : FN_QUOTE_MATCH);
84165371df8Schristos }
84265371df8Schristos
84341a59814Sdsl /*
84441a59814Sdsl * el-compatible wrapper around rl_complete; needed for key binding
84541a59814Sdsl */
84641a59814Sdsl /* ARGSUSED */
84741a59814Sdsl unsigned char
_el_fn_complete(EditLine * el,int ch)84841a59814Sdsl _el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
84941a59814Sdsl {
85041a59814Sdsl return (unsigned char)fn_complete(el, NULL, NULL,
851c11bd863Schristos break_chars, NULL, NULL, (size_t)100,
85241a59814Sdsl NULL, NULL, NULL, NULL);
85341a59814Sdsl }
8543ce4aa66Schristos
8553ce4aa66Schristos /*
8563ce4aa66Schristos * el-compatible wrapper around rl_complete; needed for key binding
8573ce4aa66Schristos */
8583ce4aa66Schristos /* ARGSUSED */
8593ce4aa66Schristos unsigned char
_el_fn_sh_complete(EditLine * el,int ch)8603ce4aa66Schristos _el_fn_sh_complete(EditLine *el, int ch)
8613ce4aa66Schristos {
8623ce4aa66Schristos return _el_fn_complete(el, ch);
8633ce4aa66Schristos }
864