tw.parse.c revision 131962
1214501Srpaulo/* $Header: /src/pub/tcsh/tw.parse.c,v 3.96 2004/01/23 16:21:33 christos Exp $ */
2214501Srpaulo/*
3214501Srpaulo * tw.parse.c: Everyone has taken a shot in this futile effort to
4214501Srpaulo *	       lexically analyze a csh line... Well we cannot good
5252726Srpaulo *	       a job as good as sh.lex.c; but we try. Amazing that
6252726Srpaulo *	       it works considering how many hands have touched this code
7214501Srpaulo */
8214501Srpaulo/*-
9214501Srpaulo * Copyright (c) 1980, 1991 The Regents of the University of California.
10214501Srpaulo * All rights reserved.
11214501Srpaulo *
12214501Srpaulo * Redistribution and use in source and binary forms, with or without
13214501Srpaulo * modification, are permitted provided that the following conditions
14214501Srpaulo * are met:
15214501Srpaulo * 1. Redistributions of source code must retain the above copyright
16214501Srpaulo *    notice, this list of conditions and the following disclaimer.
17214501Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
18214501Srpaulo *    notice, this list of conditions and the following disclaimer in the
19214501Srpaulo *    documentation and/or other materials provided with the distribution.
20214501Srpaulo * 3. Neither the name of the University nor the names of its contributors
21214501Srpaulo *    may be used to endorse or promote products derived from this software
22214501Srpaulo *    without specific prior written permission.
23214501Srpaulo *
24214501Srpaulo * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25214501Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26214501Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27214501Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28214501Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29214501Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30214501Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31214501Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32214501Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33214501Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34214501Srpaulo * SUCH DAMAGE.
35214501Srpaulo */
36214501Srpaulo#include "sh.h"
37214501Srpaulo
38214501SrpauloRCSID("$Id: tw.parse.c,v 3.96 2004/01/23 16:21:33 christos Exp $")
39214501Srpaulo
40214501Srpaulo#include "tw.h"
41214501Srpaulo#include "ed.h"
42214501Srpaulo#include "tc.h"
43214501Srpaulo
44214501Srpaulo#ifdef WINNT_NATIVE
45214501Srpaulo#include "nt.const.h"
46214501Srpaulo#endif /* WINNT_NATIVE */
47214501Srpaulo#define EVEN(x) (((x) & 1) != 1)
48214501Srpaulo
49214501Srpaulo#define DOT_NONE	0	/* Don't display dot files		*/
50214501Srpaulo#define DOT_NOT		1	/* Don't display dot or dot-dot		*/
51214501Srpaulo#define DOT_ALL		2	/* Display all dot files		*/
52214501Srpaulo
53214501Srpaulo/*  TW_NONE,	       TW_COMMAND,     TW_VARIABLE,    TW_LOGNAME,	*/
54214501Srpaulo/*  TW_FILE,	       TW_DIRECTORY,   TW_VARLIST,     TW_USER,		*/
55214501Srpaulo/*  TW_COMPLETION,     TW_ALIAS,       TW_SHELLVAR,    TW_ENVVAR,	*/
56214501Srpaulo/*  TW_BINDING,        TW_WORDLIST,    TW_LIMIT,       TW_SIGNAL	*/
57214501Srpaulo/*  TW_JOB,	       TW_EXPLAIN,     TW_TEXT,	       TW_GRPNAME	*/
58214501Srpaulostatic void (*tw_start_entry[]) __P((DIR *, Char *)) = {
59214501Srpaulo    tw_file_start,     tw_cmd_start,   tw_var_start,   tw_logname_start,
60214501Srpaulo    tw_file_start,     tw_file_start,  tw_vl_start,    tw_logname_start,
61214501Srpaulo    tw_complete_start, tw_alias_start, tw_var_start,   tw_var_start,
62214501Srpaulo    tw_bind_start,     tw_wl_start,    tw_limit_start, tw_sig_start,
63214501Srpaulo    tw_job_start,      tw_file_start,  tw_file_start,  tw_grpname_start
64214501Srpaulo};
65214501Srpaulo
66214501Srpaulostatic Char * (*tw_next_entry[]) __P((Char *, int *)) = {
67214501Srpaulo    tw_file_next,      tw_cmd_next,    tw_var_next,    tw_logname_next,
68214501Srpaulo    tw_file_next,      tw_file_next,   tw_var_next,    tw_logname_next,
69214501Srpaulo    tw_var_next,       tw_var_next,    tw_shvar_next,  tw_envvar_next,
70214501Srpaulo    tw_bind_next,      tw_wl_next,     tw_limit_next,  tw_sig_next,
71214501Srpaulo    tw_job_next,       tw_file_next,   tw_file_next,   tw_grpname_next
72214501Srpaulo};
73214501Srpaulo
74214501Srpaulostatic void (*tw_end_entry[]) __P((void)) = {
75214501Srpaulo    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
76252726Srpaulo    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
77252726Srpaulo    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
78252726Srpaulo    tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
79252726Srpaulo    tw_dir_end,	       tw_dir_end,     tw_dir_end,    tw_grpname_end
80252726Srpaulo};
81252726Srpaulo
82252726Srpaulo/* #define TDEBUG */
83252726Srpaulo
84252726Srpaulo/* Set to TRUE if recexact is set and an exact match is found
85252726Srpaulo * along with other, longer, matches.
86252726Srpaulo */
87252726Srpaulo
88252726Srpauloint curchoice = -1;
89252726Srpaulo
90252726Srpauloint match_unique_match = FALSE;
91252726Srpauloint non_unique_match = FALSE;
92252726Srpaulostatic bool SearchNoDirErr = 0;	/* t_search returns -2 if dir is unreadable */
93214501Srpaulo
94214501Srpaulo/* state so if a completion is interrupted, the input line doesn't get
95214501Srpaulo   nuked */
96214501Srpauloint InsideCompletion = 0;
97214501Srpaulo
98214501Srpaulo/* do the expand or list on the command line -- SHOULD BE REPLACED */
99214501Srpaulo
100214501Srpauloextern Char NeedsRedraw;	/* from ed.h */
101214501Srpauloextern int Tty_raw_mode;
102214501Srpauloextern int TermH;		/* from the editor routines */
103214501Srpauloextern int lbuffed;		/* from sh.print.c */
104214501Srpaulo
105214501Srpaulostatic	void	 extract_dir_and_name	__P((Char *, Char *, Char *));
106214501Srpaulostatic	int	 insert_meta		__P((Char *, Char *, Char *, bool));
107214501Srpaulostatic	Char	*tilde			__P((Char *, Char *));
108214501Srpaulo#ifndef __MVS__
109214501Srpaulostatic  int      expand_dir		__P((Char *, Char *, DIR  **, COMMAND));
110214501Srpaulo#endif
111214501Srpaulostatic	bool	 nostat			__P((Char *));
112214501Srpaulostatic	Char	 filetype		__P((Char *, Char *));
113214501Srpaulostatic	int	 t_glob			__P((Char ***, int));
114214501Srpaulostatic	int	 c_glob			__P((Char ***));
115214501Srpaulostatic	int	 is_prefix		__P((Char *, Char *));
116214501Srpaulostatic	int	 is_prefixmatch		__P((Char *, Char *, int));
117214501Srpaulostatic	int	 is_suffix		__P((Char *, Char *));
118214501Srpaulostatic	int	 recognize		__P((Char *, Char *, int, int, int,
119214501Srpaulo					     int));
120214501Srpaulostatic	int	 ignored		__P((Char *));
121214501Srpaulostatic	int	 isadirectory		__P((Char *, Char *));
122214501Srpaulo#ifndef __MVS__
123214501Srpaulostatic  int      tw_collect_items	__P((COMMAND, int, Char *, Char *,
124214501Srpaulo					     Char *, Char *, int));
125214501Srpaulostatic  int      tw_collect		__P((COMMAND, int, Char *, Char *,
126214501Srpaulo					     Char **, Char *, int, DIR *));
127214501Srpaulo#endif
128214501Srpaulostatic	Char 	 tw_suffix		__P((int, Char *, Char *, Char *,
129214501Srpaulo					     Char *));
130214501Srpaulostatic	void 	 tw_fixword		__P((int, Char *, Char *, Char *, int));
131214501Srpaulostatic	void	 tw_list_items		__P((int, int, int));
132214501Srpaulostatic 	void	 add_scroll_tab		__P((Char *));
133214501Srpaulostatic 	void 	 choose_scroll_tab	__P((Char **, int));
134214501Srpaulostatic	void	 free_scroll_tab	__P((void));
135214501Srpaulostatic	int	 find_rows		__P((Char *[], int, int));
136214501Srpaulo
137214501Srpaulo#ifdef notdef
138214501Srpaulo/*
139214501Srpaulo * If we find a set command, then we break a=b to a= and word becomes
140214501Srpaulo * b else, we don't break a=b. [don't use that; splits words badly and
141214501Srpaulo * messes up tw_complete()]
142214501Srpaulo */
143214501Srpaulo#define isaset(c, w) ((w)[-1] == '=' && \
144214501Srpaulo		      ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
145214501Srpaulo		       ((c[3] == ' ' || (c)[3] == '\t'))))
146214501Srpaulo#endif
147214501Srpaulo
148214501Srpaulo#define QLINESIZE (INBUFSIZE + 1)
149214501Srpaulo
150214501Srpaulo/* TRUE if character must be quoted */
151214501Srpaulo#define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#')
152214501Srpaulo/* TRUE if double quotes don't protect character */
153214501Srpaulo#define tricky_dq(w) (cmap(w, _DOL | _QB))
154214501Srpaulo
155214501Srpaulo/* tenematch():
156214501Srpaulo *	Return:
157214501Srpaulo *		> 1:    No. of items found
158214501Srpaulo *		= 1:    Exactly one match / spelling corrected
159214501Srpaulo *		= 0:    No match / spelling was correct
160214501Srpaulo *		< 0:    Error (incl spelling correction impossible)
161214501Srpaulo */
162214501Srpauloint
163214501Srpaulotenematch(inputline, num_read, command)
164214501Srpaulo    Char   *inputline;		/* match string prefix */
165214501Srpaulo    int     num_read;		/* # actually in inputline */
166214501Srpaulo    COMMAND command;		/* LIST or RECOGNIZE or PRINT_HELP */
167214501Srpaulo
168214501Srpaulo{
169214501Srpaulo    Char    qline[QLINESIZE];
170214501Srpaulo    Char    qu = 0, *pat = STRNULL;
171214501Srpaulo    Char   *str_end, *cp, *wp, *wordp;
172214501Srpaulo    Char   *cmd_start, *word_start, *word;
173214501Srpaulo    Char   *ocmd_start = NULL, *oword_start = NULL, *oword = NULL;
174214501Srpaulo    int	    suf = 0;
175214501Srpaulo    int     space_left;
176214501Srpaulo    int     looking;		/* what we are looking for		*/
177214501Srpaulo    int     search_ret;		/* what search returned for debugging 	*/
178214501Srpaulo    int     backq = 0;
179214501Srpaulo
180214501Srpaulo    if (num_read > QLINESIZE - 1)
181214501Srpaulo	return -1;
182214501Srpaulo    str_end = &inputline[num_read];
183214501Srpaulo
184214501Srpaulo    word_start = inputline;
185214501Srpaulo    word = cmd_start = wp = qline;
186214501Srpaulo    for (cp = inputline; cp < str_end; cp++) {
187214501Srpaulo        if (!cmap(qu, _ESC)) {
188214501Srpaulo	    if (cmap(*cp, _QF|_ESC)) {
189214501Srpaulo		if (qu == 0 || qu == *cp) {
190214501Srpaulo		    qu ^= *cp;
191214501Srpaulo		    continue;
192214501Srpaulo		}
193214501Srpaulo	    }
194214501Srpaulo	    if (qu != '\'' && cmap(*cp, _QB)) {
195214501Srpaulo		if ((backq ^= 1) != 0) {
196214501Srpaulo		    ocmd_start = cmd_start;
197214501Srpaulo		    oword_start = word_start;
198214501Srpaulo		    oword = word;
199214501Srpaulo		    word_start = cp + 1;
200214501Srpaulo		    word = cmd_start = wp + 1;
201214501Srpaulo		}
202214501Srpaulo		else {
203214501Srpaulo		    cmd_start = ocmd_start;
204214501Srpaulo		    word_start = oword_start;
205214501Srpaulo		    word = oword;
206214501Srpaulo		}
207214501Srpaulo		*wp++ = *cp;
208214501Srpaulo		continue;
209214501Srpaulo	    }
210214501Srpaulo	}
211214501Srpaulo	if (iscmdmeta(*cp))
212214501Srpaulo	    cmd_start = wp + 1;
213214501Srpaulo
214214501Srpaulo	/* Don't quote '/' to make the recognize stuff work easily */
215214501Srpaulo	/* Don't quote '$' in double quotes */
216214501Srpaulo
217214501Srpaulo	if (cmap(*cp, _ESC) && cp < str_end - 1 && cp[1] == HIST)
218214501Srpaulo	  *wp = *++cp | QUOTE;
219214501Srpaulo	else if (qu && (tricky(*cp) || *cp == '~') && !(qu == '\"' && tricky_dq(*cp)))
220214501Srpaulo	  *wp = *cp | QUOTE;
221214501Srpaulo	else
222214501Srpaulo	  *wp = *cp;
223214501Srpaulo	if (ismetahash(*wp) /* || isaset(cmd_start, wp + 1) */)
224214501Srpaulo	    word = wp + 1, word_start = cp + 1;
225214501Srpaulo	wp++;
226214501Srpaulo	if (cmap(qu, _ESC))
227214501Srpaulo	    qu = 0;
228214501Srpaulo      }
229214501Srpaulo    *wp = 0;
230214501Srpaulo
231214501Srpaulo#ifdef masscomp
232214501Srpaulo    /*
233214501Srpaulo     * Avoid a nasty message from the RTU 4.1A & RTU 5.0 compiler concerning
234214501Srpaulo     * the "overuse of registers". According to the compiler release notes,
235214501Srpaulo     * incorrect code may be produced unless the offending expression is
236214501Srpaulo     * rewritten. Therefore, we can't just ignore it, DAS DEC-90.
237214501Srpaulo     */
238214501Srpaulo    space_left = QLINESIZE - 1;
239214501Srpaulo    space_left -= word - qline;
240214501Srpaulo#else
241214501Srpaulo    space_left = QLINESIZE - 1 - (int) (word - qline);
242214501Srpaulo#endif
243214501Srpaulo
244214501Srpaulo    /*
245214501Srpaulo     *  SPECIAL HARDCODED COMPLETIONS:
246214501Srpaulo     *    first word of command       -> TW_COMMAND
247214501Srpaulo     *    everything else             -> TW_ZERO
248214501Srpaulo     *
249252726Srpaulo     */
250214501Srpaulo    looking = starting_a_command(word - 1, qline) ?
251214501Srpaulo	TW_COMMAND : TW_ZERO;
252214501Srpaulo
253252726Srpaulo    wordp = word;
254214501Srpaulo
255214501Srpaulo#ifdef TDEBUG
256214501Srpaulo    xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking);
257214501Srpaulo    xprintf("\ncmd_start:%S:\n", cmd_start);
258214501Srpaulo    xprintf("qline:%S:\n", qline);
259214501Srpaulo    xprintf("qline:");
260214501Srpaulo    for (wp = qline; *wp; wp++)
261214501Srpaulo	xprintf("%c", *wp & QUOTE ? '-' : ' ');
262214501Srpaulo    xprintf(":\n");
263214501Srpaulo    xprintf("word:%S:\n", word);
264214501Srpaulo    xprintf("word:");
265214501Srpaulo    /* Must be last, so wp is still pointing to the end of word */
266214501Srpaulo    for (wp = word; *wp; wp++)
267214501Srpaulo	xprintf("%c", *wp & QUOTE ? '-' : ' ');
268214501Srpaulo    xprintf(":\n");
269214501Srpaulo#endif
270214501Srpaulo
271214501Srpaulo    if ((looking == TW_COMMAND || looking == TW_ZERO) &&
272214501Srpaulo        (command == RECOGNIZE || command == LIST || command == SPELL ||
273214501Srpaulo	 command == RECOGNIZE_SCROLL)) {
274214501Srpaulo#ifdef TDEBUG
275214501Srpaulo	xprintf(CGETS(30, 2, "complete %d "), looking);
276214501Srpaulo#endif
277252726Srpaulo	looking = tw_complete(cmd_start, &wordp, &pat, looking, &suf);
278252726Srpaulo#ifdef TDEBUG
279214501Srpaulo	xprintf(CGETS(30, 3, "complete %d %S\n"), looking, pat);
280252726Srpaulo#endif
281252726Srpaulo    }
282214501Srpaulo
283214501Srpaulo    switch (command) {
284214501Srpaulo	Char    buffer[FILSIZ + 1], *bptr;
285214501Srpaulo	Char   *slshp;
286214501Srpaulo	Char   *items[2], **ptr;
287214501Srpaulo	int     i, count;
288214501Srpaulo
289214501Srpaulo    case RECOGNIZE:
290214501Srpaulo    case RECOGNIZE_SCROLL:
291214501Srpaulo    case RECOGNIZE_ALL:
292214501Srpaulo	if (adrof(STRautocorrect)) {
293214501Srpaulo	    if ((slshp = Strrchr(wordp, '/')) != NULL && slshp[1] != '\0') {
294214501Srpaulo		SearchNoDirErr = 1;
295214501Srpaulo		for (bptr = wordp; bptr < slshp; bptr++) {
296214501Srpaulo		    /*
297214501Srpaulo		     * do not try to correct spelling of words containing
298214501Srpaulo		     * globbing characters
299214501Srpaulo		     */
300214501Srpaulo		    if (isglob(*bptr)) {
301214501Srpaulo			SearchNoDirErr = 0;
302214501Srpaulo			break;
303214501Srpaulo		    }
304252726Srpaulo		}
305214501Srpaulo	    }
306214501Srpaulo	}
307214501Srpaulo	else
308214501Srpaulo	    slshp = STRNULL;
309252726Srpaulo	search_ret = t_search(wordp, wp, command, space_left, looking, 1,
310214501Srpaulo			      pat, suf);
311214501Srpaulo	SearchNoDirErr = 0;
312214501Srpaulo
313214501Srpaulo	if (search_ret == -2) {
314214501Srpaulo	    Char    rword[FILSIZ + 1];
315214501Srpaulo
316214501Srpaulo	    (void) Strcpy(rword, slshp);
317214501Srpaulo	    if (slshp != STRNULL)
318214501Srpaulo		*slshp = '\0';
319214501Srpaulo	    search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking,
320214501Srpaulo				  pat, suf);
321214501Srpaulo	    if (search_ret == 1) {
322214501Srpaulo		(void) Strcat(wordp, rword);
323214501Srpaulo		wp = wordp + (int) Strlen(wordp);
324214501Srpaulo		search_ret = t_search(wordp, wp, command, space_left,
325214501Srpaulo				      looking, 1, pat, suf);
326214501Srpaulo	    }
327214501Srpaulo	}
328214501Srpaulo	if (*wp && insert_meta(word_start, str_end, word, !qu) < 0)
329214501Srpaulo	    return -1;		/* error inserting */
330214501Srpaulo	return search_ret;
331214501Srpaulo
332214501Srpaulo    case SPELL:
333214501Srpaulo	for (bptr = word_start; bptr < str_end; bptr++) {
334214501Srpaulo	    /*
335214501Srpaulo	     * do not try to correct spelling of words containing globbing
336252726Srpaulo	     * characters
337214501Srpaulo	     */
338214501Srpaulo	    if (isglob(*bptr))
339252726Srpaulo		return 0;
340252726Srpaulo	}
341214501Srpaulo	search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking,
342214501Srpaulo			      pat, suf);
343214501Srpaulo	if (search_ret == 1) {
344252726Srpaulo	    if (insert_meta(word_start, str_end, word, !qu) < 0)
345252726Srpaulo		return -1;		/* error inserting */
346214501Srpaulo	}
347214501Srpaulo	return search_ret;
348214501Srpaulo
349252726Srpaulo    case PRINT_HELP:
350252726Srpaulo	do_help(cmd_start);
351252726Srpaulo	return 1;
352252726Srpaulo
353252726Srpaulo    case GLOB:
354252726Srpaulo    case GLOB_EXPAND:
355252726Srpaulo	(void) Strncpy(buffer, wordp, FILSIZ + 1);
356252726Srpaulo	items[0] = buffer;
357252726Srpaulo	items[1] = NULL;
358252726Srpaulo	ptr = items;
359252726Srpaulo	count = (looking == TW_COMMAND && Strchr(wordp, '/') == 0) ?
360252726Srpaulo		c_glob(&ptr) :
361252726Srpaulo		t_glob(&ptr, looking == TW_COMMAND);
362252726Srpaulo	if (count > 0) {
363214501Srpaulo	    if (command == GLOB)
364214501Srpaulo		print_by_column(STRNULL, ptr, count, 0);
365214501Srpaulo	    else {
366214501Srpaulo		DeleteBack(str_end - word_start);/* get rid of old word */
367252726Srpaulo		for (i = 0; i < count; i++)
368214501Srpaulo		    if (ptr[i] && *ptr[i]) {
369214501Srpaulo			(void) quote(ptr[i]);
370214501Srpaulo			if (insert_meta(0, 0, ptr[i], 0) < 0 ||
371214501Srpaulo			    InsertStr(STRspace) < 0) {
372252726Srpaulo			    blkfree(ptr);
373214501Srpaulo			    return -1;		/* error inserting */
374214501Srpaulo			}
375214501Srpaulo		    }
376214501Srpaulo	    }
377214501Srpaulo	    blkfree(ptr);
378214501Srpaulo	}
379214501Srpaulo	return count;
380214501Srpaulo
381214501Srpaulo    case VARS_EXPAND:
382214501Srpaulo	if (dollar(buffer, word)) {
383214501Srpaulo	    if (insert_meta(word_start, str_end, buffer, !qu) < 0)
384214501Srpaulo		return -1;		/* error inserting */
385214501Srpaulo	    return 1;
386214501Srpaulo	}
387214501Srpaulo	return 0;
388214501Srpaulo
389214501Srpaulo    case PATH_NORMALIZE:
390214501Srpaulo	if ((bptr = dnormalize(wordp, symlinks == SYM_IGNORE ||
391214501Srpaulo				      symlinks == SYM_EXPAND)) != NULL) {
392214501Srpaulo	    (void) Strcpy(buffer, bptr);
393214501Srpaulo	    xfree((ptr_t) bptr);
394214501Srpaulo	    if (insert_meta(word_start, str_end, buffer, !qu) < 0)
395214501Srpaulo		return -1;		/* error inserting */
396214501Srpaulo	    return 1;
397214501Srpaulo	}
398214501Srpaulo	return 0;
399214501Srpaulo
400214501Srpaulo    case COMMAND_NORMALIZE:
401214501Srpaulo	if (!cmd_expand(wordp, buffer))
402214501Srpaulo	    return 0;
403214501Srpaulo	if (insert_meta(word_start, str_end, buffer, !qu) < 0)
404214501Srpaulo	    return -1;		/* error inserting */
405214501Srpaulo	return 1;
406214501Srpaulo
407214501Srpaulo    case LIST:
408214501Srpaulo    case LIST_ALL:
409214501Srpaulo	search_ret = t_search(wordp, wp, LIST, space_left, looking, 1,
410214501Srpaulo			      pat, suf);
411214501Srpaulo	return search_ret;
412214501Srpaulo
413214501Srpaulo    default:
414214501Srpaulo	xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname);
415214501Srpaulo	return 1;
416214501Srpaulo
417214501Srpaulo    }
418214501Srpaulo} /* end tenematch */
419214501Srpaulo
420252726Srpaulo
421252726Srpaulo/* t_glob():
422252726Srpaulo * 	Return a list of files that match the pattern
423252726Srpaulo */
424252726Srpaulostatic int
425252726Srpaulot_glob(v, cmd)
426252726Srpaulo    register Char ***v;
427214501Srpaulo    int cmd;
428214501Srpaulo{
429214501Srpaulo    jmp_buf_t osetexit;
430214501Srpaulo
431214501Srpaulo    if (**v == 0)
432214501Srpaulo	return (0);
433214501Srpaulo    gflag = 0, tglob(*v);
434214501Srpaulo    if (gflag) {
435214501Srpaulo	getexit(osetexit);	/* make sure to come back here */
436214501Srpaulo	if (setexit() == 0)
437214501Srpaulo	    *v = globall(*v);
438214501Srpaulo	resexit(osetexit);
439214501Srpaulo	gargv = 0;
440214501Srpaulo	if (haderr) {
441214501Srpaulo	    haderr = 0;
442214501Srpaulo	    NeedsRedraw = 1;
443214501Srpaulo	    return (-1);
444214501Srpaulo	}
445214501Srpaulo	if (*v == 0)
446214501Srpaulo	    return (0);
447214501Srpaulo    }
448252726Srpaulo    else
449252726Srpaulo	return (0);
450252726Srpaulo
451252726Srpaulo    if (cmd) {
452252726Srpaulo	Char **av = *v, *p;
453252726Srpaulo	int fwd, i, ac = gargc;
454252726Srpaulo
455252726Srpaulo	for (i = 0, fwd = 0; i < ac; i++)
456252726Srpaulo	    if (!executable(NULL, av[i], 0)) {
457252726Srpaulo		fwd++;
458252726Srpaulo		p = av[i];
459252726Srpaulo		av[i] = NULL;
460252726Srpaulo		xfree((ptr_t) p);
461252726Srpaulo	    }
462252726Srpaulo	    else if (fwd)
463252726Srpaulo		av[i - fwd] = av[i];
464252726Srpaulo
465214501Srpaulo	if (fwd)
466214501Srpaulo	    av[i - fwd] = av[i];
467252726Srpaulo	gargc -= fwd;
468252726Srpaulo	av[gargc] = NULL;
469214501Srpaulo    }
470214501Srpaulo
471214501Srpaulo    return (gargc);
472214501Srpaulo} /* end t_glob */
473252726Srpaulo
474214501Srpaulo
475214501Srpaulo/* c_glob():
476252726Srpaulo * 	Return a list of commands that match the pattern
477252726Srpaulo */
478252726Srpaulostatic int
479252726Srpauloc_glob(v)
480252726Srpaulo    register Char ***v;
481252726Srpaulo{
482252726Srpaulo    Char *pat = **v, *cmd, **av;
483252726Srpaulo    Char dir[MAXPATHLEN+1];
484252726Srpaulo    int flag, at, ac;
485252726Srpaulo
486252726Srpaulo    if (pat == NULL)
487252726Srpaulo	return (0);
488252726Srpaulo
489252726Srpaulo    ac = 0;
490252726Srpaulo    at = 10;
491252726Srpaulo    av = (Char **) xmalloc((size_t) (at * sizeof(Char *)));
492252726Srpaulo    av[ac] = NULL;
493252726Srpaulo
494252726Srpaulo    tw_cmd_start(NULL, NULL);
495252726Srpaulo    while ((cmd = tw_cmd_next(dir, &flag)) != NULL)
496252726Srpaulo	if (Gmatch(cmd, pat)) {
497252726Srpaulo	    if (ac + 1 >= at) {
498252726Srpaulo		at += 10;
499252726Srpaulo		av = (Char **) xrealloc((ptr_t) av,
500252726Srpaulo					(size_t) (at * sizeof(Char *)));
501252726Srpaulo	    }
502252726Srpaulo	    av[ac++] = Strsave(cmd);
503252726Srpaulo	    av[ac] = NULL;
504252726Srpaulo	}
505252726Srpaulo    tw_dir_end();
506252726Srpaulo    *v = av;
507252726Srpaulo
508252726Srpaulo    return (ac);
509252726Srpaulo} /* end c_glob */
510252726Srpaulo
511252726Srpaulo
512252726Srpaulo/* insert_meta():
513252726Srpaulo *      change the word before the cursor.
514252726Srpaulo *        cp must point to the start of the unquoted word.
515252726Srpaulo *        cpend to the end of it.
516252726Srpaulo *        word is the text that has to be substituted.
517252726Srpaulo *      strategy:
518252726Srpaulo *        try to keep all the quote characters of the user's input.
519252726Srpaulo *        change quote type only if necessary.
520252726Srpaulo */
521252726Srpaulostatic int
522252726Srpauloinsert_meta(cp, cpend, word, closequotes)
523252726Srpaulo    Char   *cp;
524252726Srpaulo    Char   *cpend;
525252726Srpaulo    Char   *word;
526252726Srpaulo    bool    closequotes;
527252726Srpaulo{
528252726Srpaulo    Char buffer[2 * FILSIZ + 1], *bptr, *wptr;
529252726Srpaulo    int in_sync = (cp != NULL);
530252726Srpaulo    int qu = 0;
531252726Srpaulo    int ndel = (int) (cp ? cpend - cp : 0);
532252726Srpaulo    Char w, wq;
533252726Srpaulo#ifdef DSPMBYTE
534252726Srpaulo    int mbytepos = 1;
535252726Srpaulo#endif /* DSPMBYTE */
536252726Srpaulo
537252726Srpaulo    for (bptr = buffer, wptr = word;;) {
538252726Srpaulo	if (bptr > buffer + 2 * FILSIZ - 5)
539252726Srpaulo	    break;
540252726Srpaulo
541252726Srpaulo	if (cp >= cpend)
542252726Srpaulo	    in_sync = 0;
543252726Srpaulo#ifdef DSPMBYTE
544252726Srpaulo	if (mbytepos == 1)
545252726Srpaulo#endif /* DSPMBYTE */
546252726Srpaulo	if (in_sync && !cmap(qu, _ESC) && cmap(*cp, _QF|_ESC))
547252726Srpaulo	    if (qu == 0 || qu == *cp) {
548252726Srpaulo		qu ^= *cp;
549252726Srpaulo		*bptr++ = *cp++;
550252726Srpaulo		continue;
551252726Srpaulo	    }
552252726Srpaulo	w = *wptr;
553252726Srpaulo	if (w == 0)
554252726Srpaulo	    break;
555252726Srpaulo
556252726Srpaulo	wq = w & QUOTE;
557252726Srpaulo	w &= ~QUOTE;
558252726Srpaulo
559252726Srpaulo#ifdef DSPMBYTE
560252726Srpaulo	if (mbytepos == 2)
561252726Srpaulo	  goto mbyteskip;
562252726Srpaulo#endif /* DSPMBYTE */
563252726Srpaulo	if (cmap(w, _ESC | _QF))
564252726Srpaulo	    wq = QUOTE;		/* quotes are always quoted */
565252726Srpaulo
566252726Srpaulo	if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) {
567252726Srpaulo	    /* We have to unquote the character */
568252726Srpaulo	    in_sync = 0;
569252726Srpaulo	    if (cmap(qu, _ESC))
570252726Srpaulo		bptr[-1] = w;
571252726Srpaulo	    else {
572252726Srpaulo		*bptr++ = (Char) qu;
573252726Srpaulo		*bptr++ = w;
574252726Srpaulo		if (wptr[1] == 0)
575252726Srpaulo		    qu = 0;
576252726Srpaulo		else
577252726Srpaulo		    *bptr++ = (Char) qu;
578252726Srpaulo	    }
579252726Srpaulo	} else if (qu && w == qu) {
580252726Srpaulo	    in_sync = 0;
581252726Srpaulo	    if (bptr > buffer && bptr[-1] == qu) {
582252726Srpaulo		/* User misunderstanding :) */
583252726Srpaulo		bptr[-1] = '\\';
584252726Srpaulo		*bptr++ = w;
585252726Srpaulo		qu = 0;
586252726Srpaulo	    } else {
587252726Srpaulo		*bptr++ = (Char) qu;
588252726Srpaulo		*bptr++ = '\\';
589252726Srpaulo		*bptr++ = w;
590252726Srpaulo		*bptr++ = (Char) qu;
591252726Srpaulo	    }
592252726Srpaulo	}
593252726Srpaulo	else if (wq && qu == '\"' && tricky_dq(w)) {
594252726Srpaulo	    in_sync = 0;
595252726Srpaulo	    *bptr++ = (Char) qu;
596252726Srpaulo	    *bptr++ = '\\';
597252726Srpaulo	    *bptr++ = w;
598252726Srpaulo	    *bptr++ = (Char) qu;
599252726Srpaulo	} else if (wq && ((!qu && (tricky(w) || (w == HISTSUB && bptr == buffer))) || (!cmap(qu, _ESC) && w == HIST))) {
600252726Srpaulo	    in_sync = 0;
601252726Srpaulo	    *bptr++ = '\\';
602252726Srpaulo	    *bptr++ = w;
603252726Srpaulo	} else {
604252726Srpaulo#ifdef DSPMBYTE
605252726Srpaulo	  mbyteskip:
606252726Srpaulo#endif /* DSPMBYTE */
607252726Srpaulo	    if (in_sync && *cp++ != w)
608252726Srpaulo		in_sync = 0;
609252726Srpaulo	    *bptr++ = w;
610252726Srpaulo#ifdef DSPMBYTE
611252726Srpaulo	    if (mbytepos == 1 && Ismbyte1(w))
612252726Srpaulo	      mbytepos = 2;
613252726Srpaulo	    else
614252726Srpaulo	      mbytepos = 1;
615252726Srpaulo#endif /* DSPMBYTE */
616252726Srpaulo	}
617252726Srpaulo	wptr++;
618252726Srpaulo	if (cmap(qu, _ESC))
619252726Srpaulo	    qu = 0;
620252726Srpaulo    }
621252726Srpaulo    if (closequotes && qu && !cmap(qu, _ESC))
622252726Srpaulo	*bptr++ = (Char) qu;
623252726Srpaulo    *bptr = '\0';
624252726Srpaulo    if (ndel)
625252726Srpaulo	DeleteBack(ndel);
626252726Srpaulo    return InsertStr(buffer);
627252726Srpaulo} /* end insert_meta */
628252726Srpaulo
629252726Srpaulo
630252726Srpaulo
631252726Srpaulo/* is_prefix():
632252726Srpaulo *	return true if check matches initial chars in template
633252726Srpaulo *	This differs from PWB imatch in that if check is null
634252726Srpaulo *	it matches anything
635252726Srpaulo */
636252726Srpaulostatic int
637252726Srpaulois_prefix(check, template)
638252726Srpaulo    register Char *check, *template;
639252726Srpaulo{
640252726Srpaulo    for (; *check; check++, template++)
641252726Srpaulo	if ((*check & TRIM) != (*template & TRIM))
642252726Srpaulo	    return (FALSE);
643252726Srpaulo    return (TRUE);
644252726Srpaulo} /* end is_prefix */
645252726Srpaulo
646252726Srpaulo
647252726Srpaulo/* is_prefixmatch():
648252726Srpaulo *	return true if check matches initial chars in template
649252726Srpaulo *	This differs from PWB imatch in that if check is null
650252726Srpaulo *	it matches anything
651252726Srpaulo * and matches on shortening of commands
652252726Srpaulo */
653252726Srpaulostatic int
654252726Srpaulois_prefixmatch(check, template, igncase)
655252726Srpaulo    Char *check, *template;
656252726Srpaulo    int igncase;
657252726Srpaulo{
658252726Srpaulo    Char MCH1, MCH2;
659252726Srpaulo
660252726Srpaulo    for (; *check; check++, template++) {
661252726Srpaulo	if ((*check & TRIM) != (*template & TRIM)) {
662252726Srpaulo            MCH1 = (*check & TRIM);
663252726Srpaulo            MCH2 = (*template & TRIM);
664252726Srpaulo            MCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1;
665252726Srpaulo            MCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2;
666252726Srpaulo            if (MCH1 != MCH2) {
667252726Srpaulo                if (!igncase && ((*check & TRIM) == '-' ||
668252726Srpaulo				 (*check & TRIM) == '.' ||
669252726Srpaulo				 (*check & TRIM) == '_')) {
670252726Srpaulo                    MCH1 = MCH2 = (*check & TRIM);
671252726Srpaulo                    if (MCH1 == '_') {
672252726Srpaulo                        MCH2 = '-';
673252726Srpaulo                    } else if (MCH1 == '-') {
674252726Srpaulo                        MCH2 = '_';
675252726Srpaulo                    }
676252726Srpaulo                    for (;*template && (*template & TRIM) != MCH1 &&
677252726Srpaulo				       (*template & TRIM) != MCH2; template++)
678252726Srpaulo			continue;
679252726Srpaulo                    if (!*template) {
680252726Srpaulo	                return (FALSE);
681252726Srpaulo                    }
682252726Srpaulo                } else {
683252726Srpaulo	            return (FALSE);
684252726Srpaulo                }
685252726Srpaulo            }
686252726Srpaulo        }
687252726Srpaulo    }
688252726Srpaulo    return (TRUE);
689252726Srpaulo} /* end is_prefixmatch */
690252726Srpaulo
691252726Srpaulo
692214501Srpaulo/* is_suffix():
693 *	Return true if the chars in template appear at the
694 *	end of check, I.e., are it's suffix.
695 */
696static int
697is_suffix(check, template)
698    register Char *check, *template;
699{
700    register Char *t, *c;
701
702    for (t = template; *t++;)
703	continue;
704    for (c = check; *c++;)
705	continue;
706    for (;;) {
707	if (t == template)
708	    return 1;
709	if (c == check || (*--t & TRIM) != (*--c & TRIM))
710	    return 0;
711    }
712} /* end is_suffix */
713
714
715/* ignored():
716 *	Return true if this is an ignored item
717 */
718static int
719ignored(item)
720    register Char *item;
721{
722    struct varent *vp;
723    register Char **cp;
724
725    if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
726	return (FALSE);
727    for (; *cp != NULL; cp++)
728	if (is_suffix(item, *cp))
729	    return (TRUE);
730    return (FALSE);
731} /* end ignored */
732
733
734
735/* starting_a_command():
736 *	return true if the command starting at wordstart is a command
737 */
738int
739starting_a_command(wordstart, inputline)
740    register Char *wordstart, *inputline;
741{
742    register Char *ptr, *ncmdstart;
743    int     count;
744    static  Char
745            cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
746            cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
747
748    /*
749     * Find if the number of backquotes is odd or even.
750     */
751    for (ptr = wordstart, count = 0;
752	 ptr >= inputline;
753	 count += (*ptr-- == '`'))
754	continue;
755    /*
756     * if the number of backquotes is even don't include the backquote char in
757     * the list of command starting delimiters [if it is zero, then it does not
758     * matter]
759     */
760    ncmdstart = cmdstart + EVEN(count);
761
762    /*
763     * look for the characters previous to this word if we find a command
764     * starting delimiter we break. if we find whitespace and another previous
765     * word then we are not a command
766     *
767     * count is our state machine: 0 looking for anything 1 found white-space
768     * looking for non-ws
769     */
770    for (count = 0; wordstart >= inputline; wordstart--) {
771	if (*wordstart == '\0')
772	    continue;
773	if (Strchr(ncmdstart, *wordstart))
774	    break;
775	/*
776	 * found white space
777	 */
778	if ((ptr = Strchr(cmdalive, *wordstart)) != NULL)
779	    count = 1;
780	if (count == 1 && !ptr)
781	    return (FALSE);
782    }
783
784    if (wordstart > inputline)
785	switch (*wordstart) {
786	case '&':		/* Look for >& */
787	    while (wordstart > inputline &&
788		   (*--wordstart == ' ' || *wordstart == '\t'))
789		continue;
790	    if (*wordstart == '>')
791		return (FALSE);
792	    break;
793	case '(':		/* check for foreach, if etc. */
794	    while (wordstart > inputline &&
795		   (*--wordstart == ' ' || *wordstart == '\t'))
796		continue;
797	    if (!iscmdmeta(*wordstart) &&
798		(*wordstart != ' ' && *wordstart != '\t'))
799		return (FALSE);
800	    break;
801	default:
802	    break;
803	}
804    return (TRUE);
805} /* end starting_a_command */
806
807
808/* recognize():
809 *	Object: extend what user typed up to an ambiguity.
810 *	Algorithm:
811 *	On first match, copy full item (assume it'll be the only match)
812 *	On subsequent matches, shorten exp_name to the first
813 *	character mismatch between exp_name and item.
814 *	If we shorten it back to the prefix length, stop searching.
815 */
816static int
817recognize(exp_name, item, name_length, numitems, enhanced, igncase)
818    Char   *exp_name, *item;
819    int     name_length, numitems, enhanced, igncase;
820{
821    Char MCH1, MCH2;
822    register Char *x, *ent;
823    register int len = 0;
824
825    if (numitems == 1) {	/* 1st match */
826	copyn(exp_name, item, MAXNAMLEN);
827	return (0);
828    }
829    if (!enhanced && !igncase) {
830	for (x = exp_name, ent = item; *x && (*x & TRIM) == (*ent & TRIM); x++, ent++)
831	    len++;
832    } else {
833	for (x = exp_name, ent = item; *x; x++, ent++) {
834	    MCH1 = *x & TRIM;
835	    MCH2 = *ent & TRIM;
836            MCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1;
837            MCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2;
838	    if (MCH1 != MCH2)
839		break;
840	    len++;
841	}
842	if (*x || !*ent)	/* Shorter or exact match */
843	    copyn(exp_name, item, MAXNAMLEN);
844    }
845    *x = '\0';		/* Shorten at 1st char diff */
846    if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) && len == name_length)	/* Ambiguous to prefix? */
847	return (-1);	/* So stop now and save time */
848    return (0);
849} /* end recognize */
850
851
852/* tw_collect_items():
853 *	Collect items that match target.
854 *	SPELL command:
855 *		Returns the spelling distance of the closest match.
856 *	else
857 *		Returns the number of items found.
858 *		If none found, but some ignored items were found,
859 *		It returns the -number of ignored items.
860 */
861static int
862tw_collect_items(command, looking, exp_dir, exp_name, target, pat, flags)
863    COMMAND command;
864    int looking;
865    Char *exp_dir, *exp_name, *target, *pat;
866    int flags;
867
868{
869    int done = FALSE;			 /* Search is done */
870    int showdots;			 /* Style to show dot files */
871    int nignored = 0;			 /* Number of fignored items */
872    int numitems = 0;			 /* Number of matched items */
873    int name_length = (int) Strlen(target); /* Length of prefix (file name) */
874    int exec_check = flags & TW_EXEC_CHK;/* need to check executability	*/
875    int dir_check  = flags & TW_DIR_CHK; /* Need to check for directories */
876    int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */
877    int dir_ok     = flags & TW_DIR_OK;  /* Ignore directories? */
878    int gpat       = flags & TW_PAT_OK;	 /* Match against a pattern */
879    int ignoring   = flags & TW_IGN_OK;	 /* Use fignore? */
880    int d = 4, nd;			 /* Spelling distance */
881    Char *item, *ptr;
882    Char buf[MAXPATHLEN+1];
883    struct varent *vp;
884    int len, enhanced = 0;
885    int cnt = 0;
886    int igncase = 0;
887
888
889    flags = 0;
890
891    showdots = DOT_NONE;
892    if ((ptr = varval(STRlistflags)) != STRNULL)
893	while (*ptr)
894	    switch (*ptr++) {
895	    case 'a':
896		showdots = DOT_ALL;
897		break;
898	    case 'A':
899		showdots = DOT_NOT;
900		break;
901	    default:
902		break;
903	    }
904
905    while (!done && (item = (*tw_next_entry[looking])(exp_dir, &flags))) {
906#ifdef TDEBUG
907	xprintf("item = %S\n", item);
908#endif
909	switch (looking) {
910	case TW_FILE:
911	case TW_DIRECTORY:
912	case TW_TEXT:
913	    /*
914	     * Don't match . files on null prefix match
915	     */
916	    if (showdots == DOT_NOT && (ISDOT(item) || ISDOTDOT(item)))
917		done = TRUE;
918	    if (name_length == 0 && item[0] == '.' && showdots == DOT_NONE)
919		done = TRUE;
920	    break;
921
922	case TW_COMMAND:
923#if defined(_UWIN) || defined(__CYGWIN__)
924	    /* Turn foo.{exe,com,bat} into foo since UWIN's readdir returns
925	     * the file with the .exe, .com, .bat extension
926	     */
927	    {
928		size_t ext = strlen((char *)item) - 4;
929		if ((ext > 0) && (strcasecmp((char *)&item[ext], ".exe") == 0 ||
930				  strcasecmp((char *)&item[ext], ".bat") == 0 ||
931				  strcasecmp((char *)&item[ext], ".com") == 0))
932		    {
933			item[ext] = '\0';
934#if defined(__CYGWIN__)
935			strlwr((char *)item);
936#endif /* __CYGWIN__ */
937		    }
938	    }
939#endif /* _UWIN || __CYGWIN__ */
940	    exec_check = flags & TW_EXEC_CHK;
941	    dir_ok = flags & TW_DIR_OK;
942	    break;
943
944	default:
945	    break;
946	}
947
948	if (done) {
949	    done = FALSE;
950	    continue;
951	}
952
953	switch (command) {
954
955	case SPELL:		/* correct the spelling of the last bit */
956	    if (name_length == 0) {/* zero-length word can't be misspelled */
957		exp_name[0] = '\0';/* (not trying is important for ~) */
958		d = 0;
959		done = TRUE;
960		break;
961	    }
962	    if (gpat && !Gmatch(item, pat))
963		break;
964	    /*
965	     * Swapped the order of the spdist() arguments as suggested
966	     * by eeide@asylum.cs.utah.edu (Eric Eide)
967	     */
968	    nd = spdist(target, item);	/* test the item against original */
969	    if (nd <= d && nd != 4) {
970		if (!(exec_check && !executable(exp_dir, item, dir_ok))) {
971		    (void) Strcpy(exp_name, item);
972		    d = nd;
973		    if (d == 0)	/* if found it exactly */
974			done = TRUE;
975		}
976	    }
977	    else if (nd == 4) {
978		if (spdir(exp_name, exp_dir, item, target)) {
979		    if (exec_check && !executable(exp_dir, exp_name, dir_ok))
980			break;
981#ifdef notdef
982		    /*
983		     * We don't want to stop immediately, because
984		     * we might find an exact/better match later.
985		     */
986		    d = 0;
987		    done = TRUE;
988#endif
989		    d = 3;
990		}
991	    }
992	    break;
993
994	case LIST:
995	case RECOGNIZE:
996	case RECOGNIZE_ALL:
997	case RECOGNIZE_SCROLL:
998
999	    if ((vp = adrof(STRcomplete)) != NULL && vp->vec != NULL) {
1000		Char **cp;
1001		for (cp = vp->vec; *cp; cp++) {
1002		    if (Strcmp(*cp, STRigncase) == 0)
1003			igncase = 1;
1004		    if (Strcmp(*cp, STRenhance) == 0)
1005			enhanced = 1;
1006		}
1007	    }
1008
1009	    if (enhanced || igncase) {
1010	        if (!is_prefixmatch(target, item, igncase))
1011		    break;
1012     	    } else {
1013	        if (!is_prefix(target, item))
1014		    break;
1015	    }
1016
1017	    if (exec_check && !executable(exp_dir, item, dir_ok))
1018		break;
1019
1020	    if (dir_check && !isadirectory(exp_dir, item))
1021		break;
1022
1023	    if (text_check && isadirectory(exp_dir, item))
1024		break;
1025
1026	    /*
1027	     * Only pattern match directories if we're checking
1028	     * for directories.
1029	     */
1030	    if (gpat && !Gmatch(item, pat) &&
1031		(dir_check || !isadirectory(exp_dir, item)))
1032		    break;
1033
1034	    /*
1035	     * Remove duplicates in command listing and completion
1036             * AFEB added code for TW_LOGNAME and TW_USER cases
1037	     */
1038	    if (looking == TW_COMMAND || looking == TW_LOGNAME
1039		|| looking == TW_USER || command == LIST) {
1040		copyn(buf, item, MAXPATHLEN);
1041		len = (int) Strlen(buf);
1042		switch (looking) {
1043		case TW_COMMAND:
1044		    if (!(dir_ok && exec_check))
1045			break;
1046		    if (filetype(exp_dir, item) == '/') {
1047			buf[len++] = '/';
1048			buf[len] = '\0';
1049		    }
1050		    break;
1051
1052		case TW_FILE:
1053		case TW_DIRECTORY:
1054		    buf[len++] = filetype(exp_dir, item);
1055		    buf[len] = '\0';
1056		    break;
1057
1058		default:
1059		    break;
1060		}
1061		if ((looking == TW_COMMAND || looking == TW_USER
1062                     || looking == TW_LOGNAME) && tw_item_find(buf))
1063		    break;
1064		else {
1065		    /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
1066		    ptr = tw_item_add(len + 3);
1067		    copyn(ptr, buf, MAXPATHLEN);
1068		    if (command == LIST)
1069			numitems++;
1070		}
1071	    }
1072
1073	    if (command == RECOGNIZE || command == RECOGNIZE_ALL ||
1074		command == RECOGNIZE_SCROLL) {
1075		if (ignoring && ignored(item)) {
1076		    nignored++;
1077		    break;
1078		}
1079		else if (command == RECOGNIZE_SCROLL) {
1080		    add_scroll_tab(item);
1081		    cnt++;
1082		}
1083
1084		if (match_unique_match || is_set(STRrecexact)) {
1085		    if (StrQcmp(target, item) == 0) {	/* EXACT match */
1086			copyn(exp_name, item, MAXNAMLEN);
1087			numitems = 1;	/* fake into expanding */
1088			non_unique_match = TRUE;
1089			done = TRUE;
1090			break;
1091		    }
1092		}
1093		if (recognize(exp_name, item, name_length, ++numitems,
1094		    enhanced, igncase))
1095		    if (command != RECOGNIZE_SCROLL)
1096			done = TRUE;
1097		if (enhanced && (int)Strlen(exp_name) < name_length)
1098		    copyn(exp_name, target, MAXNAMLEN);
1099	    }
1100	    break;
1101
1102	default:
1103	    break;
1104	}
1105#ifdef TDEBUG
1106	xprintf("done item = %S\n", item);
1107#endif
1108    }
1109
1110
1111    if (command == RECOGNIZE_SCROLL) {
1112	if ((cnt <= curchoice) || (curchoice == -1)) {
1113	    curchoice = -1;
1114	    nignored = 0;
1115	    numitems = 0;
1116	} else if (numitems > 1) {
1117	    if (curchoice < -1)
1118		curchoice = cnt - 1;
1119	    choose_scroll_tab(&exp_name, cnt);
1120	    numitems = 1;
1121	}
1122    }
1123    free_scroll_tab();
1124
1125    if (command == SPELL)
1126	return d;
1127    else {
1128	if (ignoring && numitems == 0 && nignored > 0)
1129	    return -nignored;
1130	else
1131	    return numitems;
1132    }
1133}
1134
1135
1136/* tw_suffix():
1137 *	Find and return the appropriate suffix character
1138 */
1139/*ARGSUSED*/
1140static Char
1141tw_suffix(looking, exp_dir, exp_name, target, name)
1142    int looking;
1143    Char *exp_dir, *exp_name, *target, *name;
1144{
1145    Char *ptr;
1146    struct varent *vp;
1147
1148    USE(name);
1149    (void) strip(exp_name);
1150
1151    switch (looking) {
1152
1153    case TW_LOGNAME:
1154	return '/';
1155
1156    case TW_VARIABLE:
1157	/*
1158	 * Don't consider array variables or empty variables
1159	 */
1160	if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) {
1161	    if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' ||
1162		vp->vec[1] != NULL)
1163		return ' ';
1164	}
1165	else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0')
1166	    return ' ';
1167
1168	*--target = '\0';
1169
1170	return isadirectory(exp_dir, ptr) ? '/' : ' ';
1171
1172
1173    case TW_DIRECTORY:
1174	return '/';
1175
1176    case TW_COMMAND:
1177    case TW_FILE:
1178	return isadirectory(exp_dir, exp_name) ? '/' : ' ';
1179
1180    case TW_ALIAS:
1181    case TW_VARLIST:
1182    case TW_WORDLIST:
1183    case TW_SHELLVAR:
1184    case TW_ENVVAR:
1185    case TW_USER:
1186    case TW_BINDING:
1187    case TW_LIMIT:
1188    case TW_SIGNAL:
1189    case TW_JOB:
1190    case TW_COMPLETION:
1191    case TW_TEXT:
1192    case TW_GRPNAME:
1193	return ' ';
1194
1195    default:
1196	return '\0';
1197    }
1198} /* end tw_suffix */
1199
1200
1201/* tw_fixword():
1202 *	Repair a word after a spalling or a recognizwe
1203 */
1204static void
1205tw_fixword(looking, word, dir, exp_name, max_word_length)
1206    int looking;
1207    Char *word, *dir, *exp_name;
1208    int max_word_length;
1209{
1210    Char *ptr;
1211
1212    switch (looking) {
1213    case TW_LOGNAME:
1214	copyn(word, STRtilde, 1);
1215	break;
1216
1217    case TW_VARIABLE:
1218	if ((ptr = Strrchr(word, '$')) != NULL)
1219	    *++ptr = '\0';	/* Delete after the dollar */
1220	else
1221	    word[0] = '\0';
1222	break;
1223
1224    case TW_DIRECTORY:
1225    case TW_FILE:
1226    case TW_TEXT:
1227	copyn(word, dir, max_word_length);	/* put back dir part */
1228	break;
1229
1230    default:
1231	word[0] = '\0';
1232	break;
1233    }
1234
1235    (void) quote(exp_name);
1236    catn(word, exp_name, max_word_length);	/* add extended name */
1237} /* end tw_fixword */
1238
1239
1240/* tw_collect():
1241 *	Collect items. Return -1 in case we were interrupted or
1242 *	the return value of tw_collect
1243 *	This is really a wrapper for tw_collect_items, serving two
1244 *	purposes:
1245 *		1. Handles interrupt cleanups.
1246 *		2. Retries if we had no matches, but there were ignored matches
1247 */
1248static int
1249tw_collect(command, looking, exp_dir, exp_name, target, pat, flags, dir_fd)
1250    COMMAND command;
1251    int looking;
1252    Char *exp_dir, *exp_name, **target, *pat;
1253    int flags;
1254    DIR *dir_fd;
1255{
1256    static int ni;	/* static so we don't get clobbered */
1257    jmp_buf_t osetexit;
1258
1259#ifdef TDEBUG
1260    xprintf("target = %S\n", *target);
1261#endif
1262    ni = 0;
1263    getexit(osetexit);
1264    for (;;) {
1265	(*tw_start_entry[looking])(dir_fd, pat);
1266	InsideCompletion = 1;
1267	if (setexit()) {
1268	    /* interrupted, clean up */
1269	    resexit(osetexit);
1270	    InsideCompletion = 0;
1271	    haderr = 0;
1272
1273#if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
1274	    /* Compiler bug? (from PWP) */
1275	    if ((looking == TW_LOGNAME) || (looking == TW_USER))
1276		tw_logname_end();
1277	    else
1278		if (looking == TW_GRPNAME)
1279		   tw_grpname_end();
1280		else
1281		    tw_dir_end();
1282#else /* !(SOLARIS2 && i386 && !__GNUC__) */
1283	    (*tw_end_entry[looking])();
1284#endif /* !(SOLARIS2 && i386 && !__GNUC__) */
1285
1286	    /* flag error */
1287	    return(-1);
1288	}
1289        if ((ni = tw_collect_items(command, looking, exp_dir, exp_name,
1290			           *target, pat,
1291				   ni >= 0 ? flags :
1292					flags & ~TW_IGN_OK)) >= 0) {
1293	    resexit(osetexit);
1294	    InsideCompletion = 0;
1295
1296#if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
1297	    /* Compiler bug? (from PWP) */
1298	    if ((looking == TW_LOGNAME) || (looking == TW_USER))
1299		tw_logname_end();
1300	    else
1301		if (looking == TW_GRPNAME)
1302		   tw_grpname_end();
1303		else
1304		    tw_dir_end();
1305#else /* !(SOLARIS2 && i386 && !__GNUC__) */
1306	    (*tw_end_entry[looking])();
1307#endif /* !(SOLARIS2 && i386 && !__GNUC__) */
1308
1309	    return(ni);
1310	}
1311    }
1312} /* end tw_collect */
1313
1314
1315/* tw_list_items():
1316 *	List the items that were found
1317 *
1318 *	NOTE instead of looking at numerical vars listmax and listmaxrows
1319 *	we can look at numerical var listmax, and have a string value
1320 *	listmaxtype (or similar) than can have values 'items' and 'rows'
1321 *	(by default interpreted as 'items', for backwards compatibility)
1322 */
1323static void
1324tw_list_items(looking, numitems, list_max)
1325    int looking, numitems, list_max;
1326{
1327    Char *ptr;
1328    int max_items = 0;
1329    int max_rows = 0;
1330
1331    if (numitems == 0)
1332	return;
1333
1334    if ((ptr = varval(STRlistmax)) != STRNULL) {
1335	while (*ptr) {
1336	    if (!Isdigit(*ptr)) {
1337		max_items = 0;
1338		break;
1339	    }
1340	    max_items = max_items * 10 + *ptr++ - '0';
1341	}
1342	if ((max_items > 0) && (numitems > max_items) && list_max)
1343	    max_items = numitems;
1344	else
1345	    max_items = 0;
1346    }
1347
1348    if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) {
1349	int rows;
1350
1351	while (*ptr) {
1352	    if (!Isdigit(*ptr)) {
1353		max_rows = 0;
1354		break;
1355	    }
1356	    max_rows = max_rows * 10 + *ptr++ - '0';
1357	}
1358	if (max_rows != 0 && looking != TW_JOB)
1359	    rows = find_rows(tw_item_get(), numitems, TRUE);
1360	else
1361	    rows = numitems; /* underestimate for lines wider than the termH */
1362	if ((max_rows > 0) && (rows > max_rows) && list_max)
1363	    max_rows = rows;
1364	else
1365	    max_rows = 0;
1366    }
1367
1368
1369    if (max_items || max_rows) {
1370	char    	 tc;
1371	const char	*name;
1372	int maxs;
1373
1374	if (max_items) {
1375	    name = CGETS(30, 5, "items");
1376	    maxs = max_items;
1377	}
1378	else {
1379	    name = CGETS(30, 6, "rows");
1380	    maxs = max_rows;
1381	}
1382
1383	xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "),
1384		maxs, name);
1385	flush();
1386	/* We should be in Rawmode here, so no \n to catch */
1387	(void) read(SHIN, &tc, 1);
1388	xprintf("%c\r\n", tc);	/* echo the char, do a newline */
1389	/*
1390	 * Perhaps we should use the yesexpr from the
1391	 * actual locale
1392	 */
1393	if (strchr(CGETS(30, 13, "Yy"), tc) == NULL)
1394	    return;
1395    }
1396
1397    if (looking != TW_SIGNAL)
1398	qsort((ptr_t) tw_item_get(), (size_t) numitems, sizeof(Char *),
1399	      (int (*) __P((const void *, const void *))) fcompare);
1400    if (looking != TW_JOB)
1401	print_by_column(STRNULL, tw_item_get(), numitems, TRUE);
1402    else {
1403	/*
1404	 * print one item on every line because jobs can have spaces
1405	 * and it is confusing.
1406	 */
1407	int i;
1408	Char **w = tw_item_get();
1409
1410	for (i = 0; i < numitems; i++) {
1411	    xprintf("%S", w[i]);
1412	    if (Tty_raw_mode)
1413		xputchar('\r');
1414	    xputchar('\n');
1415	}
1416    }
1417} /* end tw_list_items */
1418
1419
1420/* t_search():
1421 *	Perform a RECOGNIZE, LIST or SPELL command on string "word".
1422 *
1423 *	Return value:
1424 *		>= 0:   SPELL command: "distance" (see spdist())
1425 *		                other: No. of items found
1426 *  		 < 0:   Error (message or beep is output)
1427 */
1428/*ARGSUSED*/
1429int
1430t_search(word, wp, command, max_word_length, looking, list_max, pat, suf)
1431    Char   *word, *wp;		/* original end-of-word */
1432    COMMAND command;
1433    int     max_word_length, looking, list_max;
1434    Char   *pat;
1435    int     suf;
1436{
1437    int     numitems,			/* Number of items matched */
1438	    flags = 0,			/* search flags */
1439	    gpat = pat[0] != '\0',	/* Glob pattern search */
1440	    nd;				/* Normalized directory return */
1441    Char    exp_dir[FILSIZ + 1],	/* dir after ~ expansion */
1442            dir[FILSIZ + 1],		/* /x/y/z/ part in /x/y/z/f */
1443            exp_name[MAXNAMLEN + 1],	/* the recognized (extended) */
1444            name[MAXNAMLEN + 1],	/* f part in /d/d/d/f name */
1445           *target;			/* Target to expand/correct/list */
1446    DIR    *dir_fd = NULL;
1447
1448    USE(wp);
1449
1450    /*
1451     * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
1452     * dump core when interrupted
1453     */
1454    tw_item_free();
1455
1456    non_unique_match = FALSE;	/* See the recexact code below */
1457
1458    extract_dir_and_name(word, dir, name);
1459
1460    /*
1461     *  SPECIAL HARDCODED COMPLETIONS:
1462     *    foo$variable                -> TW_VARIABLE
1463     *    ~user                       -> TW_LOGNAME
1464     *
1465     */
1466    if ((*word == '~') && (Strchr(word, '/') == NULL)) {
1467	looking = TW_LOGNAME;
1468	target = name;
1469	gpat = 0;	/* Override pattern mechanism */
1470    }
1471    else if ((target = Strrchr(name, '$')) != 0 &&
1472	     (Strchr(name, '/') == NULL)) {
1473	target++;
1474	looking = TW_VARIABLE;
1475	gpat = 0;	/* Override pattern mechanism */
1476    }
1477    else
1478	target = name;
1479
1480    /*
1481     * Try to figure out what we should be looking for
1482     */
1483    if (looking & TW_PATH) {
1484	gpat = 0;	/* pattern holds the pathname to be used */
1485	copyn(exp_dir, pat, MAXNAMLEN);
1486	if (exp_dir[Strlen(exp_dir) - 1] != '/')
1487	    catn(exp_dir, STRslash, MAXNAMLEN);
1488	catn(exp_dir, dir, MAXNAMLEN);
1489    }
1490    else
1491	exp_dir[0] = '\0';
1492
1493    switch (looking & ~TW_PATH) {
1494    case TW_NONE:
1495	return -1;
1496
1497    case TW_ZERO:
1498	looking = TW_FILE;
1499	break;
1500
1501    case TW_COMMAND:
1502	if (Strchr(word, '/') || (looking & TW_PATH)) {
1503	    looking = TW_FILE;
1504	    flags |= TW_EXEC_CHK;
1505	    flags |= TW_DIR_OK;
1506	}
1507#ifdef notdef
1508	/* PWP: don't even bother when doing ALL of the commands */
1509	if (looking == TW_COMMAND && (*word == '\0'))
1510	    return (-1);
1511#endif
1512	break;
1513
1514
1515    case TW_VARLIST:
1516    case TW_WORDLIST:
1517	gpat = 0;	/* pattern holds the name of the variable */
1518	break;
1519
1520    case TW_EXPLAIN:
1521	if (command == LIST && pat != NULL) {
1522	    xprintf("%S", pat);
1523	    if (Tty_raw_mode)
1524		xputchar('\r');
1525	    xputchar('\n');
1526	}
1527	return 2;
1528
1529    default:
1530	break;
1531    }
1532
1533    /*
1534     * let fignore work only when we are not using a pattern
1535     */
1536    flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK;
1537
1538#ifdef TDEBUG
1539    xprintf(CGETS(30, 8, "looking = %d\n"), looking);
1540#endif
1541
1542    switch (looking) {
1543    case TW_ALIAS:
1544    case TW_SHELLVAR:
1545    case TW_ENVVAR:
1546    case TW_BINDING:
1547    case TW_LIMIT:
1548    case TW_SIGNAL:
1549    case TW_JOB:
1550    case TW_COMPLETION:
1551    case TW_GRPNAME:
1552	break;
1553
1554
1555    case TW_VARIABLE:
1556	if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1557	    return nd;
1558	break;
1559
1560    case TW_DIRECTORY:
1561	flags |= TW_DIR_CHK;
1562
1563#ifdef notyet
1564	/*
1565	 * This is supposed to expand the directory stack.
1566	 * Problems:
1567	 * 1. Slow
1568	 * 2. directories with the same name
1569	 */
1570	flags |= TW_DIR_OK;
1571#endif
1572#ifdef notyet
1573	/*
1574	 * Supposed to do delayed expansion, but it is inconsistent
1575	 * from a user-interface point of view, since it does not
1576	 * immediately obey addsuffix
1577	 */
1578	if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1579	    return nd;
1580	if (isadirectory(exp_dir, name)) {
1581	    if (exp_dir[0] != '\0' || name[0] != '\0') {
1582		catn(dir, name, MAXNAMLEN);
1583		if (dir[Strlen(dir) - 1] != '/')
1584		    catn(dir, STRslash, MAXNAMLEN);
1585		if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1586		    return nd;
1587		if (word[Strlen(word) - 1] != '/')
1588		    catn(word, STRslash, MAXNAMLEN);
1589		name[0] = '\0';
1590	    }
1591	}
1592#endif
1593	if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1594	    return nd;
1595	break;
1596
1597    case TW_TEXT:
1598	flags |= TW_TEXT_CHK;
1599	/*FALLTHROUGH*/
1600    case TW_FILE:
1601	if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
1602	    return nd;
1603	break;
1604
1605    case TW_PATH | TW_TEXT:
1606    case TW_PATH | TW_FILE:
1607    case TW_PATH | TW_DIRECTORY:
1608    case TW_PATH | TW_COMMAND:
1609	if ((dir_fd = opendir(short2str(exp_dir))) == NULL) {
1610 	    if (command == RECOGNIZE)
1611 		xprintf("\n");
1612 	    xprintf("%S: %s", exp_dir, strerror(errno));
1613 	    if (command != RECOGNIZE)
1614 		xprintf("\n");
1615 	    NeedsRedraw = 1;
1616	    return -1;
1617	}
1618	if (exp_dir[Strlen(exp_dir) - 1] != '/')
1619	    catn(exp_dir, STRslash, MAXNAMLEN);
1620
1621	looking &= ~TW_PATH;
1622
1623	switch (looking) {
1624	case TW_TEXT:
1625	    flags |= TW_TEXT_CHK;
1626	    break;
1627
1628	case TW_FILE:
1629	    break;
1630
1631	case TW_DIRECTORY:
1632	    flags |= TW_DIR_CHK;
1633	    break;
1634
1635	case TW_COMMAND:
1636	    copyn(target, word, MAXNAMLEN);	/* so it can match things */
1637	    break;
1638
1639	default:
1640	    abort();	/* Cannot happen */
1641	    break;
1642	}
1643	break;
1644
1645    case TW_LOGNAME:
1646	word++;
1647	/*FALLTHROUGH*/
1648    case TW_USER:
1649	/*
1650	 * Check if the spelling was already correct
1651	 * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
1652	 */
1653	if (command == SPELL && getpwnam(short2str(word)) != NULL) {
1654#ifdef YPBUGS
1655	    fix_yp_bugs();
1656#endif /* YPBUGS */
1657	    return (0);
1658	}
1659	copyn(name, word, MAXNAMLEN);	/* name sans ~ */
1660	if (looking == TW_LOGNAME)
1661	    word--;
1662	break;
1663
1664    case TW_COMMAND:
1665    case TW_VARLIST:
1666    case TW_WORDLIST:
1667	copyn(target, word, MAXNAMLEN);	/* so it can match things */
1668	break;
1669
1670    default:
1671	xprintf(CGETS(30, 9,
1672		"\n%s internal error: I don't know what I'm looking for!\n"),
1673		progname);
1674	NeedsRedraw = 1;
1675	return (-1);
1676    }
1677
1678    numitems = tw_collect(command, looking, exp_dir, exp_name,
1679			  &target, pat, flags, dir_fd);
1680    if (numitems == -1)
1681	return -1;
1682
1683    switch (command) {
1684    case RECOGNIZE:
1685    case RECOGNIZE_ALL:
1686    case RECOGNIZE_SCROLL:
1687	if (numitems <= 0)
1688	    return (numitems);
1689
1690	tw_fixword(looking, word, dir, exp_name, max_word_length);
1691
1692	if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) {
1693	    Char suffix[2];
1694
1695	    suffix[1] = '\0';
1696	    switch (suf) {
1697	    case 0: 	/* Automatic suffix */
1698		suffix[0] = tw_suffix(looking, exp_dir, exp_name, target, name);
1699		break;
1700
1701	    case -1:	/* No suffix */
1702		return numitems;
1703
1704	    default:	/* completion specified suffix */
1705		suffix[0] = (Char) suf;
1706		break;
1707	    }
1708	    catn(word, suffix, max_word_length);
1709	}
1710	return numitems;
1711
1712    case LIST:
1713	tw_list_items(looking, numitems, list_max);
1714	tw_item_free();
1715	return (numitems);
1716
1717    case SPELL:
1718	tw_fixword(looking, word, dir, exp_name, max_word_length);
1719	return (numitems);
1720
1721    default:
1722	xprintf("Bad tw_command\n");
1723	return (0);
1724    }
1725} /* end t_search */
1726
1727
1728/* extract_dir_and_name():
1729 * 	parse full path in file into 2 parts: directory and file names
1730 * 	Should leave final slash (/) at end of dir.
1731 */
1732static void
1733extract_dir_and_name(path, dir, name)
1734    Char   *path, *dir, *name;
1735{
1736    register Char *p;
1737
1738    p = Strrchr(path, '/');
1739#ifdef WINNT_NATIVE
1740    if (p == NULL)
1741	p = Strrchr(path, ':');
1742#endif /* WINNT_NATIVE */
1743    if (p == NULL) {
1744	copyn(name, path, MAXNAMLEN);
1745	dir[0] = '\0';
1746    }
1747    else {
1748	p++;
1749	copyn(name, p, MAXNAMLEN);
1750	copyn(dir, path, p - path);
1751    }
1752} /* end extract_dir_and_name */
1753
1754
1755/* dollar():
1756 * 	expand "/$old1/$old2/old3/"
1757 * 	to "/value_of_old1/value_of_old2/old3/"
1758 */
1759Char *
1760dollar(new, old)
1761    Char   *new;
1762    const Char *old;
1763{
1764    Char    *p;
1765    size_t   space;
1766
1767    for (space = FILSIZ, p = new; *old && space > 0;)
1768	if (*old != '$') {
1769	    *p++ = *old++;
1770	    space--;
1771	}
1772	else {
1773	    if (expdollar(&p, &old, &space, QUOTE) == NULL)
1774		return NULL;
1775	}
1776    *p = '\0';
1777    return (new);
1778} /* end dollar */
1779
1780
1781/* tilde():
1782 * 	expand ~person/foo to home_directory_of_person/foo
1783 *	or =<stack-entry> to <dir in stack entry>
1784 */
1785static Char *
1786tilde(new, old)
1787    Char   *new, *old;
1788{
1789    register Char *o, *p;
1790
1791    switch (old[0]) {
1792    case '~':
1793	for (p = new, o = &old[1]; *o && *o != '/'; *p++ = *o++)
1794	    continue;
1795	*p = '\0';
1796	if (gethdir(new)) {
1797	    new[0] = '\0';
1798	    return NULL;
1799	}
1800#ifdef apollo
1801	/* Special case: if the home directory expands to "/", we do
1802	 * not want to create "//" by appending a slash from o.
1803	 */
1804	if (new[0] == '/' && new[1] == '\0' && *o == '/')
1805	    ++o;
1806#endif /* apollo */
1807	(void) Strcat(new, o);
1808	return new;
1809
1810    case '=':
1811	if ((p = globequal(new, old)) == NULL) {
1812	    *new = '\0';
1813	    return NULL;
1814	}
1815	if (p == new)
1816	    return new;
1817	/*FALLTHROUGH*/
1818
1819    default:
1820	(void) Strcpy(new, old);
1821	return new;
1822    }
1823} /* end tilde */
1824
1825
1826/* expand_dir():
1827 *	Open the directory given, expanding ~user and $var
1828 *	Optionally normalize the path given
1829 */
1830static int
1831expand_dir(dir, edir, dfd, cmd)
1832    Char   *dir, *edir;
1833    DIR   **dfd;
1834    COMMAND cmd;
1835{
1836    Char   *nd = NULL;
1837    Char    tdir[MAXPATHLEN + 1];
1838
1839    if ((dollar(tdir, dir) == 0) ||
1840	(tilde(edir, tdir) == 0) ||
1841	!(nd = dnormalize(*edir ? edir : STRdot, symlinks == SYM_IGNORE ||
1842						 symlinks == SYM_EXPAND)) ||
1843	((*dfd = opendir(short2str(nd))) == NULL)) {
1844	xfree((ptr_t) nd);
1845	if (cmd == SPELL || SearchNoDirErr)
1846	    return (-2);
1847	/*
1848	 * From: Amos Shapira <amoss@cs.huji.ac.il>
1849	 * Print a better message when completion fails
1850	 */
1851	xprintf("\n%S %s\n",
1852		*edir ? edir :
1853		(*tdir ? tdir : dir),
1854		(errno == ENOTDIR ? CGETS(30, 10, "not a directory") :
1855		(errno == ENOENT ? CGETS(30, 11, "not found") :
1856		 CGETS(30, 12, "unreadable"))));
1857	NeedsRedraw = 1;
1858	return (-1);
1859    }
1860    if (nd) {
1861	if (*dir != '\0') {
1862	    Char   *s, *d, *p;
1863
1864	    /*
1865	     * Copy and append a / if there was one
1866	     */
1867	    for (p = edir; *p; p++)
1868		continue;
1869	    if (*--p == '/') {
1870		for (p = nd; *p; p++)
1871		    continue;
1872		if (*--p != '/')
1873		    p = NULL;
1874	    }
1875	    for (d = edir, s = nd; (*d++ = *s++) != '\0';)
1876		continue;
1877	    if (!p) {
1878		*d-- = '\0';
1879		*d = '/';
1880	    }
1881	}
1882	xfree((ptr_t) nd);
1883    }
1884    return 0;
1885} /* end expand_dir */
1886
1887
1888/* nostat():
1889 *	Returns true if the directory should not be stat'd,
1890 *	false otherwise.
1891 *	This way, things won't grind to a halt when you complete in /afs
1892 *	or very large directories.
1893 */
1894static bool
1895nostat(dir)
1896     Char *dir;
1897{
1898    struct varent *vp;
1899    register Char **cp;
1900
1901    if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL)
1902	return FALSE;
1903    for (; *cp != NULL; cp++) {
1904	if (Strcmp(*cp, STRstar) == 0)
1905	    return TRUE;
1906	if (Gmatch(dir, *cp))
1907	    return TRUE;
1908    }
1909    return FALSE;
1910} /* end nostat */
1911
1912
1913/* filetype():
1914 *	Return a character that signifies a filetype
1915 *	symbology from 4.3 ls command.
1916 */
1917static  Char
1918filetype(dir, file)
1919    Char   *dir, *file;
1920{
1921    if (dir) {
1922	Char    path[512];
1923	char   *ptr;
1924	struct stat statb;
1925#ifdef S_ISCDF
1926	/*
1927	 * From: veals@crchh84d.bnr.ca (Percy Veals)
1928	 * An extra stat is required for HPUX CDF files.
1929	 */
1930	struct stat hpstatb;
1931#endif /* S_ISCDF */
1932
1933	if (nostat(dir)) return(' ');
1934
1935	(void) Strcpy(path, dir);
1936	catn(path, file, (int) (sizeof(path) / sizeof(Char)));
1937
1938	if (lstat(ptr = short2str(path), &statb) != -1)
1939	    /* see above #define of lstat */
1940	{
1941#ifdef S_ISLNK
1942	    if (S_ISLNK(statb.st_mode)) {	/* Symbolic link */
1943		if (adrof(STRlistlinks)) {
1944		    if (stat(ptr, &statb) == -1)
1945			return ('&');
1946		    else if (S_ISDIR(statb.st_mode))
1947			return ('>');
1948		    else
1949			return ('@');
1950		}
1951		else
1952		    return ('@');
1953	    }
1954#endif
1955#ifdef S_ISSOCK
1956	    if (S_ISSOCK(statb.st_mode))	/* Socket */
1957		return ('=');
1958#endif
1959#ifdef S_ISFIFO
1960	    if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
1961		return ('|');
1962#endif
1963#ifdef S_ISHIDDEN
1964	    if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
1965		return ('+');
1966#endif
1967#ifdef S_ISCDF
1968	    (void) strcat(ptr, "+");	/* Must append a '+' and re-stat(). */
1969	    if ((stat(ptr, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode))
1970	    	return ('+');		/* Context Dependent Files [hpux] */
1971#endif
1972#ifdef S_ISNWK
1973	    if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
1974		return (':');
1975#endif
1976#ifdef S_ISCHR
1977	    if (S_ISCHR(statb.st_mode))	/* char device */
1978		return ('%');
1979#endif
1980#ifdef S_ISBLK
1981	    if (S_ISBLK(statb.st_mode))	/* block device */
1982		return ('#');
1983#endif
1984#ifdef S_ISDIR
1985	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
1986		return ('/');
1987#endif
1988	    if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
1989		return ('*');
1990	}
1991    }
1992    return (' ');
1993} /* end filetype */
1994
1995
1996/* isadirectory():
1997 *	Return trus if the file is a directory
1998 */
1999static int
2000isadirectory(dir, file)		/* return 1 if dir/file is a directory */
2001    Char   *dir, *file;		/* uses stat rather than lstat to get dest. */
2002{
2003    if (dir) {
2004	Char    path[MAXPATHLEN];
2005	struct stat statb;
2006
2007	(void) Strcpy(path, dir);
2008	catn(path, file, (int) (sizeof(path) / sizeof(Char)));
2009	if (stat(short2str(path), &statb) >= 0) {	/* resolve through
2010							 * symlink */
2011#ifdef S_ISSOCK
2012	    if (S_ISSOCK(statb.st_mode))	/* Socket */
2013		return 0;
2014#endif
2015#ifdef S_ISFIFO
2016	    if (S_ISFIFO(statb.st_mode))	/* Named Pipe */
2017		return 0;
2018#endif
2019	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
2020		return 1;
2021	}
2022    }
2023    return 0;
2024} /* end isadirectory */
2025
2026
2027
2028/* find_rows():
2029 * 	Return how many rows needed to print sorted down columns
2030 */
2031static int
2032find_rows(items, count, no_file_suffix)
2033    Char *items[];
2034    int     count, no_file_suffix;
2035{
2036    register int i, columns, rows;
2037    unsigned int maxwidth = 0;
2038
2039    for (i = 0; i < count; i++)	/* find widest string */
2040	maxwidth = max(maxwidth, (unsigned int) Strlen(items[i]));
2041
2042    maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
2043    columns = (TermH + 1) / maxwidth;	/* PWP: terminal size change */
2044    if (!columns)
2045	columns = 1;
2046    rows = (count + (columns - 1)) / columns;
2047
2048    return rows;
2049} /* end rows_needed_by_print_by_column */
2050
2051
2052/* print_by_column():
2053 * 	Print sorted down columns or across columns when the first
2054 *	word of $listflags shell variable contains 'x'.
2055 *
2056 */
2057void
2058print_by_column(dir, items, count, no_file_suffix)
2059    register Char *dir, *items[];
2060    int     count, no_file_suffix;
2061{
2062    register int i, r, c, columns, rows;
2063    unsigned int w, maxwidth = 0;
2064    Char *val;
2065    bool across;
2066
2067    lbuffed = 0;		/* turn off line buffering */
2068
2069
2070    across = ((val = varval(STRlistflags)) != STRNULL) &&
2071	     (Strchr(val, 'x') != NULL);
2072
2073    for (i = 0; i < count; i++)	/* find widest string */
2074	maxwidth = max(maxwidth, (unsigned int) Strlen(items[i]));
2075
2076    maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
2077    columns = TermH / maxwidth;		/* PWP: terminal size change */
2078    if (!columns || !isatty(didfds ? 1 : SHOUT))
2079	columns = 1;
2080    rows = (count + (columns - 1)) / columns;
2081
2082    i = -1;
2083    for (r = 0; r < rows; r++) {
2084	for (c = 0; c < columns; c++) {
2085	    i = across ? (i + 1) : (c * rows + r);
2086
2087	    if (i < count) {
2088		w = (unsigned int) Strlen(items[i]);
2089
2090#ifdef COLOR_LS_F
2091		if (no_file_suffix) {
2092		    /* Print the command name */
2093		    Char f = items[i][w - 1];
2094		    items[i][w - 1] = 0;
2095		    print_with_color(items[i], w - 1, f);
2096		}
2097		else {
2098		    /* Print filename followed by '/' or '*' or ' ' */
2099		    print_with_color(items[i], w, filetype(dir, items[i]));
2100		    w++;
2101		}
2102#else /* ifndef COLOR_LS_F */
2103		if (no_file_suffix) {
2104		    /* Print the command name */
2105		    xprintf("%S", items[i]);
2106		}
2107		else {
2108		    /* Print filename followed by '/' or '*' or ' ' */
2109		    xprintf("\045S%c", items[i],
2110			    filetype(dir, items[i]));
2111		    w++;
2112		}
2113#endif /* COLOR_LS_F */
2114
2115		if (c < (columns - 1))	/* Not last column? */
2116		    for (; w < maxwidth; w++)
2117			xputchar(' ');
2118	    }
2119	    else if (across)
2120		break;
2121	}
2122	if (Tty_raw_mode)
2123	    xputchar('\r');
2124	xputchar('\n');
2125    }
2126
2127    lbuffed = 1;		/* turn back on line buffering */
2128    flush();
2129} /* end print_by_column */
2130
2131
2132/* StrQcmp():
2133 *	Compare strings ignoring the quoting chars
2134 */
2135int
2136StrQcmp(str1, str2)
2137    register Char *str1, *str2;
2138{
2139    for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM);
2140	 str1++, str2++)
2141	continue;
2142    /*
2143     * The following case analysis is necessary so that characters which look
2144     * negative collate low against normal characters but high against the
2145     * end-of-string NUL.
2146     */
2147    if (*str1 == '\0' && *str2 == '\0')
2148	return (0);
2149    else if (*str1 == '\0')
2150	return (-1);
2151    else if (*str2 == '\0')
2152	return (1);
2153    else
2154	return ((*str1 & TRIM) - (*str2 & TRIM));
2155} /* end StrQcmp */
2156
2157
2158/* fcompare():
2159 * 	Comparison routine for qsort
2160 */
2161int
2162fcompare(file1, file2)
2163    Char  **file1, **file2;
2164{
2165    return (int) collate(*file1, *file2);
2166} /* end fcompare */
2167
2168
2169/* catn():
2170 *	Concatenate src onto tail of des.
2171 *	Des is a string whose maximum length is count.
2172 *	Always null terminate.
2173 */
2174void
2175catn(des, src, count)
2176    register Char *des, *src;
2177    int count;
2178{
2179    while (--count >= 0 && *des)
2180	des++;
2181    while (--count >= 0)
2182	if ((*des++ = *src++) == 0)
2183	    return;
2184    *des = '\0';
2185} /* end catn */
2186
2187
2188/* copyn():
2189 *	 like strncpy but always leave room for trailing \0
2190 *	 and always null terminate.
2191 */
2192void
2193copyn(des, src, count)
2194    register Char *des, *src;
2195    int count;
2196{
2197    while (--count >= 0)
2198	if ((*des++ = *src++) == 0)
2199	    return;
2200    *des = '\0';
2201} /* end copyn */
2202
2203
2204/* tgetenv():
2205 *	like it's normal string counter-part
2206 *	[apollo uses that in tc.os.c, so it cannot be static]
2207 */
2208Char *
2209tgetenv(str)
2210    Char   *str;
2211{
2212    Char  **var;
2213    int     len, res;
2214
2215    len = (int) Strlen(str);
2216    /* Search the STR_environ for the entry matching str. */
2217    for (var = STR_environ; var != NULL && *var != NULL; var++)
2218	if (Strlen(*var) >= len && (*var)[len] == '=') {
2219	  /* Temporarily terminate the string so we can copy the variable
2220	     name. */
2221	    (*var)[len] = '\0';
2222	    res = StrQcmp(*var, str);
2223	    /* Restore the '=' and return a pointer to the value of the
2224	       environment variable. */
2225	    (*var)[len] = '=';
2226	    if (res == 0)
2227		return (&((*var)[len + 1]));
2228	}
2229    return (NULL);
2230} /* end tgetenv */
2231
2232
2233struct scroll_tab_list *scroll_tab = 0;
2234
2235static void
2236add_scroll_tab(item)
2237    Char *item;
2238{
2239    struct scroll_tab_list *new_scroll;
2240
2241    new_scroll = (struct scroll_tab_list *) xmalloc((size_t)
2242	    sizeof(struct scroll_tab_list));
2243    new_scroll->element = Strsave(item);
2244    new_scroll->next = scroll_tab;
2245    scroll_tab = new_scroll;
2246}
2247
2248static void
2249choose_scroll_tab(exp_name, cnt)
2250    Char **exp_name;
2251    int cnt;
2252{
2253    struct scroll_tab_list *loop;
2254    int tmp = cnt;
2255    Char **ptr;
2256
2257    ptr = (Char **) xmalloc((size_t) sizeof(Char *) * cnt);
2258
2259    for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next)
2260	ptr[--tmp] = loop->element;
2261
2262    qsort((ptr_t) ptr, (size_t) cnt, sizeof(Char *),
2263	  (int (*) __P((const void *, const void *))) fcompare);
2264
2265    copyn(*exp_name, ptr[curchoice], (int) Strlen(ptr[curchoice]));
2266    xfree((ptr_t) ptr);
2267}
2268
2269static void
2270free_scroll_tab()
2271{
2272    struct scroll_tab_list *loop;
2273
2274    while(scroll_tab) {
2275	loop = scroll_tab;
2276	scroll_tab = scroll_tab->next;
2277	xfree((ptr_t) loop->element);
2278	xfree((ptr_t) loop);
2279    }
2280}
2281