159243Sobrien/*
259243Sobrien * tw.parse.c: Everyone has taken a shot in this futile effort to
359243Sobrien *	       lexically analyze a csh line... Well we cannot good
459243Sobrien *	       a job as good as sh.lex.c; but we try. Amazing that
559243Sobrien *	       it works considering how many hands have touched this code
659243Sobrien */
759243Sobrien/*-
859243Sobrien * Copyright (c) 1980, 1991 The Regents of the University of California.
959243Sobrien * All rights reserved.
1059243Sobrien *
1159243Sobrien * Redistribution and use in source and binary forms, with or without
1259243Sobrien * modification, are permitted provided that the following conditions
1359243Sobrien * are met:
1459243Sobrien * 1. Redistributions of source code must retain the above copyright
1559243Sobrien *    notice, this list of conditions and the following disclaimer.
1659243Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1759243Sobrien *    notice, this list of conditions and the following disclaimer in the
1859243Sobrien *    documentation and/or other materials provided with the distribution.
19100616Smp * 3. Neither the name of the University nor the names of its contributors
2059243Sobrien *    may be used to endorse or promote products derived from this software
2159243Sobrien *    without specific prior written permission.
2259243Sobrien *
2359243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2459243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2559243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2659243Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2759243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2859243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2959243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3059243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3159243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3259243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3359243Sobrien * SUCH DAMAGE.
3459243Sobrien */
3559243Sobrien#include "sh.h"
3659243Sobrien#include "tw.h"
3759243Sobrien#include "ed.h"
3859243Sobrien#include "tc.h"
3959243Sobrien
40167465Smp#include <assert.h>
41167465Smp
4269408Sache#ifdef WINNT_NATIVE
4359243Sobrien#include "nt.const.h"
4469408Sache#endif /* WINNT_NATIVE */
4559243Sobrien#define EVEN(x) (((x) & 1) != 1)
4659243Sobrien
4759243Sobrien#define DOT_NONE	0	/* Don't display dot files		*/
4859243Sobrien#define DOT_NOT		1	/* Don't display dot or dot-dot		*/
4959243Sobrien#define DOT_ALL		2	/* Display all dot files		*/
5059243Sobrien
5159243Sobrien/*  TW_NONE,	       TW_COMMAND,     TW_VARIABLE,    TW_LOGNAME,	*/
5259243Sobrien/*  TW_FILE,	       TW_DIRECTORY,   TW_VARLIST,     TW_USER,		*/
5359243Sobrien/*  TW_COMPLETION,     TW_ALIAS,       TW_SHELLVAR,    TW_ENVVAR,	*/
5459243Sobrien/*  TW_BINDING,        TW_WORDLIST,    TW_LIMIT,       TW_SIGNAL	*/
5559243Sobrien/*  TW_JOB,	       TW_EXPLAIN,     TW_TEXT,	       TW_GRPNAME	*/
56167465Smpstatic void (*const tw_start_entry[]) (DIR *, const Char *) = {
5759243Sobrien    tw_file_start,     tw_cmd_start,   tw_var_start,   tw_logname_start,
5859243Sobrien    tw_file_start,     tw_file_start,  tw_vl_start,    tw_logname_start,
5959243Sobrien    tw_complete_start, tw_alias_start, tw_var_start,   tw_var_start,
6059243Sobrien    tw_bind_start,     tw_wl_start,    tw_limit_start, tw_sig_start,
6159243Sobrien    tw_job_start,      tw_file_start,  tw_file_start,  tw_grpname_start
6259243Sobrien};
6359243Sobrien
64167465Smpstatic int (*const tw_next_entry[]) (struct Strbuf *, struct Strbuf *,
65167465Smp				     int *) = {
6659243Sobrien    tw_file_next,      tw_cmd_next,    tw_var_next,    tw_logname_next,
6759243Sobrien    tw_file_next,      tw_file_next,   tw_var_next,    tw_logname_next,
6859243Sobrien    tw_var_next,       tw_var_next,    tw_shvar_next,  tw_envvar_next,
6959243Sobrien    tw_bind_next,      tw_wl_next,     tw_limit_next,  tw_sig_next,
7059243Sobrien    tw_job_next,       tw_file_next,   tw_file_next,   tw_grpname_next
7159243Sobrien};
7259243Sobrien
73167465Smpstatic void (*const tw_end_entry[]) (void) = {
7459243Sobrien    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
7559243Sobrien    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
7659243Sobrien    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
7759243Sobrien    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
7859243Sobrien    tw_dir_end,	       tw_dir_end,     tw_dir_end,    tw_grpname_end
7959243Sobrien};
8059243Sobrien
8159243Sobrien/* #define TDEBUG */
8259243Sobrien
8359243Sobrien/* Set to TRUE if recexact is set and an exact match is found
8459243Sobrien * along with other, longer, matches.
8559243Sobrien */
8659243Sobrien
8759243Sobrienint curchoice = -1;
8859243Sobrien
8959243Sobrienint match_unique_match = FALSE;
9059243Sobrienint non_unique_match = FALSE;
91145479Smpstatic int SearchNoDirErr = 0;	/* t_search returns -2 if dir is unreadable */
9259243Sobrien
9359243Sobrien/* state so if a completion is interrupted, the input line doesn't get
9459243Sobrien   nuked */
9559243Sobrienint InsideCompletion = 0;
9659243Sobrien
9759243Sobrien/* do the expand or list on the command line -- SHOULD BE REPLACED */
9859243Sobrien
99167465Smpstatic	void	 extract_dir_and_name	(const Char *, struct Strbuf *,
100167465Smp					 Char **);
101167465Smpstatic	int	 insert_meta		(const Char *, const Char *,
102167465Smp					 const Char *, int);
103167465Smpstatic	int	 tilde			(struct Strbuf *, Char *);
104167465Smpstatic  int      expand_dir		(const Char *, struct Strbuf *, DIR **,
105167465Smp					 COMMAND);
106167465Smpstatic	int	 nostat			(Char *);
107167465Smpstatic	Char	 filetype		(Char *, Char *);
108167465Smpstatic	int	 t_glob			(Char ***, int);
109167465Smpstatic	int	 c_glob			(Char ***);
110167465Smpstatic	int	 is_prefix		(Char *, Char *);
111167465Smpstatic	int	 is_prefixmatch		(Char *, Char *, int);
112167465Smpstatic	int	 is_suffix		(Char *, Char *);
113167465Smpstatic	int	 recognize		(struct Strbuf *, const Char *, size_t,
114167465Smp					 int, int, int);
115167465Smpstatic	int	 ignored		(Char *);
116167465Smpstatic	int	 isadirectory		(const Char *, const Char *);
117167465Smpstatic  int      tw_collect_items	(COMMAND, int, struct Strbuf *,
118167465Smp					 struct Strbuf *, Char *, const Char *,
119167465Smp					 int);
120167465Smpstatic  int      tw_collect		(COMMAND, int, struct Strbuf *,
121167465Smp					 struct Strbuf *, Char *, Char *, int,
122167465Smp					 DIR *);
123231990Smpstatic	Char 	 tw_suffix		(int, struct Strbuf *,const Char *,
124231990Smp					 Char *);
125167465Smpstatic	void 	 tw_fixword		(int, struct Strbuf *, Char *, Char *);
126167465Smpstatic	void	 tw_list_items		(int, int, int);
127167465Smpstatic 	void	 add_scroll_tab		(Char *);
128167465Smpstatic 	void 	 choose_scroll_tab	(struct Strbuf *, int);
129167465Smpstatic	void	 free_scroll_tab	(void);
130167465Smpstatic	int	 find_rows		(Char *[], int, int);
13159243Sobrien
13259243Sobrien#ifdef notdef
13359243Sobrien/*
13459243Sobrien * If we find a set command, then we break a=b to a= and word becomes
13559243Sobrien * b else, we don't break a=b. [don't use that; splits words badly and
13659243Sobrien * messes up tw_complete()]
13759243Sobrien */
13859243Sobrien#define isaset(c, w) ((w)[-1] == '=' && \
13959243Sobrien		      ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
14059243Sobrien		       ((c[3] == ' ' || (c)[3] == '\t'))))
14159243Sobrien#endif
14259243Sobrien
14359243Sobrien/* TRUE if character must be quoted */
14459243Sobrien#define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#')
14559243Sobrien/* TRUE if double quotes don't protect character */
14659243Sobrien#define tricky_dq(w) (cmap(w, _DOL | _QB))
14759243Sobrien
14859243Sobrien/* tenematch():
14959243Sobrien *	Return:
15059243Sobrien *		> 1:    No. of items found
15159243Sobrien *		= 1:    Exactly one match / spelling corrected
15259243Sobrien *		= 0:    No match / spelling was correct
15359243Sobrien *		< 0:    Error (incl spelling correction impossible)
15459243Sobrien */
15559243Sobrienint
156167465Smptenematch(Char *inputline, int num_read, COMMAND command)
15759243Sobrien{
158167465Smp    struct Strbuf qline = Strbuf_INIT;
15959243Sobrien    Char    qu = 0, *pat = STRNULL;
160167465Smp    size_t wp, word, wordp, cmd_start, oword = 0, ocmd_start = 0;
161167465Smp    Char   *str_end, *cp;
162167465Smp    Char   *word_start;
163167465Smp    Char   *oword_start = NULL;
164167465Smp    eChar suf = 0;
16559243Sobrien    int     looking;		/* what we are looking for		*/
16659243Sobrien    int     search_ret;		/* what search returned for debugging 	*/
16759243Sobrien    int     backq = 0;
16859243Sobrien
16959243Sobrien    str_end = &inputline[num_read];
170167465Smp    cleanup_push(&qline, Strbuf_cleanup);
17159243Sobrien
17259243Sobrien    word_start = inputline;
173167465Smp    word = cmd_start = 0;
17459243Sobrien    for (cp = inputline; cp < str_end; cp++) {
17559243Sobrien        if (!cmap(qu, _ESC)) {
17659243Sobrien	    if (cmap(*cp, _QF|_ESC)) {
17759243Sobrien		if (qu == 0 || qu == *cp) {
17859243Sobrien		    qu ^= *cp;
17959243Sobrien		    continue;
18059243Sobrien		}
18159243Sobrien	    }
18259243Sobrien	    if (qu != '\'' && cmap(*cp, _QB)) {
18359243Sobrien		if ((backq ^= 1) != 0) {
18459243Sobrien		    ocmd_start = cmd_start;
18559243Sobrien		    oword_start = word_start;
18659243Sobrien		    oword = word;
18759243Sobrien		    word_start = cp + 1;
188167465Smp		    word = cmd_start = qline.len + 1;
18959243Sobrien		}
19059243Sobrien		else {
19159243Sobrien		    cmd_start = ocmd_start;
19259243Sobrien		    word_start = oword_start;
19359243Sobrien		    word = oword;
19459243Sobrien		}
195167465Smp		Strbuf_append1(&qline, *cp);
19659243Sobrien		continue;
19759243Sobrien	    }
19859243Sobrien	}
19959243Sobrien	if (iscmdmeta(*cp))
200167465Smp	    cmd_start = qline.len + 1;
20159243Sobrien
20259243Sobrien	/* Don't quote '/' to make the recognize stuff work easily */
20359243Sobrien	/* Don't quote '$' in double quotes */
20459243Sobrien
205231990Smp	if (cmap(*cp, _ESC) && cp < str_end - 1 && cp[1] == HIST &&
206231990Smp	    HIST != '\0')
207167465Smp	    Strbuf_append1(&qline, *++cp | QUOTE);
208231990Smp	else if (qu && (tricky(*cp) || *cp == '~') &&
209231990Smp	    !(qu == '\"' && tricky_dq(*cp)))
210167465Smp	    Strbuf_append1(&qline, *cp | QUOTE);
21159243Sobrien	else
212167465Smp	    Strbuf_append1(&qline, *cp);
213167465Smp	if (ismetahash(qline.s[qline.len - 1])
214167465Smp	    /* || isaset(qline.s + cmd_start, qline.s + qline.len) */)
215167465Smp	    word = qline.len, word_start = cp + 1;
21659243Sobrien	if (cmap(qu, _ESC))
21759243Sobrien	    qu = 0;
21859243Sobrien      }
219167465Smp    Strbuf_terminate(&qline);
220167465Smp    wp = qline.len;
22159243Sobrien
22259243Sobrien    /*
22359243Sobrien     *  SPECIAL HARDCODED COMPLETIONS:
22459243Sobrien     *    first word of command       -> TW_COMMAND
22559243Sobrien     *    everything else             -> TW_ZERO
22659243Sobrien     *
22759243Sobrien     */
228167465Smp    looking = starting_a_command(qline.s + word - 1, qline.s) ?
22959243Sobrien	TW_COMMAND : TW_ZERO;
23059243Sobrien
23159243Sobrien    wordp = word;
23259243Sobrien
23359243Sobrien#ifdef TDEBUG
234167465Smp    {
235167465Smp	const Char *p;
236167465Smp
237167465Smp	xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking);
238167465Smp	xprintf("\ncmd_start:%S:\n", qline.s + cmd_start);
239167465Smp	xprintf("qline:%S:\n", qline.s);
240167465Smp	xprintf("qline:");
241167465Smp	for (p = qline.s; *p; p++)
242167465Smp	    xprintf("%c", *p & QUOTE ? '-' : ' ');
243167465Smp	xprintf(":\n");
244167465Smp	xprintf("word:%S:\n", qline.s + word);
245167465Smp	xprintf("word:");
246167465Smp	for (p = qline.s + word; *p; p++)
247167465Smp	    xprintf("%c", *p & QUOTE ? '-' : ' ');
248167465Smp	xprintf(":\n");
249167465Smp    }
25059243Sobrien#endif
25159243Sobrien
25259243Sobrien    if ((looking == TW_COMMAND || looking == TW_ZERO) &&
25359243Sobrien        (command == RECOGNIZE || command == LIST || command == SPELL ||
25459243Sobrien	 command == RECOGNIZE_SCROLL)) {
255167465Smp	Char *p;
256167465Smp
25759243Sobrien#ifdef TDEBUG
25859243Sobrien	xprintf(CGETS(30, 2, "complete %d "), looking);
25959243Sobrien#endif
260167465Smp	p = qline.s + wordp;
261167465Smp	looking = tw_complete(qline.s + cmd_start, &p, &pat, looking, &suf);
262167465Smp	wordp = p - qline.s;
26359243Sobrien#ifdef TDEBUG
26459243Sobrien	xprintf(CGETS(30, 3, "complete %d %S\n"), looking, pat);
26559243Sobrien#endif
26659243Sobrien    }
26759243Sobrien
26859243Sobrien    switch (command) {
269167465Smp	Char   *bptr;
27059243Sobrien	Char   *items[2], **ptr;
27159243Sobrien	int     i, count;
27259243Sobrien
27359243Sobrien    case RECOGNIZE:
27459243Sobrien    case RECOGNIZE_SCROLL:
275167465Smp    case RECOGNIZE_ALL: {
276167465Smp	struct Strbuf wordbuf = Strbuf_INIT;
277167465Smp	Char   *slshp;
278167465Smp
27959243Sobrien	if (adrof(STRautocorrect)) {
280167465Smp	    if ((slshp = Strrchr(qline.s + wordp, '/')) != NULL &&
281167465Smp		slshp[1] != '\0') {
28259243Sobrien		SearchNoDirErr = 1;
283167465Smp		for (bptr = qline.s + wordp; bptr < slshp; bptr++) {
28459243Sobrien		    /*
28559243Sobrien		     * do not try to correct spelling of words containing
28659243Sobrien		     * globbing characters
28759243Sobrien		     */
28859243Sobrien		    if (isglob(*bptr)) {
28959243Sobrien			SearchNoDirErr = 0;
29059243Sobrien			break;
29159243Sobrien		    }
29259243Sobrien		}
29359243Sobrien	    }
29459243Sobrien	}
29559243Sobrien	else
29659243Sobrien	    slshp = STRNULL;
297167465Smp	Strbuf_append(&wordbuf, qline.s + wordp);
298167465Smp	Strbuf_terminate(&wordbuf);
299167465Smp	cleanup_push(&wordbuf, Strbuf_cleanup);
300167465Smp	search_ret = t_search(&wordbuf, command, looking, 1, pat, suf);
301167465Smp	qline.len = wordp;
302167465Smp	Strbuf_append(&qline, wordbuf.s);
303167465Smp	Strbuf_terminate(&qline);
304167465Smp	cleanup_until(&wordbuf);
30559243Sobrien	SearchNoDirErr = 0;
30659243Sobrien
30759243Sobrien	if (search_ret == -2) {
308167465Smp	    Char *rword;
30959243Sobrien
310167465Smp	    rword = Strsave(slshp);
311167465Smp	    cleanup_push(rword, xfree);
31259243Sobrien	    if (slshp != STRNULL)
31359243Sobrien		*slshp = '\0';
314167465Smp	    wordbuf = Strbuf_init;
315167465Smp	    Strbuf_append(&wordbuf, qline.s + wordp);
316167465Smp	    Strbuf_terminate(&wordbuf);
317167465Smp	    cleanup_push(&wordbuf, Strbuf_cleanup);
318167465Smp	    search_ret = spell_me(&wordbuf, looking, pat, suf);
31959243Sobrien	    if (search_ret == 1) {
320167465Smp		Strbuf_append(&wordbuf, rword);
321167465Smp		Strbuf_terminate(&wordbuf);
322167465Smp		wp = wordp + wordbuf.len;
323167465Smp		search_ret = t_search(&wordbuf, command, looking, 1, pat, suf);
32459243Sobrien	    }
325167465Smp	    qline.len = wordp;
326167465Smp	    Strbuf_append(&qline, wordbuf.s);
327167465Smp	    Strbuf_terminate(&qline);
328167465Smp	    cleanup_until(rword);
32959243Sobrien	}
330167465Smp	if (qline.s[wp] != '\0' &&
331167465Smp	    insert_meta(word_start, str_end, qline.s + word, !qu) < 0)
332167465Smp	    goto err;		/* error inserting */
333167465Smp	break;
334167465Smp    }
33559243Sobrien
336167465Smp    case SPELL: {
337167465Smp	struct Strbuf wordbuf = Strbuf_INIT;
338167465Smp
33959243Sobrien	for (bptr = word_start; bptr < str_end; bptr++) {
34059243Sobrien	    /*
34159243Sobrien	     * do not try to correct spelling of words containing globbing
34259243Sobrien	     * characters
34359243Sobrien	     */
344167465Smp	    if (isglob(*bptr)) {
345167465Smp		search_ret = 0;
346167465Smp		goto end;
347167465Smp	    }
34859243Sobrien	}
349167465Smp	Strbuf_append(&wordbuf, qline.s + wordp);
350167465Smp	Strbuf_terminate(&wordbuf);
351167465Smp	cleanup_push(&wordbuf, Strbuf_cleanup);
352231990Smp
353231990Smp	/*
354231990Smp	 * Don't try to spell things that we know they are correct.
355231990Smp	 * Trying to spell can hang when we have NFS mounted hung
356231990Smp	 * volumes.
357231990Smp	 */
358231990Smp	if ((looking == TW_COMMAND) && Strchr(wordbuf.s, '/') != NULL) {
359231990Smp	    if (executable(NULL, wordbuf.s, 0)) {
360231990Smp		cleanup_until(&wordbuf);
361231990Smp		search_ret = 0;
362231990Smp		goto end;
363231990Smp	    }
364231990Smp	}
365231990Smp
366167465Smp	search_ret = spell_me(&wordbuf, looking, pat, suf);
367167465Smp	qline.len = wordp;
368167465Smp	Strbuf_append(&qline, wordbuf.s);
369167465Smp	Strbuf_terminate(&qline);
370167465Smp	cleanup_until(&wordbuf);
37159243Sobrien	if (search_ret == 1) {
372167465Smp	    if (insert_meta(word_start, str_end, qline.s + word, !qu) < 0)
373167465Smp		goto err;		/* error inserting */
37459243Sobrien	}
375167465Smp	break;
376167465Smp    }
37759243Sobrien
37859243Sobrien    case PRINT_HELP:
379167465Smp	do_help(qline.s + cmd_start);
380167465Smp	search_ret = 1;
381167465Smp	break;
38259243Sobrien
38359243Sobrien    case GLOB:
38459243Sobrien    case GLOB_EXPAND:
385167465Smp	items[0] = Strsave(qline.s + wordp);
38659243Sobrien	items[1] = NULL;
387167465Smp	cleanup_push(items[0], xfree);
38859243Sobrien	ptr = items;
389167465Smp	count = (looking == TW_COMMAND && Strchr(qline.s + wordp, '/') == 0) ?
390167465Smp		c_glob(&ptr) :
39159243Sobrien		t_glob(&ptr, looking == TW_COMMAND);
392167465Smp	cleanup_until(items[0]);
393167465Smp	if (ptr != items)
394167465Smp	    cleanup_push(ptr, blk_cleanup);
39559243Sobrien	if (count > 0) {
39659243Sobrien	    if (command == GLOB)
39759243Sobrien		print_by_column(STRNULL, ptr, count, 0);
39859243Sobrien	    else {
39959243Sobrien		DeleteBack(str_end - word_start);/* get rid of old word */
40059243Sobrien		for (i = 0; i < count; i++)
40159243Sobrien		    if (ptr[i] && *ptr[i]) {
40259243Sobrien			(void) quote(ptr[i]);
40359243Sobrien			if (insert_meta(0, 0, ptr[i], 0) < 0 ||
40459243Sobrien			    InsertStr(STRspace) < 0) {
405167465Smp			    if (ptr != items)
406167465Smp				cleanup_until(ptr);
407167465Smp			    goto err;		/* error inserting */
40859243Sobrien			}
40959243Sobrien		    }
41059243Sobrien	    }
41159243Sobrien	}
412167465Smp	if (ptr != items)
413167465Smp	    cleanup_until(ptr);
414167465Smp	search_ret = count;
415167465Smp	break;
41659243Sobrien
41759243Sobrien    case VARS_EXPAND:
418167465Smp	bptr = dollar(qline.s + word);
419167465Smp	if (bptr != NULL) {
420167465Smp	    if (insert_meta(word_start, str_end, bptr, !qu) < 0) {
421167465Smp		xfree(bptr);
422167465Smp		goto err;		/* error inserting */
423167465Smp	    }
424167465Smp	    xfree(bptr);
425167465Smp	    search_ret = 1;
426167465Smp	    break;
42759243Sobrien	}
428167465Smp	search_ret = 0;
429167465Smp	break;
43059243Sobrien
43159243Sobrien    case PATH_NORMALIZE:
432167465Smp	if ((bptr = dnormalize(qline.s + wordp, symlinks == SYM_IGNORE ||
433167465Smp			       symlinks == SYM_EXPAND)) != NULL) {
434167465Smp	    if (insert_meta(word_start, str_end, bptr, !qu) < 0) {
435167465Smp		xfree(bptr);
436167465Smp		goto err;		/* error inserting */
437167465Smp	    }
438167465Smp	    xfree(bptr);
439167465Smp	    search_ret = 1;
440167465Smp	    break;
44159243Sobrien	}
442167465Smp	search_ret = 0;
443167465Smp	break;
44459243Sobrien
445167465Smp    case COMMAND_NORMALIZE: {
446167465Smp	Char *p;
447167465Smp	int found;
44859243Sobrien
449195609Smp	found = cmd_expand(qline.s + wordp, &p);
450167465Smp
451167465Smp	if (!found) {
452167465Smp	    xfree(p);
453167465Smp	    search_ret = 0;
454167465Smp	    break;
455167465Smp	}
456167465Smp	if (insert_meta(word_start, str_end, p, !qu) < 0) {
457167465Smp	    xfree(p);
458167465Smp	    goto err;		/* error inserting */
459167465Smp	}
460167465Smp	xfree(p);
461167465Smp	search_ret = 1;
462167465Smp	break;
463167465Smp    }
464167465Smp
46559243Sobrien    case LIST:
466167465Smp    case LIST_ALL: {
467167465Smp	struct Strbuf wordbuf = Strbuf_INIT;
46859243Sobrien
469167465Smp	Strbuf_append(&wordbuf, qline.s + wordp);
470167465Smp	Strbuf_terminate(&wordbuf);
471167465Smp	cleanup_push(&wordbuf, Strbuf_cleanup);
472167465Smp	search_ret = t_search(&wordbuf, LIST, looking, 1, pat, suf);
473167465Smp	qline.len = wordp;
474167465Smp	Strbuf_append(&qline, wordbuf.s);
475167465Smp	Strbuf_terminate(&qline);
476167465Smp	cleanup_until(&wordbuf);
477167465Smp	break;
478167465Smp    }
479167465Smp
48059243Sobrien    default:
48159243Sobrien	xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname);
482167465Smp	search_ret = 1;
483167465Smp    }
484167465Smp end:
485167465Smp    cleanup_until(&qline);
486167465Smp    return search_ret;
48759243Sobrien
488167465Smp err:
489167465Smp    cleanup_until(&qline);
490167465Smp    return -1;
49159243Sobrien} /* end tenematch */
49259243Sobrien
49359243Sobrien
49459243Sobrien/* t_glob():
49559243Sobrien * 	Return a list of files that match the pattern
49659243Sobrien */
49759243Sobrienstatic int
498167465Smpt_glob(Char ***v, int cmd)
49959243Sobrien{
50059243Sobrien    jmp_buf_t osetexit;
501167465Smp    int gflag;
50259243Sobrien
50359243Sobrien    if (**v == 0)
50459243Sobrien	return (0);
505167465Smp    gflag = tglob(*v);
50659243Sobrien    if (gflag) {
507167465Smp	size_t omark;
508167465Smp
50959243Sobrien	getexit(osetexit);	/* make sure to come back here */
510167465Smp	omark = cleanup_push_mark();
51159243Sobrien	if (setexit() == 0)
512167465Smp	    *v = globall(*v, gflag);
513167465Smp	cleanup_pop_mark(omark);
51459243Sobrien	resexit(osetexit);
51559243Sobrien	if (haderr) {
51659243Sobrien	    haderr = 0;
51759243Sobrien	    NeedsRedraw = 1;
51859243Sobrien	    return (-1);
51959243Sobrien	}
52059243Sobrien	if (*v == 0)
52159243Sobrien	    return (0);
52259243Sobrien    }
52359243Sobrien    else
52459243Sobrien	return (0);
52559243Sobrien
52659243Sobrien    if (cmd) {
52759243Sobrien	Char **av = *v, *p;
528167465Smp	int fwd, i;
52959243Sobrien
530167465Smp	for (i = 0, fwd = 0; av[i] != NULL; i++)
53159243Sobrien	    if (!executable(NULL, av[i], 0)) {
53259243Sobrien		fwd++;
53359243Sobrien		p = av[i];
53459243Sobrien		av[i] = NULL;
535167465Smp		xfree(p);
53659243Sobrien	    }
53759243Sobrien	    else if (fwd)
53859243Sobrien		av[i - fwd] = av[i];
53959243Sobrien
54059243Sobrien	if (fwd)
54159243Sobrien	    av[i - fwd] = av[i];
54259243Sobrien    }
54359243Sobrien
544167465Smp    return blklen(*v);
54559243Sobrien} /* end t_glob */
54659243Sobrien
54759243Sobrien
54859243Sobrien/* c_glob():
54959243Sobrien * 	Return a list of commands that match the pattern
55059243Sobrien */
55159243Sobrienstatic int
552167465Smpc_glob(Char ***v)
55359243Sobrien{
554167465Smp    struct blk_buf av = BLK_BUF_INIT;
555167465Smp    struct Strbuf cmd = Strbuf_INIT, dir = Strbuf_INIT;
556167465Smp    Char *pat = **v;
557167465Smp    int flag;
55859243Sobrien
55959243Sobrien    if (pat == NULL)
56059243Sobrien	return (0);
56159243Sobrien
562167465Smp    cleanup_push(&av, bb_cleanup);
563167465Smp    cleanup_push(&cmd, Strbuf_cleanup);
564167465Smp    cleanup_push(&dir, Strbuf_cleanup);
56559243Sobrien
56659243Sobrien    tw_cmd_start(NULL, NULL);
567167465Smp    while (cmd.len = 0, tw_cmd_next(&cmd, &dir, &flag) != 0) {
568167465Smp	Strbuf_terminate(&cmd);
569167465Smp	if (Gmatch(cmd.s, pat))
570167465Smp	    bb_append(&av, Strsave(cmd.s));
571167465Smp    }
57259243Sobrien    tw_dir_end();
573167465Smp    *v = bb_finish(&av);
574167465Smp    cleanup_ignore(&av);
575167465Smp    cleanup_until(&av);
57659243Sobrien
577167465Smp    return av.len;
57859243Sobrien} /* end c_glob */
57959243Sobrien
58059243Sobrien
58159243Sobrien/* insert_meta():
58259243Sobrien *      change the word before the cursor.
58359243Sobrien *        cp must point to the start of the unquoted word.
58459243Sobrien *        cpend to the end of it.
58559243Sobrien *        word is the text that has to be substituted.
58659243Sobrien *      strategy:
58759243Sobrien *        try to keep all the quote characters of the user's input.
58859243Sobrien *        change quote type only if necessary.
58959243Sobrien */
59059243Sobrienstatic int
591167465Smpinsert_meta(const Char *cp, const Char *cpend, const Char *word,
592167465Smp	    int closequotes)
59359243Sobrien{
594167465Smp    struct Strbuf buffer = Strbuf_INIT;
595167465Smp    Char *bptr;
596167465Smp    const Char *wptr;
59759243Sobrien    int in_sync = (cp != NULL);
598145479Smp    Char qu = 0;
59959243Sobrien    int ndel = (int) (cp ? cpend - cp : 0);
60059243Sobrien    Char w, wq;
601167465Smp    int res;
60259243Sobrien
603167465Smp    for (wptr = word;;) {
60459243Sobrien	if (cp >= cpend)
60559243Sobrien	    in_sync = 0;
60659243Sobrien	if (in_sync && !cmap(qu, _ESC) && cmap(*cp, _QF|_ESC))
60759243Sobrien	    if (qu == 0 || qu == *cp) {
60859243Sobrien		qu ^= *cp;
609167465Smp		Strbuf_append1(&buffer, *cp++);
61059243Sobrien		continue;
61159243Sobrien	    }
61259243Sobrien	w = *wptr;
61359243Sobrien	if (w == 0)
61459243Sobrien	    break;
61559243Sobrien
61659243Sobrien	wq = w & QUOTE;
617316957Sdchagin#if INVALID_BYTE != 0
618316957Sdchagin	/* add checking INVALID_BYTE for FIX UTF32 */
619316957Sdchagin	if ((w & INVALID_BYTE) != INVALID_BYTE)		/* w < INVALID_BYTE */
620316957Sdchagin#endif
621316957Sdchagin	    w &= ~QUOTE;
62259243Sobrien
62359243Sobrien	if (cmap(w, _ESC | _QF))
62459243Sobrien	    wq = QUOTE;		/* quotes are always quoted */
62559243Sobrien
62659243Sobrien	if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) {
62759243Sobrien	    /* We have to unquote the character */
62859243Sobrien	    in_sync = 0;
62959243Sobrien	    if (cmap(qu, _ESC))
630167465Smp		buffer.s[buffer.len - 1] = w;
63159243Sobrien	    else {
632167465Smp		Strbuf_append1(&buffer, qu);
633167465Smp		Strbuf_append1(&buffer, w);
63459243Sobrien		if (wptr[1] == 0)
63559243Sobrien		    qu = 0;
63659243Sobrien		else
637167465Smp		    Strbuf_append1(&buffer, qu);
63859243Sobrien	    }
63959243Sobrien	} else if (qu && w == qu) {
64059243Sobrien	    in_sync = 0;
641167465Smp	    if (buffer.len != 0 && buffer.s[buffer.len - 1] == qu) {
64259243Sobrien		/* User misunderstanding :) */
643167465Smp		buffer.s[buffer.len - 1] = '\\';
644167465Smp		Strbuf_append1(&buffer, w);
64559243Sobrien		qu = 0;
64659243Sobrien	    } else {
647167465Smp		Strbuf_append1(&buffer, qu);
648167465Smp		Strbuf_append1(&buffer, '\\');
649167465Smp		Strbuf_append1(&buffer, w);
650167465Smp		Strbuf_append1(&buffer, qu);
65159243Sobrien	    }
65259243Sobrien	}
65359243Sobrien	else if (wq && qu == '\"' && tricky_dq(w)) {
65459243Sobrien	    in_sync = 0;
655167465Smp	    Strbuf_append1(&buffer, qu);
656167465Smp	    Strbuf_append1(&buffer, '\\');
657167465Smp	    Strbuf_append1(&buffer, w);
658167465Smp	    Strbuf_append1(&buffer, qu);
659167465Smp	} else if (wq &&
660231990Smp		   ((!qu && (tricky(w) || (w == HISTSUB && HISTSUB != '\0'
661231990Smp		       && buffer.len == 0))) ||
662231990Smp		    (!cmap(qu, _ESC) && w == HIST && HIST != '\0'))) {
66359243Sobrien	    in_sync = 0;
664167465Smp	    Strbuf_append1(&buffer, '\\');
665167465Smp	    Strbuf_append1(&buffer, w);
66659243Sobrien	} else {
66759243Sobrien	    if (in_sync && *cp++ != w)
66859243Sobrien		in_sync = 0;
669167465Smp	    Strbuf_append1(&buffer, w);
67059243Sobrien	}
67159243Sobrien	wptr++;
67259243Sobrien	if (cmap(qu, _ESC))
67359243Sobrien	    qu = 0;
67459243Sobrien    }
67559243Sobrien    if (closequotes && qu && !cmap(qu, _ESC))
676167465Smp	Strbuf_append1(&buffer, w);
677167465Smp    bptr = Strbuf_finish(&buffer);
67859243Sobrien    if (ndel)
67959243Sobrien	DeleteBack(ndel);
680167465Smp    res = InsertStr(bptr);
681167465Smp    xfree(bptr);
682167465Smp    return res;
68359243Sobrien} /* end insert_meta */
68459243Sobrien
68559243Sobrien
68659243Sobrien
68759243Sobrien/* is_prefix():
68859243Sobrien *	return true if check matches initial chars in template
68959243Sobrien *	This differs from PWB imatch in that if check is null
69059243Sobrien *	it matches anything
69159243Sobrien */
69259243Sobrienstatic int
693167465Smpis_prefix(Char *check, Char *template)
69459243Sobrien{
69559243Sobrien    for (; *check; check++, template++)
69659243Sobrien	if ((*check & TRIM) != (*template & TRIM))
69759243Sobrien	    return (FALSE);
69859243Sobrien    return (TRUE);
69959243Sobrien} /* end is_prefix */
70059243Sobrien
70159243Sobrien
70259243Sobrien/* is_prefixmatch():
70359243Sobrien *	return true if check matches initial chars in template
70459243Sobrien *	This differs from PWB imatch in that if check is null
70559243Sobrien *	it matches anything
70659243Sobrien * and matches on shortening of commands
70759243Sobrien */
70859243Sobrienstatic int
709231990Smpis_prefixmatch(Char *check, Char *template, int enhanced)
71059243Sobrien{
711231990Smp    Char MCH1, MCH2, LCH1, LCH2;
71259243Sobrien
71359243Sobrien    for (; *check; check++, template++) {
71459243Sobrien	if ((*check & TRIM) != (*template & TRIM)) {
715231990Smp	    MCH1 = (*check & TRIM);
716231990Smp	    MCH2 = (*template & TRIM);
717231990Smp            LCH1 = Isupper(MCH1) ? Tolower(MCH1) :
718231990Smp		enhanced == 2 && MCH1 == '_' ? '-' : MCH1;
719231990Smp            LCH2 = Isupper(MCH2) ? Tolower(MCH2) :
720231990Smp		enhanced == 2 && MCH2 == '_' ? '-' : MCH2;
721231990Smp	    if (MCH1 != MCH2 && MCH1 != LCH2 &&
722231990Smp		(LCH1 != MCH2 || enhanced == 2)) {
723231990Smp		if (enhanced && ((*check & TRIM) == '-' ||
72459243Sobrien				 (*check & TRIM) == '.' ||
72559243Sobrien				 (*check & TRIM) == '_')) {
726231990Smp		    MCH1 = MCH2 = (*check & TRIM);
727231990Smp		    if (MCH1 == '_' && enhanced != 2) {
728231990Smp			MCH2 = '-';
729231990Smp		    } else if (MCH1 == '-') {
730231990Smp			MCH2 = '_';
731231990Smp		    }
732231990Smp		    for (; *template && (*template & TRIM) != MCH1 &&
733231990Smp					(*template & TRIM) != MCH2; template++)
73459243Sobrien			continue;
735231990Smp		    if (!*template) {
73659243Sobrien	                return (FALSE);
737231990Smp		    }
738231990Smp		} else {
739231990Smp		    return (FALSE);
740231990Smp		}
741231990Smp	    }
742231990Smp	}
74359243Sobrien    }
74459243Sobrien    return (TRUE);
74559243Sobrien} /* end is_prefixmatch */
74659243Sobrien
74759243Sobrien
74859243Sobrien/* is_suffix():
74959243Sobrien *	Return true if the chars in template appear at the
75059243Sobrien *	end of check, I.e., are it's suffix.
75159243Sobrien */
75259243Sobrienstatic int
753167465Smpis_suffix(Char *check, Char *template)
75459243Sobrien{
755145479Smp    Char *t, *c;
75659243Sobrien
757167465Smp    t = Strend(template);
758167465Smp    c = Strend(check);
75959243Sobrien    for (;;) {
76059243Sobrien	if (t == template)
76159243Sobrien	    return 1;
76259243Sobrien	if (c == check || (*--t & TRIM) != (*--c & TRIM))
76359243Sobrien	    return 0;
76459243Sobrien    }
76559243Sobrien} /* end is_suffix */
76659243Sobrien
76759243Sobrien
76859243Sobrien/* ignored():
76959243Sobrien *	Return true if this is an ignored item
77059243Sobrien */
77159243Sobrienstatic int
772167465Smpignored(Char *item)
77359243Sobrien{
77459243Sobrien    struct varent *vp;
775145479Smp    Char **cp;
77659243Sobrien
77759243Sobrien    if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
77859243Sobrien	return (FALSE);
77959243Sobrien    for (; *cp != NULL; cp++)
78059243Sobrien	if (is_suffix(item, *cp))
78159243Sobrien	    return (TRUE);
78259243Sobrien    return (FALSE);
78359243Sobrien} /* end ignored */
78459243Sobrien
78559243Sobrien
78659243Sobrien
78759243Sobrien/* starting_a_command():
78859243Sobrien *	return true if the command starting at wordstart is a command
78959243Sobrien */
79059243Sobrienint
791167465Smpstarting_a_command(Char *wordstart, Char *inputline)
79259243Sobrien{
793145479Smp    Char *ptr, *ncmdstart;
794145479Smp    int     count, bsl;
79559243Sobrien    static  Char
79659243Sobrien            cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
79759243Sobrien            cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
79859243Sobrien
79959243Sobrien    /*
80059243Sobrien     * Find if the number of backquotes is odd or even.
80159243Sobrien     */
80259243Sobrien    for (ptr = wordstart, count = 0;
80359243Sobrien	 ptr >= inputline;
80459243Sobrien	 count += (*ptr-- == '`'))
80559243Sobrien	continue;
80659243Sobrien    /*
80759243Sobrien     * if the number of backquotes is even don't include the backquote char in
80859243Sobrien     * the list of command starting delimiters [if it is zero, then it does not
80959243Sobrien     * matter]
81059243Sobrien     */
81159243Sobrien    ncmdstart = cmdstart + EVEN(count);
81259243Sobrien
81359243Sobrien    /*
81459243Sobrien     * look for the characters previous to this word if we find a command
81559243Sobrien     * starting delimiter we break. if we find whitespace and another previous
81659243Sobrien     * word then we are not a command
81759243Sobrien     *
81859243Sobrien     * count is our state machine: 0 looking for anything 1 found white-space
81959243Sobrien     * looking for non-ws
82059243Sobrien     */
82159243Sobrien    for (count = 0; wordstart >= inputline; wordstart--) {
82259243Sobrien	if (*wordstart == '\0')
82359243Sobrien	    continue;
824145479Smp	if (Strchr(ncmdstart, *wordstart)) {
825145479Smp	    for (ptr = wordstart, bsl = 0; *(--ptr) == '\\'; bsl++);
826145479Smp	    if (bsl & 1) {
827145479Smp		wordstart--;
828145479Smp		continue;
829145479Smp	    } else
830145479Smp		break;
831145479Smp	}
83259243Sobrien	/*
83359243Sobrien	 * found white space
83459243Sobrien	 */
83559243Sobrien	if ((ptr = Strchr(cmdalive, *wordstart)) != NULL)
83659243Sobrien	    count = 1;
83759243Sobrien	if (count == 1 && !ptr)
83859243Sobrien	    return (FALSE);
83959243Sobrien    }
84059243Sobrien
84159243Sobrien    if (wordstart > inputline)
84259243Sobrien	switch (*wordstart) {
84359243Sobrien	case '&':		/* Look for >& */
84459243Sobrien	    while (wordstart > inputline &&
84559243Sobrien		   (*--wordstart == ' ' || *wordstart == '\t'))
84659243Sobrien		continue;
84759243Sobrien	    if (*wordstart == '>')
84859243Sobrien		return (FALSE);
84959243Sobrien	    break;
85059243Sobrien	case '(':		/* check for foreach, if etc. */
85159243Sobrien	    while (wordstart > inputline &&
85259243Sobrien		   (*--wordstart == ' ' || *wordstart == '\t'))
85359243Sobrien		continue;
85459243Sobrien	    if (!iscmdmeta(*wordstart) &&
85559243Sobrien		(*wordstart != ' ' && *wordstart != '\t'))
85659243Sobrien		return (FALSE);
85759243Sobrien	    break;
85859243Sobrien	default:
85959243Sobrien	    break;
86059243Sobrien	}
86159243Sobrien    return (TRUE);
86259243Sobrien} /* end starting_a_command */
86359243Sobrien
86459243Sobrien
86559243Sobrien/* recognize():
86659243Sobrien *	Object: extend what user typed up to an ambiguity.
86759243Sobrien *	Algorithm:
86859243Sobrien *	On first match, copy full item (assume it'll be the only match)
86959243Sobrien *	On subsequent matches, shorten exp_name to the first
87059243Sobrien *	character mismatch between exp_name and item.
87159243Sobrien *	If we shorten it back to the prefix length, stop searching.
87259243Sobrien */
87359243Sobrienstatic int
874167465Smprecognize(struct Strbuf *exp_name, const Char *item, size_t name_length,
875167465Smp	  int numitems, int enhanced, int igncase)
87659243Sobrien{
877231990Smp    Char MCH1, MCH2, LCH1, LCH2;
878167465Smp    Char *x;
879167465Smp    const Char *ent;
880167465Smp    size_t len = 0;
88159243Sobrien
88259243Sobrien    if (numitems == 1) {	/* 1st match */
883167465Smp	exp_name->len = 0;
884167465Smp	Strbuf_append(exp_name, item);
885167465Smp	Strbuf_terminate(exp_name);
88659243Sobrien	return (0);
88759243Sobrien    }
888131962Smp    if (!enhanced && !igncase) {
889167465Smp	for (x = exp_name->s, ent = item; *x && (*x & TRIM) == (*ent & TRIM);
890167465Smp	     x++, ent++)
89159243Sobrien	    len++;
89259243Sobrien    } else {
893167465Smp	for (x = exp_name->s, ent = item; *x; x++, ent++) {
89459243Sobrien	    MCH1 = *x & TRIM;
89559243Sobrien	    MCH2 = *ent & TRIM;
896231990Smp	    LCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1;
897231990Smp	    LCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2;
898231990Smp	    if (MCH1 != MCH2) {
899231990Smp		if (LCH1 == MCH2 || (MCH1 == '_' && MCH2 == '-'))
900231990Smp		    *x = *ent;
901231990Smp		else if (LCH1 != LCH2)
902231990Smp		    break;
903231990Smp	    }
90459243Sobrien	    len++;
90559243Sobrien	}
90659243Sobrien    }
90759243Sobrien    *x = '\0';		/* Shorten at 1st char diff */
908167465Smp    exp_name->len = x - exp_name->s;
909231990Smp    if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) &&
910231990Smp	len == name_length)	/* Ambiguous to prefix? */
91159243Sobrien	return (-1);	/* So stop now and save time */
91259243Sobrien    return (0);
91359243Sobrien} /* end recognize */
91459243Sobrien
91559243Sobrien
91659243Sobrien/* tw_collect_items():
91759243Sobrien *	Collect items that match target.
91859243Sobrien *	SPELL command:
91959243Sobrien *		Returns the spelling distance of the closest match.
92059243Sobrien *	else
92159243Sobrien *		Returns the number of items found.
92259243Sobrien *		If none found, but some ignored items were found,
92359243Sobrien *		It returns the -number of ignored items.
92459243Sobrien */
92559243Sobrienstatic int
926167465Smptw_collect_items(COMMAND command, int looking, struct Strbuf *exp_dir,
927167465Smp		 struct Strbuf *exp_name, Char *target, const Char *pat,
928167465Smp		 int flags)
92959243Sobrien{
93059243Sobrien    int done = FALSE;			 /* Search is done */
93159243Sobrien    int showdots;			 /* Style to show dot files */
93259243Sobrien    int nignored = 0;			 /* Number of fignored items */
93359243Sobrien    int numitems = 0;			 /* Number of matched items */
934167465Smp    size_t name_length = Strlen(target); /* Length of prefix (file name) */
93559243Sobrien    int exec_check = flags & TW_EXEC_CHK;/* need to check executability	*/
93659243Sobrien    int dir_check  = flags & TW_DIR_CHK; /* Need to check for directories */
93759243Sobrien    int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */
93859243Sobrien    int dir_ok     = flags & TW_DIR_OK;  /* Ignore directories? */
93959243Sobrien    int gpat       = flags & TW_PAT_OK;	 /* Match against a pattern */
94059243Sobrien    int ignoring   = flags & TW_IGN_OK;	 /* Use fignore? */
94159243Sobrien    int d = 4, nd;			 /* Spelling distance */
942231990Smp    Char **cp;
943167465Smp    Char *ptr;
94459243Sobrien    struct varent *vp;
945167465Smp    struct Strbuf buf = Strbuf_INIT, item = Strbuf_INIT;
946167465Smp    int enhanced = 0;
94759243Sobrien    int cnt = 0;
94859243Sobrien    int igncase = 0;
94959243Sobrien
95059243Sobrien
95159243Sobrien    flags = 0;
95259243Sobrien
95359243Sobrien    showdots = DOT_NONE;
95459243Sobrien    if ((ptr = varval(STRlistflags)) != STRNULL)
95559243Sobrien	while (*ptr)
95659243Sobrien	    switch (*ptr++) {
95759243Sobrien	    case 'a':
95859243Sobrien		showdots = DOT_ALL;
95959243Sobrien		break;
96059243Sobrien	    case 'A':
96159243Sobrien		showdots = DOT_NOT;
96259243Sobrien		break;
96359243Sobrien	    default:
96459243Sobrien		break;
96559243Sobrien	    }
96659243Sobrien
967231990Smp    if (looking == TW_COMMAND
968231990Smp	&& (vp = adrof(STRautorehash)) != NULL && vp->vec != NULL)
969231990Smp	for (cp = vp->vec; *cp; cp++)
970231990Smp	    if (Strcmp(*cp, STRalways) == 0
971231990Smp		|| (Strcmp(*cp, STRcorrect) == 0 && command == SPELL)
972231990Smp		|| (Strcmp(*cp, STRcomplete) == 0 && command != SPELL)) {
973231990Smp		tw_cmd_free();
974231990Smp		tw_cmd_start(NULL, NULL);
975231990Smp		break;
976231990Smp	    }
977231990Smp
978167465Smp    cleanup_push(&item, Strbuf_cleanup);
979167465Smp    cleanup_push(&buf, Strbuf_cleanup);
980167465Smp    while (!done &&
981167465Smp	   (item.len = 0,
982167465Smp	    tw_next_entry[looking](&item, exp_dir, &flags) != 0)) {
983167465Smp	Strbuf_terminate(&item);
98459243Sobrien#ifdef TDEBUG
985167465Smp	xprintf("item = %S\n", item.s);
98659243Sobrien#endif
98759243Sobrien	switch (looking) {
98859243Sobrien	case TW_FILE:
98959243Sobrien	case TW_DIRECTORY:
99059243Sobrien	case TW_TEXT:
99159243Sobrien	    /*
99259243Sobrien	     * Don't match . files on null prefix match
99359243Sobrien	     */
994167465Smp	    if (showdots == DOT_NOT && (ISDOT(item.s) || ISDOTDOT(item.s)))
99559243Sobrien		done = TRUE;
996167465Smp	    if (name_length == 0 && item.s[0] == '.' && showdots == DOT_NONE)
99759243Sobrien		done = TRUE;
99859243Sobrien	    break;
99959243Sobrien
100059243Sobrien	case TW_COMMAND:
1001131962Smp#if defined(_UWIN) || defined(__CYGWIN__)
1002167465Smp	    /*
1003167465Smp	     * Turn foo.{exe,com,bat,cmd} into foo since UWIN's readdir returns
1004167465Smp	     * the file with the .exe, .com, .bat, .cmd extension
1005231990Smp	     *
1006231990Smp	     * Same for Cygwin, but only for .exe and .com extension.
1007131962Smp	     */
1008131962Smp	    {
1009231990Smp#ifdef __CYGWIN__
1010231990Smp		static const char *rext[] = { ".exe", ".com" };
1011231990Smp#else
1012167465Smp		static const char *rext[] = { ".exe", ".bat", ".com", ".cmd" };
1013231990Smp#endif
1014167465Smp		size_t exti = Strlen(item.s);
1015167465Smp
1016167465Smp		if (exti > 4) {
1017167465Smp		    char *ext = short2str(&item.s[exti -= 4]);
1018167465Smp		    size_t i;
1019167465Smp
1020167465Smp		    for (i = 0; i < sizeof(rext) / sizeof(rext[0]); i++)
1021167465Smp			if (strcasecmp(ext, rext[i]) == 0) {
1022167465Smp			    item.len = exti;
1023167465Smp			    Strbuf_terminate(&item);
1024167465Smp			    break;
1025167465Smp			}
1026167465Smp		}
1027131962Smp	    }
1028131962Smp#endif /* _UWIN || __CYGWIN__ */
102959243Sobrien	    exec_check = flags & TW_EXEC_CHK;
103059243Sobrien	    dir_ok = flags & TW_DIR_OK;
103159243Sobrien	    break;
103259243Sobrien
103359243Sobrien	default:
103459243Sobrien	    break;
103559243Sobrien	}
103659243Sobrien
103759243Sobrien	if (done) {
103859243Sobrien	    done = FALSE;
103959243Sobrien	    continue;
104059243Sobrien	}
104159243Sobrien
104259243Sobrien	switch (command) {
104359243Sobrien
104459243Sobrien	case SPELL:		/* correct the spelling of the last bit */
104559243Sobrien	    if (name_length == 0) {/* zero-length word can't be misspelled */
1046167465Smp		exp_name->len = 0; /* (not trying is important for ~) */
1047167465Smp		Strbuf_terminate(exp_name);
104859243Sobrien		d = 0;
104959243Sobrien		done = TRUE;
105059243Sobrien		break;
105159243Sobrien	    }
1052167465Smp	    if (gpat && !Gmatch(item.s, pat))
105359243Sobrien		break;
105459243Sobrien	    /*
105559243Sobrien	     * Swapped the order of the spdist() arguments as suggested
105659243Sobrien	     * by eeide@asylum.cs.utah.edu (Eric Eide)
105759243Sobrien	     */
1058167465Smp	    nd = spdist(target, item.s); /* test the item against original */
105959243Sobrien	    if (nd <= d && nd != 4) {
1060167465Smp		if (!(exec_check && !executable(exp_dir->s, item.s, dir_ok))) {
1061167465Smp		    exp_name->len = 0;
1062167465Smp		    Strbuf_append(exp_name, item.s);
1063167465Smp		    Strbuf_terminate(exp_name);
106459243Sobrien		    d = nd;
106559243Sobrien		    if (d == 0)	/* if found it exactly */
106659243Sobrien			done = TRUE;
106759243Sobrien		}
106859243Sobrien	    }
106959243Sobrien	    else if (nd == 4) {
1070167465Smp	        if (spdir(exp_name, exp_dir->s, item.s, target)) {
1071167465Smp		    if (exec_check &&
1072167465Smp			!executable(exp_dir->s, exp_name->s, dir_ok))
107359243Sobrien			break;
107459243Sobrien#ifdef notdef
107559243Sobrien		    /*
107659243Sobrien		     * We don't want to stop immediately, because
107759243Sobrien		     * we might find an exact/better match later.
107859243Sobrien		     */
107959243Sobrien		    d = 0;
108059243Sobrien		    done = TRUE;
108159243Sobrien#endif
108259243Sobrien		    d = 3;
108359243Sobrien		}
108459243Sobrien	    }
108559243Sobrien	    break;
108659243Sobrien
108759243Sobrien	case LIST:
108859243Sobrien	case RECOGNIZE:
108959243Sobrien	case RECOGNIZE_ALL:
109059243Sobrien	case RECOGNIZE_SCROLL:
109159243Sobrien
1092231990Smp	    if ((vp = adrof(STRcomplete)) != NULL && vp->vec != NULL)
1093131962Smp		for (cp = vp->vec; *cp; cp++) {
1094231990Smp		    if (Strcmp(*cp, STREnhance) == 0)
1095231990Smp			enhanced = 2;
1096231990Smp		    else if (Strcmp(*cp, STRigncase) == 0)
1097131962Smp			igncase = 1;
1098231990Smp		    else if (Strcmp(*cp, STRenhance) == 0)
1099131962Smp			enhanced = 1;
1100131962Smp		}
1101131962Smp
110259243Sobrien	    if (enhanced || igncase) {
1103231990Smp	        if (!is_prefixmatch(target, item.s, enhanced))
110459243Sobrien		    break;
110559243Sobrien     	    } else {
1106167465Smp	        if (!is_prefix(target, item.s))
110759243Sobrien		    break;
110859243Sobrien	    }
110959243Sobrien
1110167465Smp	    if (exec_check && !executable(exp_dir->s, item.s, dir_ok))
111159243Sobrien		break;
111259243Sobrien
1113167465Smp	    if (dir_check && !isadirectory(exp_dir->s, item.s))
111459243Sobrien		break;
111559243Sobrien
1116167465Smp	    if (text_check && isadirectory(exp_dir->s, item.s))
111759243Sobrien		break;
111859243Sobrien
111959243Sobrien	    /*
112059243Sobrien	     * Only pattern match directories if we're checking
112159243Sobrien	     * for directories.
112259243Sobrien	     */
1123167465Smp	    if (gpat && !Gmatch(item.s, pat) &&
1124167465Smp		(dir_check || !isadirectory(exp_dir->s, item.s)))
112559243Sobrien		    break;
112659243Sobrien
112759243Sobrien	    /*
112859243Sobrien	     * Remove duplicates in command listing and completion
112959243Sobrien             * AFEB added code for TW_LOGNAME and TW_USER cases
113059243Sobrien	     */
113159243Sobrien	    if (looking == TW_COMMAND || looking == TW_LOGNAME
113259243Sobrien		|| looking == TW_USER || command == LIST) {
1133167465Smp		buf.len = 0;
1134167465Smp		Strbuf_append(&buf, item.s);
113559243Sobrien		switch (looking) {
113659243Sobrien		case TW_COMMAND:
113759243Sobrien		    if (!(dir_ok && exec_check))
113859243Sobrien			break;
1139167465Smp		    if (filetype(exp_dir->s, item.s) == '/')
1140167465Smp			Strbuf_append1(&buf, '/');
114159243Sobrien		    break;
114259243Sobrien
114359243Sobrien		case TW_FILE:
114459243Sobrien		case TW_DIRECTORY:
1145167465Smp		    Strbuf_append1(&buf, filetype(exp_dir->s, item.s));
114659243Sobrien		    break;
114759243Sobrien
114859243Sobrien		default:
114959243Sobrien		    break;
115059243Sobrien		}
1151167465Smp		Strbuf_terminate(&buf);
115259243Sobrien		if ((looking == TW_COMMAND || looking == TW_USER
1153167465Smp                     || looking == TW_LOGNAME) && tw_item_find(buf.s))
115459243Sobrien		    break;
115559243Sobrien		else {
115659243Sobrien		    /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
1157167465Smp		    tw_item_add(&buf);
115859243Sobrien		    if (command == LIST)
115959243Sobrien			numitems++;
116059243Sobrien		}
116159243Sobrien	    }
116259243Sobrien
116359243Sobrien	    if (command == RECOGNIZE || command == RECOGNIZE_ALL ||
116459243Sobrien		command == RECOGNIZE_SCROLL) {
1165167465Smp		if (ignoring && ignored(item.s)) {
116659243Sobrien		    nignored++;
116759243Sobrien		    break;
1168167465Smp		}
116959243Sobrien		else if (command == RECOGNIZE_SCROLL) {
1170167465Smp		    add_scroll_tab(item.s);
117159243Sobrien		    cnt++;
117259243Sobrien		}
1173167465Smp
117459243Sobrien		if (match_unique_match || is_set(STRrecexact)) {
1175167465Smp		    if (StrQcmp(target, item.s) == 0) {	/* EXACT match */
1176167465Smp			exp_name->len = 0;
1177167465Smp			Strbuf_append(exp_name, item.s);
1178167465Smp			Strbuf_terminate(exp_name);
117959243Sobrien			numitems = 1;	/* fake into expanding */
118059243Sobrien			non_unique_match = TRUE;
118159243Sobrien			done = TRUE;
118259243Sobrien			break;
118359243Sobrien		    }
118459243Sobrien		}
1185167465Smp		if (recognize(exp_name, item.s, name_length, ++numitems,
1186167465Smp		    enhanced, igncase))
118759243Sobrien		    if (command != RECOGNIZE_SCROLL)
118859243Sobrien			done = TRUE;
1189167465Smp		if (enhanced && exp_name->len < name_length) {
1190167465Smp		    exp_name->len = 0;
1191167465Smp		    Strbuf_append(exp_name, target);
1192167465Smp		    Strbuf_terminate(exp_name);
1193167465Smp		}
119459243Sobrien	    }
119559243Sobrien	    break;
119659243Sobrien
119759243Sobrien	default:
119859243Sobrien	    break;
119959243Sobrien	}
120059243Sobrien#ifdef TDEBUG
1201167465Smp	xprintf("done item = %S\n", item.s);
120259243Sobrien#endif
120359243Sobrien    }
1204167465Smp    cleanup_until(&item);
120559243Sobrien
120659243Sobrien    if (command == RECOGNIZE_SCROLL) {
120759243Sobrien	if ((cnt <= curchoice) || (curchoice == -1)) {
120859243Sobrien	    curchoice = -1;
120959243Sobrien	    nignored = 0;
121059243Sobrien	    numitems = 0;
121159243Sobrien	} else if (numitems > 1) {
121259243Sobrien	    if (curchoice < -1)
121359243Sobrien		curchoice = cnt - 1;
1214167465Smp	    choose_scroll_tab(exp_name, cnt);
121559243Sobrien	    numitems = 1;
121659243Sobrien	}
121759243Sobrien    }
121859243Sobrien    free_scroll_tab();
121959243Sobrien
122059243Sobrien    if (command == SPELL)
122159243Sobrien	return d;
122259243Sobrien    else {
122359243Sobrien	if (ignoring && numitems == 0 && nignored > 0)
122459243Sobrien	    return -nignored;
122559243Sobrien	else
122659243Sobrien	    return numitems;
122759243Sobrien    }
122859243Sobrien}
122959243Sobrien
123059243Sobrien
123159243Sobrien/* tw_suffix():
123259243Sobrien *	Find and return the appropriate suffix character
123359243Sobrien */
123459243Sobrien/*ARGSUSED*/
1235167465Smpstatic Char
1236231990Smptw_suffix(int looking, struct Strbuf *word, const Char *exp_dir, Char *exp_name)
1237167465Smp{
123859243Sobrien    Char *ptr;
1239316957Sdchagin    Char *dol;
124059243Sobrien    struct varent *vp;
124159243Sobrien
124259243Sobrien    (void) strip(exp_name);
124359243Sobrien
124459243Sobrien    switch (looking) {
124559243Sobrien
124659243Sobrien    case TW_LOGNAME:
124759243Sobrien	return '/';
124859243Sobrien
124959243Sobrien    case TW_VARIABLE:
125059243Sobrien	/*
125159243Sobrien	 * Don't consider array variables or empty variables
125259243Sobrien	 */
1253100616Smp	if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) {
125459243Sobrien	    if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' ||
125559243Sobrien		vp->vec[1] != NULL)
125659243Sobrien		return ' ';
125759243Sobrien	}
125859243Sobrien	else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0')
125959243Sobrien	    return ' ';
126059243Sobrien
1261316957Sdchagin	if ((dol = Strrchr(word->s, '$')) != 0 &&
1262316957Sdchagin	    dol[1] == '{' && Strchr(dol, '}') == NULL)
1263231990Smp	  return '}';
1264231990Smp
126559243Sobrien	return isadirectory(exp_dir, ptr) ? '/' : ' ';
126659243Sobrien
126759243Sobrien
126859243Sobrien    case TW_DIRECTORY:
126959243Sobrien	return '/';
127059243Sobrien
127159243Sobrien    case TW_COMMAND:
127259243Sobrien    case TW_FILE:
127359243Sobrien	return isadirectory(exp_dir, exp_name) ? '/' : ' ';
127459243Sobrien
127559243Sobrien    case TW_ALIAS:
127659243Sobrien    case TW_VARLIST:
127759243Sobrien    case TW_WORDLIST:
127859243Sobrien    case TW_SHELLVAR:
127959243Sobrien    case TW_ENVVAR:
128059243Sobrien    case TW_USER:
128159243Sobrien    case TW_BINDING:
128259243Sobrien    case TW_LIMIT:
128359243Sobrien    case TW_SIGNAL:
128459243Sobrien    case TW_JOB:
128559243Sobrien    case TW_COMPLETION:
128659243Sobrien    case TW_TEXT:
128759243Sobrien    case TW_GRPNAME:
128859243Sobrien	return ' ';
128959243Sobrien
129059243Sobrien    default:
129159243Sobrien	return '\0';
129259243Sobrien    }
129359243Sobrien} /* end tw_suffix */
129459243Sobrien
129559243Sobrien
129659243Sobrien/* tw_fixword():
129759243Sobrien *	Repair a word after a spalling or a recognizwe
129859243Sobrien */
129959243Sobrienstatic void
1300167465Smptw_fixword(int looking, struct Strbuf *word, Char *dir, Char *exp_name)
130159243Sobrien{
130259243Sobrien    Char *ptr;
130359243Sobrien
130459243Sobrien    switch (looking) {
130559243Sobrien    case TW_LOGNAME:
1306167465Smp	word->len = 0;
1307167465Smp	Strbuf_append1(word, '~');
130859243Sobrien	break;
1309167465Smp
131059243Sobrien    case TW_VARIABLE:
1311167465Smp	if ((ptr = Strrchr(word->s, '$')) != NULL) {
1312231990Smp	    if (ptr[1] == '{') ptr++;
1313167465Smp	    word->len = ptr + 1 - word->s; /* Delete after the dollar */
1314167465Smp	} else
1315167465Smp	    word->len = 0;
131659243Sobrien	break;
131759243Sobrien
131859243Sobrien    case TW_DIRECTORY:
131959243Sobrien    case TW_FILE:
132059243Sobrien    case TW_TEXT:
1321167465Smp	word->len = 0;
1322167465Smp	Strbuf_append(word, dir);		/* put back dir part */
132359243Sobrien	break;
132459243Sobrien
132559243Sobrien    default:
1326167465Smp	word->len = 0;
132759243Sobrien	break;
132859243Sobrien    }
132959243Sobrien
133059243Sobrien    (void) quote(exp_name);
1331167465Smp    Strbuf_append(word, exp_name);		/* add extended name */
1332167465Smp    Strbuf_terminate(word);
133359243Sobrien} /* end tw_fixword */
133459243Sobrien
133559243Sobrien
133659243Sobrien/* tw_collect():
133759243Sobrien *	Collect items. Return -1 in case we were interrupted or
133859243Sobrien *	the return value of tw_collect
133959243Sobrien *	This is really a wrapper for tw_collect_items, serving two
134059243Sobrien *	purposes:
134159243Sobrien *		1. Handles interrupt cleanups.
134259243Sobrien *		2. Retries if we had no matches, but there were ignored matches
134359243Sobrien */
134459243Sobrienstatic int
1345167465Smptw_collect(COMMAND command, int looking, struct Strbuf *exp_dir,
1346167465Smp	   struct Strbuf *exp_name, Char *target, Char *pat, int flags,
1347167465Smp	   DIR *dir_fd)
134859243Sobrien{
1349167465Smp    volatile int ni;
135059243Sobrien    jmp_buf_t osetexit;
135159243Sobrien
135259243Sobrien#ifdef TDEBUG
1353167465Smp    xprintf("target = %S\n", target);
135459243Sobrien#endif
135559243Sobrien    ni = 0;
135659243Sobrien    getexit(osetexit);
135759243Sobrien    for (;;) {
1358167465Smp	volatile size_t omark;
1359167465Smp
136059243Sobrien	(*tw_start_entry[looking])(dir_fd, pat);
136159243Sobrien	InsideCompletion = 1;
136259243Sobrien	if (setexit()) {
1363167465Smp	    cleanup_pop_mark(omark);
1364167465Smp	    resexit(osetexit);
136559243Sobrien	    /* interrupted, clean up */
136659243Sobrien	    haderr = 0;
1367167465Smp	    ni = -1; /* flag error */
1368167465Smp	    break;
136959243Sobrien	}
1370167465Smp	omark = cleanup_push_mark();
1371167465Smp	ni = tw_collect_items(command, looking, exp_dir, exp_name, target, pat,
1372167465Smp			      ni >= 0 ? flags : flags & ~TW_IGN_OK);
1373167465Smp	cleanup_pop_mark(omark);
1374167465Smp	resexit(osetexit);
1375167465Smp        if (ni >= 0)
1376167465Smp	    break;
1377167465Smp    }
1378167465Smp    InsideCompletion = 0;
137959243Sobrien#if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
1380167465Smp    /* Compiler bug? (from PWP) */
1381167465Smp    if ((looking == TW_LOGNAME) || (looking == TW_USER))
1382167465Smp	tw_logname_end();
1383167465Smp    else if (looking == TW_GRPNAME)
1384167465Smp	tw_grpname_end();
1385167465Smp    else
1386167465Smp	tw_dir_end();
138759243Sobrien#else /* !(SOLARIS2 && i386 && !__GNUC__) */
1388167465Smp    (*tw_end_entry[looking])();
138959243Sobrien#endif /* !(SOLARIS2 && i386 && !__GNUC__) */
1390167465Smp    return(ni);
139159243Sobrien} /* end tw_collect */
139259243Sobrien
139359243Sobrien
139459243Sobrien/* tw_list_items():
139559243Sobrien *	List the items that were found
139659243Sobrien *
139759243Sobrien *	NOTE instead of looking at numerical vars listmax and listmaxrows
139859243Sobrien *	we can look at numerical var listmax, and have a string value
139959243Sobrien *	listmaxtype (or similar) than can have values 'items' and 'rows'
140059243Sobrien *	(by default interpreted as 'items', for backwards compatibility)
140159243Sobrien */
140259243Sobrienstatic void
1403167465Smptw_list_items(int looking, int numitems, int list_max)
140459243Sobrien{
140559243Sobrien    Char *ptr;
140659243Sobrien    int max_items = 0;
140759243Sobrien    int max_rows = 0;
140859243Sobrien
140983098Smp    if (numitems == 0)
141083098Smp	return;
141183098Smp
141259243Sobrien    if ((ptr = varval(STRlistmax)) != STRNULL) {
141359243Sobrien	while (*ptr) {
141459243Sobrien	    if (!Isdigit(*ptr)) {
141559243Sobrien		max_items = 0;
141659243Sobrien		break;
141759243Sobrien	    }
141859243Sobrien	    max_items = max_items * 10 + *ptr++ - '0';
141959243Sobrien	}
142059243Sobrien	if ((max_items > 0) && (numitems > max_items) && list_max)
142159243Sobrien	    max_items = numitems;
142259243Sobrien	else
142359243Sobrien	    max_items = 0;
142459243Sobrien    }
142559243Sobrien
142659243Sobrien    if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) {
142759243Sobrien	int rows;
142859243Sobrien
142959243Sobrien	while (*ptr) {
143059243Sobrien	    if (!Isdigit(*ptr)) {
143159243Sobrien		max_rows = 0;
143259243Sobrien		break;
143359243Sobrien	    }
143459243Sobrien	    max_rows = max_rows * 10 + *ptr++ - '0';
143559243Sobrien	}
143659243Sobrien	if (max_rows != 0 && looking != TW_JOB)
143759243Sobrien	    rows = find_rows(tw_item_get(), numitems, TRUE);
143859243Sobrien	else
143959243Sobrien	    rows = numitems; /* underestimate for lines wider than the termH */
144059243Sobrien	if ((max_rows > 0) && (rows > max_rows) && list_max)
144159243Sobrien	    max_rows = rows;
144259243Sobrien	else
144359243Sobrien	    max_rows = 0;
144459243Sobrien    }
144559243Sobrien
144659243Sobrien
144759243Sobrien    if (max_items || max_rows) {
1448145479Smp	char    	 tc, *sname;
144959243Sobrien	const char	*name;
145059243Sobrien	int maxs;
145159243Sobrien
145259243Sobrien	if (max_items) {
145359243Sobrien	    name = CGETS(30, 5, "items");
145459243Sobrien	    maxs = max_items;
145559243Sobrien	}
145659243Sobrien	else {
145759243Sobrien	    name = CGETS(30, 6, "rows");
145859243Sobrien	    maxs = max_rows;
145959243Sobrien	}
146059243Sobrien
1461145479Smp	sname = strsave(name);
1462167465Smp	cleanup_push(sname, xfree);
146359243Sobrien	xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "),
1464145479Smp		maxs, sname);
1465167465Smp	cleanup_until(sname);
146659243Sobrien	flush();
146759243Sobrien	/* We should be in Rawmode here, so no \n to catch */
1468167465Smp	(void) xread(SHIN, &tc, 1);
146959243Sobrien	xprintf("%c\r\n", tc);	/* echo the char, do a newline */
1470167465Smp	/*
147159243Sobrien	 * Perhaps we should use the yesexpr from the
147259243Sobrien	 * actual locale
147359243Sobrien	 */
147459243Sobrien	if (strchr(CGETS(30, 13, "Yy"), tc) == NULL)
147559243Sobrien	    return;
147659243Sobrien    }
147759243Sobrien
147859243Sobrien    if (looking != TW_SIGNAL)
1479167465Smp	qsort(tw_item_get(), numitems, sizeof(Char *), fcompare);
148059243Sobrien    if (looking != TW_JOB)
148159243Sobrien	print_by_column(STRNULL, tw_item_get(), numitems, TRUE);
148259243Sobrien    else {
148359243Sobrien	/*
148459243Sobrien	 * print one item on every line because jobs can have spaces
148559243Sobrien	 * and it is confusing.
148659243Sobrien	 */
148759243Sobrien	int i;
148859243Sobrien	Char **w = tw_item_get();
148959243Sobrien
149059243Sobrien	for (i = 0; i < numitems; i++) {
149159243Sobrien	    xprintf("%S", w[i]);
149259243Sobrien	    if (Tty_raw_mode)
149359243Sobrien		xputchar('\r');
149459243Sobrien	    xputchar('\n');
149559243Sobrien	}
149659243Sobrien    }
149759243Sobrien} /* end tw_list_items */
149859243Sobrien
149959243Sobrien
150059243Sobrien/* t_search():
150159243Sobrien *	Perform a RECOGNIZE, LIST or SPELL command on string "word".
150259243Sobrien *
150359243Sobrien *	Return value:
150459243Sobrien *		>= 0:   SPELL command: "distance" (see spdist())
150559243Sobrien *		                other: No. of items found
150659243Sobrien *  		 < 0:   Error (message or beep is output)
150759243Sobrien */
150859243Sobrien/*ARGSUSED*/
150959243Sobrienint
1510167465Smpt_search(struct Strbuf *word, COMMAND command, int looking, int list_max,
1511167465Smp	 Char *pat, eChar suf)
151259243Sobrien{
151359243Sobrien    int     numitems,			/* Number of items matched */
151459243Sobrien	    flags = 0,			/* search flags */
151559243Sobrien	    gpat = pat[0] != '\0',	/* Glob pattern search */
1516167465Smp	    res;			/* Return value */
1517167465Smp    struct Strbuf exp_dir = Strbuf_INIT;/* dir after ~ expansion */
1518167465Smp    struct Strbuf dir = Strbuf_INIT;	/* /x/y/z/ part in /x/y/z/f */
1519167465Smp    struct Strbuf exp_name = Strbuf_INIT;/* the recognized (extended) */
1520167465Smp    Char   *name,			/* f part in /d/d/d/f name */
152159243Sobrien           *target;			/* Target to expand/correct/list */
1522167465Smp    DIR    *dir_fd = NULL;
152359243Sobrien
152459243Sobrien    /*
152559243Sobrien     * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
152659243Sobrien     * dump core when interrupted
152759243Sobrien     */
152859243Sobrien    tw_item_free();
152959243Sobrien
153059243Sobrien    non_unique_match = FALSE;	/* See the recexact code below */
153159243Sobrien
1532167465Smp    extract_dir_and_name(word->s, &dir, &name);
1533167465Smp    cleanup_push(&dir, Strbuf_cleanup);
1534167465Smp    cleanup_push(&name, xfree_indirect);
153559243Sobrien
153659243Sobrien    /*
153759243Sobrien     *  SPECIAL HARDCODED COMPLETIONS:
153859243Sobrien     *    foo$variable                -> TW_VARIABLE
153959243Sobrien     *    ~user                       -> TW_LOGNAME
154059243Sobrien     *
154159243Sobrien     */
1542167465Smp    if ((*word->s == '~') && (Strchr(word->s, '/') == NULL)) {
154359243Sobrien	looking = TW_LOGNAME;
154459243Sobrien	target = name;
154559243Sobrien	gpat = 0;	/* Override pattern mechanism */
154659243Sobrien    }
154759243Sobrien    else if ((target = Strrchr(name, '$')) != 0 &&
1548231990Smp	     (target[1] != '{' || Strchr(target, '}') == NULL) &&
154959243Sobrien	     (Strchr(name, '/') == NULL)) {
155059243Sobrien	target++;
1551231990Smp	if (target[0] == '{') target++;
155259243Sobrien	looking = TW_VARIABLE;
155359243Sobrien	gpat = 0;	/* Override pattern mechanism */
155459243Sobrien    }
155559243Sobrien    else
155659243Sobrien	target = name;
155759243Sobrien
155859243Sobrien    /*
155959243Sobrien     * Try to figure out what we should be looking for
156059243Sobrien     */
156159243Sobrien    if (looking & TW_PATH) {
156259243Sobrien	gpat = 0;	/* pattern holds the pathname to be used */
1563167465Smp	Strbuf_append(&exp_dir, pat);
1564167465Smp	if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/')
1565167465Smp	    Strbuf_append1(&exp_dir, '/');
1566167465Smp	Strbuf_append(&exp_dir, dir.s);
156759243Sobrien    }
1568167465Smp    Strbuf_terminate(&exp_dir);
1569167465Smp    cleanup_push(&exp_dir, Strbuf_cleanup);
157059243Sobrien
157159243Sobrien    switch (looking & ~TW_PATH) {
157259243Sobrien    case TW_NONE:
1573167465Smp	res = -1;
1574167465Smp	goto err_dir;
157559243Sobrien
157659243Sobrien    case TW_ZERO:
157759243Sobrien	looking = TW_FILE;
157859243Sobrien	break;
157959243Sobrien
158059243Sobrien    case TW_COMMAND:
1581167465Smp	if (Strchr(word->s, '/') || (looking & TW_PATH)) {
158259243Sobrien	    looking = TW_FILE;
158359243Sobrien	    flags |= TW_EXEC_CHK;
158459243Sobrien	    flags |= TW_DIR_OK;
158559243Sobrien	}
158659243Sobrien#ifdef notdef
158759243Sobrien	/* PWP: don't even bother when doing ALL of the commands */
1588167465Smp	if (looking == TW_COMMAND && word->len == 0) {
1589167465Smp	    res = -1;
1590167465Smp	    goto err_dir;
1591167465Smp	}
159259243Sobrien#endif
159359243Sobrien	break;
159459243Sobrien
159559243Sobrien
159659243Sobrien    case TW_VARLIST:
159759243Sobrien    case TW_WORDLIST:
159859243Sobrien	gpat = 0;	/* pattern holds the name of the variable */
159959243Sobrien	break;
160059243Sobrien
160159243Sobrien    case TW_EXPLAIN:
160259243Sobrien	if (command == LIST && pat != NULL) {
160359243Sobrien	    xprintf("%S", pat);
160459243Sobrien	    if (Tty_raw_mode)
160559243Sobrien		xputchar('\r');
160659243Sobrien	    xputchar('\n');
160759243Sobrien	}
1608167465Smp	res = 2;
1609167465Smp	goto err_dir;
161059243Sobrien
161159243Sobrien    default:
161259243Sobrien	break;
161359243Sobrien    }
161459243Sobrien
161559243Sobrien    /*
161659243Sobrien     * let fignore work only when we are not using a pattern
161759243Sobrien     */
161859243Sobrien    flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK;
161959243Sobrien
162059243Sobrien#ifdef TDEBUG
162159243Sobrien    xprintf(CGETS(30, 8, "looking = %d\n"), looking);
162259243Sobrien#endif
162359243Sobrien
162459243Sobrien    switch (looking) {
1625167465Smp	Char *user_name;
1626167465Smp
162759243Sobrien    case TW_ALIAS:
162859243Sobrien    case TW_SHELLVAR:
162959243Sobrien    case TW_ENVVAR:
163059243Sobrien    case TW_BINDING:
163159243Sobrien    case TW_LIMIT:
163259243Sobrien    case TW_SIGNAL:
163359243Sobrien    case TW_JOB:
163459243Sobrien    case TW_COMPLETION:
163559243Sobrien    case TW_GRPNAME:
163659243Sobrien	break;
163759243Sobrien
163859243Sobrien
163959243Sobrien    case TW_VARIABLE:
1640167465Smp	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1641167465Smp	    goto err_dir;
164259243Sobrien	break;
164359243Sobrien
164459243Sobrien    case TW_DIRECTORY:
164559243Sobrien	flags |= TW_DIR_CHK;
164659243Sobrien
164759243Sobrien#ifdef notyet
164859243Sobrien	/*
164959243Sobrien	 * This is supposed to expand the directory stack.
165059243Sobrien	 * Problems:
165159243Sobrien	 * 1. Slow
165259243Sobrien	 * 2. directories with the same name
165359243Sobrien	 */
165459243Sobrien	flags |= TW_DIR_OK;
165559243Sobrien#endif
165659243Sobrien#ifdef notyet
165759243Sobrien	/*
165859243Sobrien	 * Supposed to do delayed expansion, but it is inconsistent
165959243Sobrien	 * from a user-interface point of view, since it does not
166059243Sobrien	 * immediately obey addsuffix
166159243Sobrien	 */
1662167465Smp	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1663167465Smp	    goto err_dir;
1664167465Smp	if (isadirectory(exp_dir.s, name)) {
1665167465Smp	    if (exp_dir.len != 0 || name[0] != '\0') {
1666167465Smp		Strbuf_append(&dir, name);
1667167465Smp		if (dir.s[dir.len - 1] != '/')
1668167465Smp		    Strbuf_append1(&dir, '/');
1669167465Smp		Strbuf_terminate(&dir);
1670167465Smp		if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1671167465Smp		    goto err_dir;
1672167465Smp		if (word->len != 0 && word->s[word->len - 1] != '/') {
1673167465Smp		    Strbuf_append1(word, '/');
1674167465Smp		    Strbuf_terminate(word);
1675167465Smp		}
167659243Sobrien		name[0] = '\0';
167759243Sobrien	    }
167859243Sobrien	}
167959243Sobrien#endif
1680167465Smp	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1681167465Smp	    goto err_dir;
168259243Sobrien	break;
168359243Sobrien
168459243Sobrien    case TW_TEXT:
168559243Sobrien	flags |= TW_TEXT_CHK;
168659243Sobrien	/*FALLTHROUGH*/
168759243Sobrien    case TW_FILE:
1688167465Smp	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1689167465Smp	    goto err_dir;
169059243Sobrien	break;
169159243Sobrien
169259243Sobrien    case TW_PATH | TW_TEXT:
169359243Sobrien    case TW_PATH | TW_FILE:
169459243Sobrien    case TW_PATH | TW_DIRECTORY:
169559243Sobrien    case TW_PATH | TW_COMMAND:
1696167465Smp	if ((dir_fd = opendir(short2str(exp_dir.s))) == NULL) {
169783098Smp 	    if (command == RECOGNIZE)
169883098Smp 		xprintf("\n");
1699167465Smp 	    xprintf("%S: %s", exp_dir.s, strerror(errno));
170083098Smp 	    if (command != RECOGNIZE)
170183098Smp 		xprintf("\n");
170283098Smp 	    NeedsRedraw = 1;
1703167465Smp	    res = -1;
1704167465Smp	    goto err_dir;
170559243Sobrien	}
1706167465Smp	if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') {
1707167465Smp	    Strbuf_append1(&exp_dir, '/');
1708167465Smp	    Strbuf_terminate(&exp_dir);
1709167465Smp	}
171059243Sobrien
171159243Sobrien	looking &= ~TW_PATH;
171259243Sobrien
171359243Sobrien	switch (looking) {
171459243Sobrien	case TW_TEXT:
171559243Sobrien	    flags |= TW_TEXT_CHK;
171659243Sobrien	    break;
171759243Sobrien
171859243Sobrien	case TW_FILE:
171959243Sobrien	    break;
172059243Sobrien
172159243Sobrien	case TW_DIRECTORY:
172259243Sobrien	    flags |= TW_DIR_CHK;
172359243Sobrien	    break;
172459243Sobrien
172559243Sobrien	case TW_COMMAND:
1726167465Smp	    xfree(name);
1727167465Smp	    target = name = Strsave(word->s);	/* so it can match things */
172859243Sobrien	    break;
172959243Sobrien
173059243Sobrien	default:
173159243Sobrien	    abort();	/* Cannot happen */
173259243Sobrien	    break;
173359243Sobrien	}
173459243Sobrien	break;
173559243Sobrien
173659243Sobrien    case TW_LOGNAME:
1737167465Smp	user_name = word->s + 1;
1738167465Smp	goto do_user;
1739167465Smp
174059243Sobrien	/*FALLTHROUGH*/
174159243Sobrien    case TW_USER:
1742167465Smp	user_name = word->s;
1743167465Smp    do_user:
174459243Sobrien	/*
174559243Sobrien	 * Check if the spelling was already correct
174659243Sobrien	 * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
174759243Sobrien	 */
1748167465Smp	if (command == SPELL && xgetpwnam(short2str(user_name)) != NULL) {
174959243Sobrien#ifdef YPBUGS
175059243Sobrien	    fix_yp_bugs();
175159243Sobrien#endif /* YPBUGS */
1752167465Smp	    res = 0;
1753167465Smp	    goto err_dir;
175459243Sobrien	}
1755167465Smp	xfree(name);
1756167465Smp	target = name = Strsave(user_name);
175759243Sobrien	break;
175859243Sobrien
175959243Sobrien    case TW_COMMAND:
176059243Sobrien    case TW_VARLIST:
176159243Sobrien    case TW_WORDLIST:
1762167465Smp	target = name = Strsave(word->s);	/* so it can match things */
176359243Sobrien	break;
176459243Sobrien
176559243Sobrien    default:
176659243Sobrien	xprintf(CGETS(30, 9,
176759243Sobrien		"\n%s internal error: I don't know what I'm looking for!\n"),
176859243Sobrien		progname);
176959243Sobrien	NeedsRedraw = 1;
1770167465Smp	res = -1;
1771167465Smp	goto err_dir;
177259243Sobrien    }
177359243Sobrien
1774167465Smp    cleanup_push(&exp_name, Strbuf_cleanup);
1775167465Smp    numitems = tw_collect(command, looking, &exp_dir, &exp_name, target, pat,
1776167465Smp			  flags, dir_fd);
177759243Sobrien    if (numitems == -1)
1778167465Smp	goto end;
177959243Sobrien
178059243Sobrien    switch (command) {
178159243Sobrien    case RECOGNIZE:
178259243Sobrien    case RECOGNIZE_ALL:
178359243Sobrien    case RECOGNIZE_SCROLL:
178459243Sobrien	if (numitems <= 0)
1785167465Smp	    break;
178659243Sobrien
1787167465Smp	Strbuf_terminate(&exp_name);
1788167465Smp	tw_fixword(looking, word, dir.s, exp_name.s);
178959243Sobrien
179059243Sobrien	if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) {
179159243Sobrien	    switch (suf) {
179259243Sobrien	    case 0: 	/* Automatic suffix */
1793167465Smp		Strbuf_append1(word,
1794231990Smp			       tw_suffix(looking, word, exp_dir.s, exp_name.s));
179559243Sobrien		break;
179659243Sobrien
1797167465Smp	    case CHAR_ERR:	/* No suffix */
1798167465Smp		break;
179959243Sobrien
180059243Sobrien	    default:	/* completion specified suffix */
1801167465Smp		Strbuf_append1(word, suf);
180259243Sobrien		break;
180359243Sobrien	    }
1804167465Smp	    Strbuf_terminate(word);
180559243Sobrien	}
1806167465Smp	break;
180759243Sobrien
180859243Sobrien    case LIST:
180959243Sobrien	tw_list_items(looking, numitems, list_max);
181059243Sobrien	tw_item_free();
1811167465Smp	break;
181259243Sobrien
181359243Sobrien    case SPELL:
1814167465Smp	Strbuf_terminate(&exp_name);
1815167465Smp	tw_fixword(looking, word, dir.s, exp_name.s);
1816167465Smp	break;
181759243Sobrien
181859243Sobrien    default:
181959243Sobrien	xprintf("Bad tw_command\n");
1820167465Smp	numitems = 0;
182159243Sobrien    }
1822167465Smp end:
1823167465Smp    res = numitems;
1824167465Smp err_dir:
1825167465Smp    cleanup_until(&dir);
1826167465Smp    return res;
182759243Sobrien} /* end t_search */
182859243Sobrien
182959243Sobrien
183059243Sobrien/* extract_dir_and_name():
183159243Sobrien * 	parse full path in file into 2 parts: directory and file names
183259243Sobrien * 	Should leave final slash (/) at end of dir.
183359243Sobrien */
183459243Sobrienstatic void
1835167465Smpextract_dir_and_name(const Char *path, struct Strbuf *dir, Char **name)
183659243Sobrien{
1837145479Smp    Char *p;
183859243Sobrien
183959243Sobrien    p = Strrchr(path, '/');
184069408Sache#ifdef WINNT_NATIVE
184159243Sobrien    if (p == NULL)
184259243Sobrien	p = Strrchr(path, ':');
184369408Sache#endif /* WINNT_NATIVE */
1844167465Smp    if (p == NULL)
1845167465Smp	*name = Strsave(path);
184659243Sobrien    else {
184759243Sobrien	p++;
1848167465Smp	*name = Strsave(p);
1849167465Smp	Strbuf_appendn(dir, path, p - path);
185059243Sobrien    }
1851167465Smp    Strbuf_terminate(dir);
185259243Sobrien} /* end extract_dir_and_name */
185359243Sobrien
185459243Sobrien
185559243Sobrien/* dollar():
185659243Sobrien * 	expand "/$old1/$old2/old3/"
185759243Sobrien * 	to "/value_of_old1/value_of_old2/old3/"
185859243Sobrien */
185959243SobrienChar *
1860167465Smpdollar(const Char *old)
186159243Sobrien{
1862167465Smp    struct Strbuf buf = Strbuf_INIT;
186359243Sobrien
1864167465Smp    while (*old) {
1865167465Smp	if (*old != '$')
1866167465Smp	    Strbuf_append1(&buf, *old++);
186759243Sobrien	else {
1868167465Smp	    if (expdollar(&buf, &old, QUOTE) == 0) {
1869167465Smp		xfree(buf.s);
187059243Sobrien		return NULL;
1871167465Smp	    }
187259243Sobrien	}
1873167465Smp    }
1874167465Smp    return Strbuf_finish(&buf);
187559243Sobrien} /* end dollar */
187659243Sobrien
187759243Sobrien
187859243Sobrien/* tilde():
187959243Sobrien * 	expand ~person/foo to home_directory_of_person/foo
188059243Sobrien *	or =<stack-entry> to <dir in stack entry>
188159243Sobrien */
1882167465Smpstatic int
1883167465Smptilde(struct Strbuf *new, Char *old)
188459243Sobrien{
1885145479Smp    Char *o, *p;
188659243Sobrien
1887167465Smp    new->len = 0;
188859243Sobrien    switch (old[0]) {
1889167465Smp    case '~': {
1890167465Smp	Char *name, *home;
1891167465Smp
1892167465Smp	old++;
1893167465Smp	for (o = old; *o && *o != '/'; o++)
189459243Sobrien	    continue;
1895167465Smp	name = Strnsave(old, o - old);
1896167465Smp	home = gethdir(name);
1897167465Smp	xfree(name);
1898167465Smp	if (home == NULL)
1899167465Smp	    goto err;
1900167465Smp	Strbuf_append(new, home);
1901167465Smp	xfree(home);
1902167465Smp	/* If the home directory expands to "/", we do
190383098Smp	 * not want to create "//" by appending a slash from o.
190483098Smp	 */
1905167465Smp	if (new->s[0] == '/' && new->len == 1 && *o == '/')
190683098Smp	    ++o;
1907167465Smp	Strbuf_append(new, o);
1908167465Smp	break;
1909167465Smp    }
191059243Sobrien
191159243Sobrien    case '=':
1912167465Smp	if ((p = globequal(old)) == NULL)
1913167465Smp	    goto err;
1914167465Smp	if (p != old) {
1915167465Smp	    Strbuf_append(new, p);
1916167465Smp	    xfree(p);
1917167465Smp	    break;
191859243Sobrien	}
191959243Sobrien	/*FALLTHROUGH*/
192059243Sobrien
192159243Sobrien    default:
1922167465Smp	Strbuf_append(new, old);
1923167465Smp	break;
192459243Sobrien    }
1925167465Smp    Strbuf_terminate(new);
1926167465Smp    return 0;
1927167465Smp
1928167465Smp err:
1929167465Smp    Strbuf_terminate(new);
1930167465Smp    return -1;
193159243Sobrien} /* end tilde */
193259243Sobrien
193359243Sobrien
193459243Sobrien/* expand_dir():
193559243Sobrien *	Open the directory given, expanding ~user and $var
193659243Sobrien *	Optionally normalize the path given
193759243Sobrien */
193859243Sobrienstatic int
1939167465Smpexpand_dir(const Char *dir, struct Strbuf *edir, DIR **dfd, COMMAND cmd)
194059243Sobrien{
194159243Sobrien    Char   *nd = NULL;
1942167465Smp    Char *tdir;
194359243Sobrien
1944167465Smp    tdir = dollar(dir);
1945167465Smp    cleanup_push(tdir, xfree);
1946167465Smp    if (tdir == NULL ||
1947167465Smp	(tilde(edir, tdir) != 0) ||
1948167465Smp	!(nd = dnormalize(edir->len ? edir->s : STRdot,
1949167465Smp			  symlinks == SYM_IGNORE || symlinks == SYM_EXPAND)) ||
195059243Sobrien	((*dfd = opendir(short2str(nd))) == NULL)) {
1951167465Smp	xfree(nd);
1952167465Smp	if (cmd == SPELL || SearchNoDirErr) {
1953167465Smp	    cleanup_until(tdir);
195459243Sobrien	    return (-2);
1955167465Smp	}
195659243Sobrien	/*
195759243Sobrien	 * From: Amos Shapira <amoss@cs.huji.ac.il>
195859243Sobrien	 * Print a better message when completion fails
195959243Sobrien	 */
1960167465Smp	xprintf("\n%S %s\n", edir->len ? edir->s : (tdir ? tdir : dir),
196159243Sobrien		(errno == ENOTDIR ? CGETS(30, 10, "not a directory") :
196259243Sobrien		(errno == ENOENT ? CGETS(30, 11, "not found") :
196359243Sobrien		 CGETS(30, 12, "unreadable"))));
196459243Sobrien	NeedsRedraw = 1;
1965167465Smp	cleanup_until(tdir);
196659243Sobrien	return (-1);
196759243Sobrien    }
1968167465Smp    cleanup_until(tdir);
196959243Sobrien    if (nd) {
197059243Sobrien	if (*dir != '\0') {
1971167465Smp	    int slash;
197259243Sobrien
197359243Sobrien	    /*
197459243Sobrien	     * Copy and append a / if there was one
197559243Sobrien	     */
1976167465Smp	    slash = edir->len != 0 && edir->s[edir->len - 1] == '/';
1977167465Smp	    edir->len = 0;
1978167465Smp	    Strbuf_append(edir, nd);
1979167465Smp	    if (slash != 0 && edir->s[edir->len - 1] != '/')
1980167465Smp		Strbuf_append1(edir, '/');
1981167465Smp	    Strbuf_terminate(edir);
198259243Sobrien	}
1983167465Smp	xfree(nd);
198459243Sobrien    }
198559243Sobrien    return 0;
198659243Sobrien} /* end expand_dir */
198759243Sobrien
198859243Sobrien
198959243Sobrien/* nostat():
199059243Sobrien *	Returns true if the directory should not be stat'd,
199159243Sobrien *	false otherwise.
199259243Sobrien *	This way, things won't grind to a halt when you complete in /afs
199359243Sobrien *	or very large directories.
199459243Sobrien */
1995145479Smpstatic int
1996167465Smpnostat(Char *dir)
199759243Sobrien{
199859243Sobrien    struct varent *vp;
1999145479Smp    Char **cp;
200059243Sobrien
200159243Sobrien    if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL)
200259243Sobrien	return FALSE;
200359243Sobrien    for (; *cp != NULL; cp++) {
200459243Sobrien	if (Strcmp(*cp, STRstar) == 0)
200559243Sobrien	    return TRUE;
200659243Sobrien	if (Gmatch(dir, *cp))
200759243Sobrien	    return TRUE;
200859243Sobrien    }
200959243Sobrien    return FALSE;
201059243Sobrien} /* end nostat */
201159243Sobrien
201259243Sobrien
201359243Sobrien/* filetype():
201459243Sobrien *	Return a character that signifies a filetype
201559243Sobrien *	symbology from 4.3 ls command.
201659243Sobrien */
201759243Sobrienstatic  Char
2018167465Smpfiletype(Char *dir, Char *file)
201959243Sobrien{
202059243Sobrien    if (dir) {
2021167465Smp	Char *path;
202259243Sobrien	char   *ptr;
202359243Sobrien	struct stat statb;
202459243Sobrien
202559243Sobrien	if (nostat(dir)) return(' ');
202659243Sobrien
2027167465Smp	path = Strspl(dir, file);
2028167465Smp	ptr = short2str(path);
2029167465Smp	xfree(path);
203059243Sobrien
2031167465Smp	if (lstat(ptr, &statb) != -1) {
203259243Sobrien#ifdef S_ISLNK
203359243Sobrien	    if (S_ISLNK(statb.st_mode)) {	/* Symbolic link */
203459243Sobrien		if (adrof(STRlistlinks)) {
203559243Sobrien		    if (stat(ptr, &statb) == -1)
203659243Sobrien			return ('&');
203759243Sobrien		    else if (S_ISDIR(statb.st_mode))
203859243Sobrien			return ('>');
203959243Sobrien		    else
204059243Sobrien			return ('@');
204159243Sobrien		}
204259243Sobrien		else
204359243Sobrien		    return ('@');
204459243Sobrien	    }
204559243Sobrien#endif
204659243Sobrien#ifdef S_ISSOCK
204759243Sobrien	    if (S_ISSOCK(statb.st_mode))	/* Socket */
204859243Sobrien		return ('=');
204959243Sobrien#endif
205059243Sobrien#ifdef S_ISFIFO
205159243Sobrien	    if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
205259243Sobrien		return ('|');
205359243Sobrien#endif
205459243Sobrien#ifdef S_ISHIDDEN
205559243Sobrien	    if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
205659243Sobrien		return ('+');
205759243Sobrien#endif
2058167465Smp#ifdef S_ISCDF
2059167465Smp	    {
2060167465Smp		struct stat hpstatb;
2061167465Smp		char *p2;
2062167465Smp
2063167465Smp		p2 = strspl(ptr, "+");	/* Must append a '+' and re-stat(). */
2064167465Smp		if ((stat(p2, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode)) {
2065167465Smp		    xfree(p2);
2066167465Smp		    return ('+');	/* Context Dependent Files [hpux] */
2067167465Smp		}
2068167465Smp		xfree(p2);
2069167465Smp	    }
2070167465Smp#endif
207159243Sobrien#ifdef S_ISNWK
207259243Sobrien	    if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
207359243Sobrien		return (':');
207459243Sobrien#endif
207559243Sobrien#ifdef S_ISCHR
207659243Sobrien	    if (S_ISCHR(statb.st_mode))	/* char device */
207759243Sobrien		return ('%');
207859243Sobrien#endif
207959243Sobrien#ifdef S_ISBLK
208059243Sobrien	    if (S_ISBLK(statb.st_mode))	/* block device */
208159243Sobrien		return ('#');
208259243Sobrien#endif
208359243Sobrien#ifdef S_ISDIR
208459243Sobrien	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
208559243Sobrien		return ('/');
208659243Sobrien#endif
208759243Sobrien	    if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
208859243Sobrien		return ('*');
208959243Sobrien	}
209059243Sobrien    }
209159243Sobrien    return (' ');
209259243Sobrien} /* end filetype */
209359243Sobrien
209459243Sobrien
209559243Sobrien/* isadirectory():
209659243Sobrien *	Return trus if the file is a directory
209759243Sobrien */
209859243Sobrienstatic int
2099167465Smpisadirectory(const Char *dir, const Char *file)
2100167465Smp     /* return 1 if dir/file is a directory */
2101167465Smp     /* uses stat rather than lstat to get dest. */
210259243Sobrien{
210359243Sobrien    if (dir) {
2104167465Smp	Char *path;
2105167465Smp	char *cpath;
210659243Sobrien	struct stat statb;
210759243Sobrien
2108167465Smp	path = Strspl(dir, file);
2109167465Smp	cpath = short2str(path);
2110167465Smp	xfree(path);
2111167465Smp	if (stat(cpath, &statb) >= 0) {	/* resolve through symlink */
211259243Sobrien#ifdef S_ISSOCK
211359243Sobrien	    if (S_ISSOCK(statb.st_mode))	/* Socket */
211459243Sobrien		return 0;
211559243Sobrien#endif
211659243Sobrien#ifdef S_ISFIFO
211759243Sobrien	    if (S_ISFIFO(statb.st_mode))	/* Named Pipe */
211859243Sobrien		return 0;
211959243Sobrien#endif
212059243Sobrien	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
212159243Sobrien		return 1;
212259243Sobrien	}
212359243Sobrien    }
212459243Sobrien    return 0;
212559243Sobrien} /* end isadirectory */
212659243Sobrien
212759243Sobrien
212859243Sobrien
212959243Sobrien/* find_rows():
213059243Sobrien * 	Return how many rows needed to print sorted down columns
213159243Sobrien */
213259243Sobrienstatic int
2133167465Smpfind_rows(Char *items[], int count, int no_file_suffix)
213459243Sobrien{
2135145479Smp    int i, columns, rows;
213659243Sobrien    unsigned int maxwidth = 0;
213759243Sobrien
213859243Sobrien    for (i = 0; i < count; i++)	/* find widest string */
213959243Sobrien	maxwidth = max(maxwidth, (unsigned int) Strlen(items[i]));
214059243Sobrien
214159243Sobrien    maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
214259243Sobrien    columns = (TermH + 1) / maxwidth;	/* PWP: terminal size change */
214359243Sobrien    if (!columns)
214459243Sobrien	columns = 1;
214559243Sobrien    rows = (count + (columns - 1)) / columns;
214659243Sobrien
214759243Sobrien    return rows;
214859243Sobrien} /* end rows_needed_by_print_by_column */
214959243Sobrien
215059243Sobrien
215159243Sobrien/* print_by_column():
215259243Sobrien * 	Print sorted down columns or across columns when the first
215359243Sobrien *	word of $listflags shell variable contains 'x'.
215459243Sobrien *
215559243Sobrien */
215659243Sobrienvoid
2157167465Smpprint_by_column(Char *dir, Char *items[], int count, int no_file_suffix)
215859243Sobrien{
2159145479Smp    int i, r, c, columns, rows;
2160167465Smp    size_t w;
2161167465Smp    unsigned int wx, maxwidth = 0;
216259243Sobrien    Char *val;
2163145479Smp    int across;
216459243Sobrien
216559243Sobrien    lbuffed = 0;		/* turn off line buffering */
216659243Sobrien
216759243Sobrien
216859243Sobrien    across = ((val = varval(STRlistflags)) != STRNULL) &&
216959243Sobrien	     (Strchr(val, 'x') != NULL);
217059243Sobrien
2171145479Smp    for (i = 0; i < count; i++)	{ /* find widest string */
2172145479Smp	maxwidth = max(maxwidth, (unsigned int) NLSStringWidth(items[i]));
2173145479Smp    }
217459243Sobrien
217559243Sobrien    maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
217659243Sobrien    columns = TermH / maxwidth;		/* PWP: terminal size change */
217759243Sobrien    if (!columns || !isatty(didfds ? 1 : SHOUT))
217859243Sobrien	columns = 1;
217959243Sobrien    rows = (count + (columns - 1)) / columns;
218059243Sobrien
218159243Sobrien    i = -1;
218259243Sobrien    for (r = 0; r < rows; r++) {
218359243Sobrien	for (c = 0; c < columns; c++) {
218459243Sobrien	    i = across ? (i + 1) : (c * rows + r);
218559243Sobrien
218659243Sobrien	    if (i < count) {
2187145479Smp		wx = 0;
2188167465Smp		w = Strlen(items[i]);
218959243Sobrien
219059243Sobrien#ifdef COLOR_LS_F
219159243Sobrien		if (no_file_suffix) {
219259243Sobrien		    /* Print the command name */
219359243Sobrien		    Char f = items[i][w - 1];
219459243Sobrien		    items[i][w - 1] = 0;
219559243Sobrien		    print_with_color(items[i], w - 1, f);
2196167465Smp		    items[i][w - 1] = f;
219759243Sobrien		}
219859243Sobrien		else {
219959243Sobrien		    /* Print filename followed by '/' or '*' or ' ' */
220059243Sobrien		    print_with_color(items[i], w, filetype(dir, items[i]));
2201145479Smp		    wx++;
220259243Sobrien		}
220359243Sobrien#else /* ifndef COLOR_LS_F */
220459243Sobrien		if (no_file_suffix) {
220559243Sobrien		    /* Print the command name */
220659243Sobrien		    xprintf("%S", items[i]);
220759243Sobrien		}
220859243Sobrien		else {
220959243Sobrien		    /* Print filename followed by '/' or '*' or ' ' */
2210167465Smp		    xprintf("%-S%c", items[i], filetype(dir, items[i]));
2211145479Smp		    wx++;
221259243Sobrien		}
221359243Sobrien#endif /* COLOR_LS_F */
221459243Sobrien
2215145479Smp		if (c < (columns - 1)) {	/* Not last column? */
2216167465Smp		    w = NLSStringWidth(items[i]) + wx;
221759243Sobrien		    for (; w < maxwidth; w++)
221859243Sobrien			xputchar(' ');
2219145479Smp		}
222059243Sobrien	    }
222159243Sobrien	    else if (across)
222259243Sobrien		break;
222359243Sobrien	}
222459243Sobrien	if (Tty_raw_mode)
222559243Sobrien	    xputchar('\r');
222659243Sobrien	xputchar('\n');
222759243Sobrien    }
222859243Sobrien
222959243Sobrien    lbuffed = 1;		/* turn back on line buffering */
223059243Sobrien    flush();
223159243Sobrien} /* end print_by_column */
223259243Sobrien
223359243Sobrien
223459243Sobrien/* StrQcmp():
223559243Sobrien *	Compare strings ignoring the quoting chars
223659243Sobrien */
223759243Sobrienint
2238167465SmpStrQcmp(const Char *str1, const Char *str2)
223959243Sobrien{
224059243Sobrien    for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM);
224159243Sobrien	 str1++, str2++)
224259243Sobrien	continue;
224359243Sobrien    /*
224459243Sobrien     * The following case analysis is necessary so that characters which look
224559243Sobrien     * negative collate low against normal characters but high against the
224659243Sobrien     * end-of-string NUL.
224759243Sobrien     */
224859243Sobrien    if (*str1 == '\0' && *str2 == '\0')
224959243Sobrien	return (0);
225059243Sobrien    else if (*str1 == '\0')
225159243Sobrien	return (-1);
225259243Sobrien    else if (*str2 == '\0')
225359243Sobrien	return (1);
225459243Sobrien    else
225559243Sobrien	return ((*str1 & TRIM) - (*str2 & TRIM));
225659243Sobrien} /* end StrQcmp */
225759243Sobrien
225859243Sobrien
225959243Sobrien/* fcompare():
2260167465Smp * 	Comparison routine for qsort, (Char **, Char **)
226159243Sobrien */
226259243Sobrienint
2263167465Smpfcompare(const void *xfile1, const void *xfile2)
226459243Sobrien{
2265167465Smp    const Char *const *file1 = xfile1, *const *file2 = xfile2;
2266167465Smp
2267167465Smp    return collate(*file1, *file2);
226859243Sobrien} /* end fcompare */
226959243Sobrien
227059243Sobrien
227159243Sobrien/* catn():
227259243Sobrien *	Concatenate src onto tail of des.
227359243Sobrien *	Des is a string whose maximum length is count.
227459243Sobrien *	Always null terminate.
227559243Sobrien */
227659243Sobrienvoid
2277167465Smpcatn(Char *des, const Char *src, int count)
227859243Sobrien{
2279167465Smp    while (*des && --count > 0)
228059243Sobrien	des++;
2281167465Smp    while (--count > 0)
228259243Sobrien	if ((*des++ = *src++) == 0)
228359243Sobrien	    return;
228459243Sobrien    *des = '\0';
228559243Sobrien} /* end catn */
228659243Sobrien
228759243Sobrien
228859243Sobrien/* copyn():
228959243Sobrien *	 like strncpy but always leave room for trailing \0
229059243Sobrien *	 and always null terminate.
229159243Sobrien */
229259243Sobrienvoid
2293167465Smpcopyn(Char *des, const Char *src, size_t count)
229459243Sobrien{
2295167465Smp    while (--count != 0)
229659243Sobrien	if ((*des++ = *src++) == 0)
229759243Sobrien	    return;
229859243Sobrien    *des = '\0';
229959243Sobrien} /* end copyn */
230059243Sobrien
230159243Sobrien
230259243Sobrien/* tgetenv():
230359243Sobrien *	like it's normal string counter-part
230459243Sobrien */
230559243SobrienChar *
2306167465Smptgetenv(Char *str)
230759243Sobrien{
230859243Sobrien    Char  **var;
2309145479Smp    size_t  len;
2310145479Smp    int     res;
231159243Sobrien
2312145479Smp    len = Strlen(str);
231359243Sobrien    /* Search the STR_environ for the entry matching str. */
231459243Sobrien    for (var = STR_environ; var != NULL && *var != NULL; var++)
231559243Sobrien	if (Strlen(*var) >= len && (*var)[len] == '=') {
231659243Sobrien	  /* Temporarily terminate the string so we can copy the variable
231759243Sobrien	     name. */
231859243Sobrien	    (*var)[len] = '\0';
231959243Sobrien	    res = StrQcmp(*var, str);
232059243Sobrien	    /* Restore the '=' and return a pointer to the value of the
232159243Sobrien	       environment variable. */
232259243Sobrien	    (*var)[len] = '=';
232359243Sobrien	    if (res == 0)
232459243Sobrien		return (&((*var)[len + 1]));
232559243Sobrien	}
232659243Sobrien    return (NULL);
232759243Sobrien} /* end tgetenv */
232859243Sobrien
232959243Sobrien
233059243Sobrienstruct scroll_tab_list *scroll_tab = 0;
233159243Sobrien
233259243Sobrienstatic void
2333167465Smpadd_scroll_tab(Char *item)
233459243Sobrien{
233559243Sobrien    struct scroll_tab_list *new_scroll;
233659243Sobrien
2337167465Smp    new_scroll = xmalloc(sizeof(struct scroll_tab_list));
233859243Sobrien    new_scroll->element = Strsave(item);
233959243Sobrien    new_scroll->next = scroll_tab;
234059243Sobrien    scroll_tab = new_scroll;
234159243Sobrien}
234259243Sobrien
234359243Sobrienstatic void
2344167465Smpchoose_scroll_tab(struct Strbuf *exp_name, int cnt)
234559243Sobrien{
234659243Sobrien    struct scroll_tab_list *loop;
234759243Sobrien    int tmp = cnt;
234859243Sobrien    Char **ptr;
234959243Sobrien
2350167465Smp    ptr = xmalloc(sizeof(Char *) * cnt);
2351167465Smp    cleanup_push(ptr, xfree);
235259243Sobrien
235359243Sobrien    for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next)
235459243Sobrien	ptr[--tmp] = loop->element;
235559243Sobrien
2356167465Smp    qsort(ptr, cnt, sizeof(Char *), fcompare);
2357167465Smp
2358167465Smp    exp_name->len = 0;
2359167465Smp    Strbuf_append(exp_name, ptr[curchoice]);
2360167465Smp    Strbuf_terminate(exp_name);
2361167465Smp    cleanup_until(ptr);
236259243Sobrien}
236359243Sobrien
236459243Sobrienstatic void
2365167465Smpfree_scroll_tab(void)
236659243Sobrien{
236759243Sobrien    struct scroll_tab_list *loop;
236859243Sobrien
236959243Sobrien    while(scroll_tab) {
237059243Sobrien	loop = scroll_tab;
237159243Sobrien	scroll_tab = scroll_tab->next;
2372167465Smp	xfree(loop->element);
2373167465Smp	xfree(loop);
237459243Sobrien    }
237559243Sobrien}
2376