xref: /freebsd/contrib/tcsh/tw.parse.c (revision ca987d4641cdcd7f27e153db17c5bf064934faf5)
1 /* $Header: /p/tcsh/cvsroot/tcsh/tw.parse.c,v 3.139 2015/10/16 14:59:56 christos Exp $ */
2 /*
3  * tw.parse.c: Everyone has taken a shot in this futile effort to
4  *	       lexically analyze a csh line... Well we cannot good
5  *	       a job as good as sh.lex.c; but we try. Amazing that
6  *	       it works considering how many hands have touched this code
7  */
8 /*-
9  * Copyright (c) 1980, 1991 The Regents of the University of California.
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 #include "sh.h"
37 
38 RCSID("$tcsh: tw.parse.c,v 3.139 2015/10/16 14:59:56 christos Exp $")
39 
40 #include "tw.h"
41 #include "ed.h"
42 #include "tc.h"
43 
44 #include <assert.h>
45 
46 #ifdef WINNT_NATIVE
47 #include "nt.const.h"
48 #endif /* WINNT_NATIVE */
49 #define EVEN(x) (((x) & 1) != 1)
50 
51 #define DOT_NONE	0	/* Don't display dot files		*/
52 #define DOT_NOT		1	/* Don't display dot or dot-dot		*/
53 #define DOT_ALL		2	/* Display all dot files		*/
54 
55 /*  TW_NONE,	       TW_COMMAND,     TW_VARIABLE,    TW_LOGNAME,	*/
56 /*  TW_FILE,	       TW_DIRECTORY,   TW_VARLIST,     TW_USER,		*/
57 /*  TW_COMPLETION,     TW_ALIAS,       TW_SHELLVAR,    TW_ENVVAR,	*/
58 /*  TW_BINDING,        TW_WORDLIST,    TW_LIMIT,       TW_SIGNAL	*/
59 /*  TW_JOB,	       TW_EXPLAIN,     TW_TEXT,	       TW_GRPNAME	*/
60 static void (*const tw_start_entry[]) (DIR *, const Char *) = {
61     tw_file_start,     tw_cmd_start,   tw_var_start,   tw_logname_start,
62     tw_file_start,     tw_file_start,  tw_vl_start,    tw_logname_start,
63     tw_complete_start, tw_alias_start, tw_var_start,   tw_var_start,
64     tw_bind_start,     tw_wl_start,    tw_limit_start, tw_sig_start,
65     tw_job_start,      tw_file_start,  tw_file_start,  tw_grpname_start
66 };
67 
68 static int (*const tw_next_entry[]) (struct Strbuf *, struct Strbuf *,
69 				     int *) = {
70     tw_file_next,      tw_cmd_next,    tw_var_next,    tw_logname_next,
71     tw_file_next,      tw_file_next,   tw_var_next,    tw_logname_next,
72     tw_var_next,       tw_var_next,    tw_shvar_next,  tw_envvar_next,
73     tw_bind_next,      tw_wl_next,     tw_limit_next,  tw_sig_next,
74     tw_job_next,       tw_file_next,   tw_file_next,   tw_grpname_next
75 };
76 
77 static void (*const tw_end_entry[]) (void) = {
78     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
79     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
80     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
81     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
82     tw_dir_end,	       tw_dir_end,     tw_dir_end,    tw_grpname_end
83 };
84 
85 /* #define TDEBUG */
86 
87 /* Set to TRUE if recexact is set and an exact match is found
88  * along with other, longer, matches.
89  */
90 
91 int curchoice = -1;
92 
93 int match_unique_match = FALSE;
94 int non_unique_match = FALSE;
95 static int SearchNoDirErr = 0;	/* t_search returns -2 if dir is unreadable */
96 
97 /* state so if a completion is interrupted, the input line doesn't get
98    nuked */
99 int InsideCompletion = 0;
100 
101 /* do the expand or list on the command line -- SHOULD BE REPLACED */
102 
103 static	void	 extract_dir_and_name	(const Char *, struct Strbuf *,
104 					 Char **);
105 static	int	 insert_meta		(const Char *, const Char *,
106 					 const Char *, int);
107 static	int	 tilde			(struct Strbuf *, Char *);
108 static  int      expand_dir		(const Char *, struct Strbuf *, DIR **,
109 					 COMMAND);
110 static	int	 nostat			(Char *);
111 static	Char	 filetype		(Char *, Char *);
112 static	int	 t_glob			(Char ***, int);
113 static	int	 c_glob			(Char ***);
114 static	int	 is_prefix		(Char *, Char *);
115 static	int	 is_prefixmatch		(Char *, Char *, int);
116 static	int	 is_suffix		(Char *, Char *);
117 static	int	 recognize		(struct Strbuf *, const Char *, size_t,
118 					 int, int, int);
119 static	int	 ignored		(Char *);
120 static	int	 isadirectory		(const Char *, const Char *);
121 static  int      tw_collect_items	(COMMAND, int, struct Strbuf *,
122 					 struct Strbuf *, Char *, const Char *,
123 					 int);
124 static  int      tw_collect		(COMMAND, int, struct Strbuf *,
125 					 struct Strbuf *, Char *, Char *, int,
126 					 DIR *);
127 static	Char 	 tw_suffix		(int, struct Strbuf *,const Char *,
128 					 Char *);
129 static	void 	 tw_fixword		(int, struct Strbuf *, Char *, Char *);
130 static	void	 tw_list_items		(int, int, int);
131 static 	void	 add_scroll_tab		(Char *);
132 static 	void 	 choose_scroll_tab	(struct Strbuf *, int);
133 static	void	 free_scroll_tab	(void);
134 static	int	 find_rows		(Char *[], int, int);
135 
136 #ifdef notdef
137 /*
138  * If we find a set command, then we break a=b to a= and word becomes
139  * b else, we don't break a=b. [don't use that; splits words badly and
140  * messes up tw_complete()]
141  */
142 #define isaset(c, w) ((w)[-1] == '=' && \
143 		      ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
144 		       ((c[3] == ' ' || (c)[3] == '\t'))))
145 #endif
146 
147 /* TRUE if character must be quoted */
148 #define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#')
149 /* TRUE if double quotes don't protect character */
150 #define tricky_dq(w) (cmap(w, _DOL | _QB))
151 
152 /* tenematch():
153  *	Return:
154  *		> 1:    No. of items found
155  *		= 1:    Exactly one match / spelling corrected
156  *		= 0:    No match / spelling was correct
157  *		< 0:    Error (incl spelling correction impossible)
158  */
159 int
160 tenematch(Char *inputline, int num_read, COMMAND command)
161 {
162     struct Strbuf qline = Strbuf_INIT;
163     Char    qu = 0, *pat = STRNULL;
164     size_t wp, word, wordp, cmd_start, oword = 0, ocmd_start = 0;
165     Char   *str_end, *cp;
166     Char   *word_start;
167     Char   *oword_start = NULL;
168     eChar suf = 0;
169     int     looking;		/* what we are looking for		*/
170     int     search_ret;		/* what search returned for debugging 	*/
171     int     backq = 0;
172 
173     str_end = &inputline[num_read];
174     cleanup_push(&qline, Strbuf_cleanup);
175 
176     word_start = inputline;
177     word = cmd_start = 0;
178     for (cp = inputline; cp < str_end; cp++) {
179         if (!cmap(qu, _ESC)) {
180 	    if (cmap(*cp, _QF|_ESC)) {
181 		if (qu == 0 || qu == *cp) {
182 		    qu ^= *cp;
183 		    continue;
184 		}
185 	    }
186 	    if (qu != '\'' && cmap(*cp, _QB)) {
187 		if ((backq ^= 1) != 0) {
188 		    ocmd_start = cmd_start;
189 		    oword_start = word_start;
190 		    oword = word;
191 		    word_start = cp + 1;
192 		    word = cmd_start = qline.len + 1;
193 		}
194 		else {
195 		    cmd_start = ocmd_start;
196 		    word_start = oword_start;
197 		    word = oword;
198 		}
199 		Strbuf_append1(&qline, *cp);
200 		continue;
201 	    }
202 	}
203 	if (iscmdmeta(*cp))
204 	    cmd_start = qline.len + 1;
205 
206 	/* Don't quote '/' to make the recognize stuff work easily */
207 	/* Don't quote '$' in double quotes */
208 
209 	if (cmap(*cp, _ESC) && cp < str_end - 1 && cp[1] == HIST &&
210 	    HIST != '\0')
211 	    Strbuf_append1(&qline, *++cp | QUOTE);
212 	else if (qu && (tricky(*cp) || *cp == '~') &&
213 	    !(qu == '\"' && tricky_dq(*cp)))
214 	    Strbuf_append1(&qline, *cp | QUOTE);
215 	else
216 	    Strbuf_append1(&qline, *cp);
217 	if (ismetahash(qline.s[qline.len - 1])
218 	    /* || isaset(qline.s + cmd_start, qline.s + qline.len) */)
219 	    word = qline.len, word_start = cp + 1;
220 	if (cmap(qu, _ESC))
221 	    qu = 0;
222       }
223     Strbuf_terminate(&qline);
224     wp = qline.len;
225 
226     /*
227      *  SPECIAL HARDCODED COMPLETIONS:
228      *    first word of command       -> TW_COMMAND
229      *    everything else             -> TW_ZERO
230      *
231      */
232     looking = starting_a_command(qline.s + word - 1, qline.s) ?
233 	TW_COMMAND : TW_ZERO;
234 
235     wordp = word;
236 
237 #ifdef TDEBUG
238     {
239 	const Char *p;
240 
241 	xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking);
242 	xprintf("\ncmd_start:%S:\n", qline.s + cmd_start);
243 	xprintf("qline:%S:\n", qline.s);
244 	xprintf("qline:");
245 	for (p = qline.s; *p; p++)
246 	    xprintf("%c", *p & QUOTE ? '-' : ' ');
247 	xprintf(":\n");
248 	xprintf("word:%S:\n", qline.s + word);
249 	xprintf("word:");
250 	for (p = qline.s + word; *p; p++)
251 	    xprintf("%c", *p & QUOTE ? '-' : ' ');
252 	xprintf(":\n");
253     }
254 #endif
255 
256     if ((looking == TW_COMMAND || looking == TW_ZERO) &&
257         (command == RECOGNIZE || command == LIST || command == SPELL ||
258 	 command == RECOGNIZE_SCROLL)) {
259 	Char *p;
260 
261 #ifdef TDEBUG
262 	xprintf(CGETS(30, 2, "complete %d "), looking);
263 #endif
264 	p = qline.s + wordp;
265 	looking = tw_complete(qline.s + cmd_start, &p, &pat, looking, &suf);
266 	wordp = p - qline.s;
267 #ifdef TDEBUG
268 	xprintf(CGETS(30, 3, "complete %d %S\n"), looking, pat);
269 #endif
270     }
271 
272     switch (command) {
273 	Char   *bptr;
274 	Char   *items[2], **ptr;
275 	int     i, count;
276 
277     case RECOGNIZE:
278     case RECOGNIZE_SCROLL:
279     case RECOGNIZE_ALL: {
280 	struct Strbuf wordbuf = Strbuf_INIT;
281 	Char   *slshp;
282 
283 	if (adrof(STRautocorrect)) {
284 	    if ((slshp = Strrchr(qline.s + wordp, '/')) != NULL &&
285 		slshp[1] != '\0') {
286 		SearchNoDirErr = 1;
287 		for (bptr = qline.s + wordp; bptr < slshp; bptr++) {
288 		    /*
289 		     * do not try to correct spelling of words containing
290 		     * globbing characters
291 		     */
292 		    if (isglob(*bptr)) {
293 			SearchNoDirErr = 0;
294 			break;
295 		    }
296 		}
297 	    }
298 	}
299 	else
300 	    slshp = STRNULL;
301 	Strbuf_append(&wordbuf, qline.s + wordp);
302 	Strbuf_terminate(&wordbuf);
303 	cleanup_push(&wordbuf, Strbuf_cleanup);
304 	search_ret = t_search(&wordbuf, command, looking, 1, pat, suf);
305 	qline.len = wordp;
306 	Strbuf_append(&qline, wordbuf.s);
307 	Strbuf_terminate(&qline);
308 	cleanup_until(&wordbuf);
309 	SearchNoDirErr = 0;
310 
311 	if (search_ret == -2) {
312 	    Char *rword;
313 
314 	    rword = Strsave(slshp);
315 	    cleanup_push(rword, xfree);
316 	    if (slshp != STRNULL)
317 		*slshp = '\0';
318 	    wordbuf = Strbuf_init;
319 	    Strbuf_append(&wordbuf, qline.s + wordp);
320 	    Strbuf_terminate(&wordbuf);
321 	    cleanup_push(&wordbuf, Strbuf_cleanup);
322 	    search_ret = spell_me(&wordbuf, looking, pat, suf);
323 	    if (search_ret == 1) {
324 		Strbuf_append(&wordbuf, rword);
325 		Strbuf_terminate(&wordbuf);
326 		wp = wordp + wordbuf.len;
327 		search_ret = t_search(&wordbuf, command, looking, 1, pat, suf);
328 	    }
329 	    qline.len = wordp;
330 	    Strbuf_append(&qline, wordbuf.s);
331 	    Strbuf_terminate(&qline);
332 	    cleanup_until(rword);
333 	}
334 	if (qline.s[wp] != '\0' &&
335 	    insert_meta(word_start, str_end, qline.s + word, !qu) < 0)
336 	    goto err;		/* error inserting */
337 	break;
338     }
339 
340     case SPELL: {
341 	struct Strbuf wordbuf = Strbuf_INIT;
342 
343 	for (bptr = word_start; bptr < str_end; bptr++) {
344 	    /*
345 	     * do not try to correct spelling of words containing globbing
346 	     * characters
347 	     */
348 	    if (isglob(*bptr)) {
349 		search_ret = 0;
350 		goto end;
351 	    }
352 	}
353 	Strbuf_append(&wordbuf, qline.s + wordp);
354 	Strbuf_terminate(&wordbuf);
355 	cleanup_push(&wordbuf, Strbuf_cleanup);
356 
357 	/*
358 	 * Don't try to spell things that we know they are correct.
359 	 * Trying to spell can hang when we have NFS mounted hung
360 	 * volumes.
361 	 */
362 	if ((looking == TW_COMMAND) && Strchr(wordbuf.s, '/') != NULL) {
363 	    if (executable(NULL, wordbuf.s, 0)) {
364 		cleanup_until(&wordbuf);
365 		search_ret = 0;
366 		goto end;
367 	    }
368 	}
369 
370 	search_ret = spell_me(&wordbuf, looking, pat, suf);
371 	qline.len = wordp;
372 	Strbuf_append(&qline, wordbuf.s);
373 	Strbuf_terminate(&qline);
374 	cleanup_until(&wordbuf);
375 	if (search_ret == 1) {
376 	    if (insert_meta(word_start, str_end, qline.s + word, !qu) < 0)
377 		goto err;		/* error inserting */
378 	}
379 	break;
380     }
381 
382     case PRINT_HELP:
383 	do_help(qline.s + cmd_start);
384 	search_ret = 1;
385 	break;
386 
387     case GLOB:
388     case GLOB_EXPAND:
389 	items[0] = Strsave(qline.s + wordp);
390 	items[1] = NULL;
391 	cleanup_push(items[0], xfree);
392 	ptr = items;
393 	count = (looking == TW_COMMAND && Strchr(qline.s + wordp, '/') == 0) ?
394 		c_glob(&ptr) :
395 		t_glob(&ptr, looking == TW_COMMAND);
396 	cleanup_until(items[0]);
397 	if (ptr != items)
398 	    cleanup_push(ptr, blk_cleanup);
399 	if (count > 0) {
400 	    if (command == GLOB)
401 		print_by_column(STRNULL, ptr, count, 0);
402 	    else {
403 		DeleteBack(str_end - word_start);/* get rid of old word */
404 		for (i = 0; i < count; i++)
405 		    if (ptr[i] && *ptr[i]) {
406 			(void) quote(ptr[i]);
407 			if (insert_meta(0, 0, ptr[i], 0) < 0 ||
408 			    InsertStr(STRspace) < 0) {
409 			    if (ptr != items)
410 				cleanup_until(ptr);
411 			    goto err;		/* error inserting */
412 			}
413 		    }
414 	    }
415 	}
416 	if (ptr != items)
417 	    cleanup_until(ptr);
418 	search_ret = count;
419 	break;
420 
421     case VARS_EXPAND:
422 	bptr = dollar(qline.s + word);
423 	if (bptr != NULL) {
424 	    if (insert_meta(word_start, str_end, bptr, !qu) < 0) {
425 		xfree(bptr);
426 		goto err;		/* error inserting */
427 	    }
428 	    xfree(bptr);
429 	    search_ret = 1;
430 	    break;
431 	}
432 	search_ret = 0;
433 	break;
434 
435     case PATH_NORMALIZE:
436 	if ((bptr = dnormalize(qline.s + wordp, symlinks == SYM_IGNORE ||
437 			       symlinks == SYM_EXPAND)) != NULL) {
438 	    if (insert_meta(word_start, str_end, bptr, !qu) < 0) {
439 		xfree(bptr);
440 		goto err;		/* error inserting */
441 	    }
442 	    xfree(bptr);
443 	    search_ret = 1;
444 	    break;
445 	}
446 	search_ret = 0;
447 	break;
448 
449     case COMMAND_NORMALIZE: {
450 	Char *p;
451 	int found;
452 
453 	found = cmd_expand(qline.s + wordp, &p);
454 
455 	if (!found) {
456 	    xfree(p);
457 	    search_ret = 0;
458 	    break;
459 	}
460 	if (insert_meta(word_start, str_end, p, !qu) < 0) {
461 	    xfree(p);
462 	    goto err;		/* error inserting */
463 	}
464 	xfree(p);
465 	search_ret = 1;
466 	break;
467     }
468 
469     case LIST:
470     case LIST_ALL: {
471 	struct Strbuf wordbuf = Strbuf_INIT;
472 
473 	Strbuf_append(&wordbuf, qline.s + wordp);
474 	Strbuf_terminate(&wordbuf);
475 	cleanup_push(&wordbuf, Strbuf_cleanup);
476 	search_ret = t_search(&wordbuf, LIST, looking, 1, pat, suf);
477 	qline.len = wordp;
478 	Strbuf_append(&qline, wordbuf.s);
479 	Strbuf_terminate(&qline);
480 	cleanup_until(&wordbuf);
481 	break;
482     }
483 
484     default:
485 	xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname);
486 	search_ret = 1;
487     }
488  end:
489     cleanup_until(&qline);
490     return search_ret;
491 
492  err:
493     cleanup_until(&qline);
494     return -1;
495 } /* end tenematch */
496 
497 
498 /* t_glob():
499  * 	Return a list of files that match the pattern
500  */
501 static int
502 t_glob(Char ***v, int cmd)
503 {
504     jmp_buf_t osetexit;
505     int gflag;
506 
507     if (**v == 0)
508 	return (0);
509     gflag = tglob(*v);
510     if (gflag) {
511 	size_t omark;
512 
513 	getexit(osetexit);	/* make sure to come back here */
514 	omark = cleanup_push_mark();
515 	if (setexit() == 0)
516 	    *v = globall(*v, gflag);
517 	cleanup_pop_mark(omark);
518 	resexit(osetexit);
519 	if (haderr) {
520 	    haderr = 0;
521 	    NeedsRedraw = 1;
522 	    return (-1);
523 	}
524 	if (*v == 0)
525 	    return (0);
526     }
527     else
528 	return (0);
529 
530     if (cmd) {
531 	Char **av = *v, *p;
532 	int fwd, i;
533 
534 	for (i = 0, fwd = 0; av[i] != NULL; i++)
535 	    if (!executable(NULL, av[i], 0)) {
536 		fwd++;
537 		p = av[i];
538 		av[i] = NULL;
539 		xfree(p);
540 	    }
541 	    else if (fwd)
542 		av[i - fwd] = av[i];
543 
544 	if (fwd)
545 	    av[i - fwd] = av[i];
546     }
547 
548     return blklen(*v);
549 } /* end t_glob */
550 
551 
552 /* c_glob():
553  * 	Return a list of commands that match the pattern
554  */
555 static int
556 c_glob(Char ***v)
557 {
558     struct blk_buf av = BLK_BUF_INIT;
559     struct Strbuf cmd = Strbuf_INIT, dir = Strbuf_INIT;
560     Char *pat = **v;
561     int flag;
562 
563     if (pat == NULL)
564 	return (0);
565 
566     cleanup_push(&av, bb_cleanup);
567     cleanup_push(&cmd, Strbuf_cleanup);
568     cleanup_push(&dir, Strbuf_cleanup);
569 
570     tw_cmd_start(NULL, NULL);
571     while (cmd.len = 0, tw_cmd_next(&cmd, &dir, &flag) != 0) {
572 	Strbuf_terminate(&cmd);
573 	if (Gmatch(cmd.s, pat))
574 	    bb_append(&av, Strsave(cmd.s));
575     }
576     tw_dir_end();
577     *v = bb_finish(&av);
578     cleanup_ignore(&av);
579     cleanup_until(&av);
580 
581     return av.len;
582 } /* end c_glob */
583 
584 
585 /* insert_meta():
586  *      change the word before the cursor.
587  *        cp must point to the start of the unquoted word.
588  *        cpend to the end of it.
589  *        word is the text that has to be substituted.
590  *      strategy:
591  *        try to keep all the quote characters of the user's input.
592  *        change quote type only if necessary.
593  */
594 static int
595 insert_meta(const Char *cp, const Char *cpend, const Char *word,
596 	    int closequotes)
597 {
598     struct Strbuf buffer = Strbuf_INIT;
599     Char *bptr;
600     const Char *wptr;
601     int in_sync = (cp != NULL);
602     Char qu = 0;
603     int ndel = (int) (cp ? cpend - cp : 0);
604     Char w, wq;
605     int res;
606 
607     for (wptr = word;;) {
608 	if (cp >= cpend)
609 	    in_sync = 0;
610 	if (in_sync && !cmap(qu, _ESC) && cmap(*cp, _QF|_ESC))
611 	    if (qu == 0 || qu == *cp) {
612 		qu ^= *cp;
613 		Strbuf_append1(&buffer, *cp++);
614 		continue;
615 	    }
616 	w = *wptr;
617 	if (w == 0)
618 	    break;
619 
620 	wq = w & QUOTE;
621 #if INVALID_BYTE != 0
622 	/* add checking INVALID_BYTE for FIX UTF32 */
623 	if ((w & INVALID_BYTE) != INVALID_BYTE)		/* w < INVALID_BYTE */
624 #endif
625 	    w &= ~QUOTE;
626 
627 	if (cmap(w, _ESC | _QF))
628 	    wq = QUOTE;		/* quotes are always quoted */
629 
630 	if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) {
631 	    /* We have to unquote the character */
632 	    in_sync = 0;
633 	    if (cmap(qu, _ESC))
634 		buffer.s[buffer.len - 1] = w;
635 	    else {
636 		Strbuf_append1(&buffer, qu);
637 		Strbuf_append1(&buffer, w);
638 		if (wptr[1] == 0)
639 		    qu = 0;
640 		else
641 		    Strbuf_append1(&buffer, qu);
642 	    }
643 	} else if (qu && w == qu) {
644 	    in_sync = 0;
645 	    if (buffer.len != 0 && buffer.s[buffer.len - 1] == qu) {
646 		/* User misunderstanding :) */
647 		buffer.s[buffer.len - 1] = '\\';
648 		Strbuf_append1(&buffer, w);
649 		qu = 0;
650 	    } else {
651 		Strbuf_append1(&buffer, qu);
652 		Strbuf_append1(&buffer, '\\');
653 		Strbuf_append1(&buffer, w);
654 		Strbuf_append1(&buffer, qu);
655 	    }
656 	}
657 	else if (wq && qu == '\"' && tricky_dq(w)) {
658 	    in_sync = 0;
659 	    Strbuf_append1(&buffer, qu);
660 	    Strbuf_append1(&buffer, '\\');
661 	    Strbuf_append1(&buffer, w);
662 	    Strbuf_append1(&buffer, qu);
663 	} else if (wq &&
664 		   ((!qu && (tricky(w) || (w == HISTSUB && HISTSUB != '\0'
665 		       && buffer.len == 0))) ||
666 		    (!cmap(qu, _ESC) && w == HIST && HIST != '\0'))) {
667 	    in_sync = 0;
668 	    Strbuf_append1(&buffer, '\\');
669 	    Strbuf_append1(&buffer, w);
670 	} else {
671 	    if (in_sync && *cp++ != w)
672 		in_sync = 0;
673 	    Strbuf_append1(&buffer, w);
674 	}
675 	wptr++;
676 	if (cmap(qu, _ESC))
677 	    qu = 0;
678     }
679     if (closequotes && qu && !cmap(qu, _ESC))
680 	Strbuf_append1(&buffer, w);
681     bptr = Strbuf_finish(&buffer);
682     if (ndel)
683 	DeleteBack(ndel);
684     res = InsertStr(bptr);
685     xfree(bptr);
686     return res;
687 } /* end insert_meta */
688 
689 
690 
691 /* is_prefix():
692  *	return true if check matches initial chars in template
693  *	This differs from PWB imatch in that if check is null
694  *	it matches anything
695  */
696 static int
697 is_prefix(Char *check, Char *template)
698 {
699     for (; *check; check++, template++)
700 	if ((*check & TRIM) != (*template & TRIM))
701 	    return (FALSE);
702     return (TRUE);
703 } /* end is_prefix */
704 
705 
706 /* is_prefixmatch():
707  *	return true if check matches initial chars in template
708  *	This differs from PWB imatch in that if check is null
709  *	it matches anything
710  * and matches on shortening of commands
711  */
712 static int
713 is_prefixmatch(Char *check, Char *template, int enhanced)
714 {
715     Char MCH1, MCH2, LCH1, LCH2;
716 
717     for (; *check; check++, template++) {
718 	if ((*check & TRIM) != (*template & TRIM)) {
719 	    MCH1 = (*check & TRIM);
720 	    MCH2 = (*template & TRIM);
721             LCH1 = Isupper(MCH1) ? Tolower(MCH1) :
722 		enhanced == 2 && MCH1 == '_' ? '-' : MCH1;
723             LCH2 = Isupper(MCH2) ? Tolower(MCH2) :
724 		enhanced == 2 && MCH2 == '_' ? '-' : MCH2;
725 	    if (MCH1 != MCH2 && MCH1 != LCH2 &&
726 		(LCH1 != MCH2 || enhanced == 2)) {
727 		if (enhanced && ((*check & TRIM) == '-' ||
728 				 (*check & TRIM) == '.' ||
729 				 (*check & TRIM) == '_')) {
730 		    MCH1 = MCH2 = (*check & TRIM);
731 		    if (MCH1 == '_' && enhanced != 2) {
732 			MCH2 = '-';
733 		    } else if (MCH1 == '-') {
734 			MCH2 = '_';
735 		    }
736 		    for (; *template && (*template & TRIM) != MCH1 &&
737 					(*template & TRIM) != MCH2; template++)
738 			continue;
739 		    if (!*template) {
740 	                return (FALSE);
741 		    }
742 		} else {
743 		    return (FALSE);
744 		}
745 	    }
746 	}
747     }
748     return (TRUE);
749 } /* end is_prefixmatch */
750 
751 
752 /* is_suffix():
753  *	Return true if the chars in template appear at the
754  *	end of check, I.e., are it's suffix.
755  */
756 static int
757 is_suffix(Char *check, Char *template)
758 {
759     Char *t, *c;
760 
761     t = Strend(template);
762     c = Strend(check);
763     for (;;) {
764 	if (t == template)
765 	    return 1;
766 	if (c == check || (*--t & TRIM) != (*--c & TRIM))
767 	    return 0;
768     }
769 } /* end is_suffix */
770 
771 
772 /* ignored():
773  *	Return true if this is an ignored item
774  */
775 static int
776 ignored(Char *item)
777 {
778     struct varent *vp;
779     Char **cp;
780 
781     if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
782 	return (FALSE);
783     for (; *cp != NULL; cp++)
784 	if (is_suffix(item, *cp))
785 	    return (TRUE);
786     return (FALSE);
787 } /* end ignored */
788 
789 
790 
791 /* starting_a_command():
792  *	return true if the command starting at wordstart is a command
793  */
794 int
795 starting_a_command(Char *wordstart, Char *inputline)
796 {
797     Char *ptr, *ncmdstart;
798     int     count, bsl;
799     static  Char
800             cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
801             cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
802 
803     /*
804      * Find if the number of backquotes is odd or even.
805      */
806     for (ptr = wordstart, count = 0;
807 	 ptr >= inputline;
808 	 count += (*ptr-- == '`'))
809 	continue;
810     /*
811      * if the number of backquotes is even don't include the backquote char in
812      * the list of command starting delimiters [if it is zero, then it does not
813      * matter]
814      */
815     ncmdstart = cmdstart + EVEN(count);
816 
817     /*
818      * look for the characters previous to this word if we find a command
819      * starting delimiter we break. if we find whitespace and another previous
820      * word then we are not a command
821      *
822      * count is our state machine: 0 looking for anything 1 found white-space
823      * looking for non-ws
824      */
825     for (count = 0; wordstart >= inputline; wordstart--) {
826 	if (*wordstart == '\0')
827 	    continue;
828 	if (Strchr(ncmdstart, *wordstart)) {
829 	    for (ptr = wordstart, bsl = 0; *(--ptr) == '\\'; bsl++);
830 	    if (bsl & 1) {
831 		wordstart--;
832 		continue;
833 	    } else
834 		break;
835 	}
836 	/*
837 	 * found white space
838 	 */
839 	if ((ptr = Strchr(cmdalive, *wordstart)) != NULL)
840 	    count = 1;
841 	if (count == 1 && !ptr)
842 	    return (FALSE);
843     }
844 
845     if (wordstart > inputline)
846 	switch (*wordstart) {
847 	case '&':		/* Look for >& */
848 	    while (wordstart > inputline &&
849 		   (*--wordstart == ' ' || *wordstart == '\t'))
850 		continue;
851 	    if (*wordstart == '>')
852 		return (FALSE);
853 	    break;
854 	case '(':		/* check for foreach, if etc. */
855 	    while (wordstart > inputline &&
856 		   (*--wordstart == ' ' || *wordstart == '\t'))
857 		continue;
858 	    if (!iscmdmeta(*wordstart) &&
859 		(*wordstart != ' ' && *wordstart != '\t'))
860 		return (FALSE);
861 	    break;
862 	default:
863 	    break;
864 	}
865     return (TRUE);
866 } /* end starting_a_command */
867 
868 
869 /* recognize():
870  *	Object: extend what user typed up to an ambiguity.
871  *	Algorithm:
872  *	On first match, copy full item (assume it'll be the only match)
873  *	On subsequent matches, shorten exp_name to the first
874  *	character mismatch between exp_name and item.
875  *	If we shorten it back to the prefix length, stop searching.
876  */
877 static int
878 recognize(struct Strbuf *exp_name, const Char *item, size_t name_length,
879 	  int numitems, int enhanced, int igncase)
880 {
881     Char MCH1, MCH2, LCH1, LCH2;
882     Char *x;
883     const Char *ent;
884     size_t len = 0;
885 
886     if (numitems == 1) {	/* 1st match */
887 	exp_name->len = 0;
888 	Strbuf_append(exp_name, item);
889 	Strbuf_terminate(exp_name);
890 	return (0);
891     }
892     if (!enhanced && !igncase) {
893 	for (x = exp_name->s, ent = item; *x && (*x & TRIM) == (*ent & TRIM);
894 	     x++, ent++)
895 	    len++;
896     } else {
897 	for (x = exp_name->s, ent = item; *x; x++, ent++) {
898 	    MCH1 = *x & TRIM;
899 	    MCH2 = *ent & TRIM;
900 	    LCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1;
901 	    LCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2;
902 	    if (MCH1 != MCH2) {
903 		if (LCH1 == MCH2 || (MCH1 == '_' && MCH2 == '-'))
904 		    *x = *ent;
905 		else if (LCH1 != LCH2)
906 		    break;
907 	    }
908 	    len++;
909 	}
910     }
911     *x = '\0';		/* Shorten at 1st char diff */
912     exp_name->len = x - exp_name->s;
913     if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) &&
914 	len == name_length)	/* Ambiguous to prefix? */
915 	return (-1);	/* So stop now and save time */
916     return (0);
917 } /* end recognize */
918 
919 
920 /* tw_collect_items():
921  *	Collect items that match target.
922  *	SPELL command:
923  *		Returns the spelling distance of the closest match.
924  *	else
925  *		Returns the number of items found.
926  *		If none found, but some ignored items were found,
927  *		It returns the -number of ignored items.
928  */
929 static int
930 tw_collect_items(COMMAND command, int looking, struct Strbuf *exp_dir,
931 		 struct Strbuf *exp_name, Char *target, const Char *pat,
932 		 int flags)
933 {
934     int done = FALSE;			 /* Search is done */
935     int showdots;			 /* Style to show dot files */
936     int nignored = 0;			 /* Number of fignored items */
937     int numitems = 0;			 /* Number of matched items */
938     size_t name_length = Strlen(target); /* Length of prefix (file name) */
939     int exec_check = flags & TW_EXEC_CHK;/* need to check executability	*/
940     int dir_check  = flags & TW_DIR_CHK; /* Need to check for directories */
941     int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */
942     int dir_ok     = flags & TW_DIR_OK;  /* Ignore directories? */
943     int gpat       = flags & TW_PAT_OK;	 /* Match against a pattern */
944     int ignoring   = flags & TW_IGN_OK;	 /* Use fignore? */
945     int d = 4, nd;			 /* Spelling distance */
946     Char **cp;
947     Char *ptr;
948     struct varent *vp;
949     struct Strbuf buf = Strbuf_INIT, item = Strbuf_INIT;
950     int enhanced = 0;
951     int cnt = 0;
952     int igncase = 0;
953 
954 
955     flags = 0;
956 
957     showdots = DOT_NONE;
958     if ((ptr = varval(STRlistflags)) != STRNULL)
959 	while (*ptr)
960 	    switch (*ptr++) {
961 	    case 'a':
962 		showdots = DOT_ALL;
963 		break;
964 	    case 'A':
965 		showdots = DOT_NOT;
966 		break;
967 	    default:
968 		break;
969 	    }
970 
971     if (looking == TW_COMMAND
972 	&& (vp = adrof(STRautorehash)) != NULL && vp->vec != NULL)
973 	for (cp = vp->vec; *cp; cp++)
974 	    if (Strcmp(*cp, STRalways) == 0
975 		|| (Strcmp(*cp, STRcorrect) == 0 && command == SPELL)
976 		|| (Strcmp(*cp, STRcomplete) == 0 && command != SPELL)) {
977 		tw_cmd_free();
978 		tw_cmd_start(NULL, NULL);
979 		break;
980 	    }
981 
982     cleanup_push(&item, Strbuf_cleanup);
983     cleanup_push(&buf, Strbuf_cleanup);
984     while (!done &&
985 	   (item.len = 0,
986 	    tw_next_entry[looking](&item, exp_dir, &flags) != 0)) {
987 	Strbuf_terminate(&item);
988 #ifdef TDEBUG
989 	xprintf("item = %S\n", item.s);
990 #endif
991 	switch (looking) {
992 	case TW_FILE:
993 	case TW_DIRECTORY:
994 	case TW_TEXT:
995 	    /*
996 	     * Don't match . files on null prefix match
997 	     */
998 	    if (showdots == DOT_NOT && (ISDOT(item.s) || ISDOTDOT(item.s)))
999 		done = TRUE;
1000 	    if (name_length == 0 && item.s[0] == '.' && showdots == DOT_NONE)
1001 		done = TRUE;
1002 	    break;
1003 
1004 	case TW_COMMAND:
1005 #if defined(_UWIN) || defined(__CYGWIN__)
1006 	    /*
1007 	     * Turn foo.{exe,com,bat,cmd} into foo since UWIN's readdir returns
1008 	     * the file with the .exe, .com, .bat, .cmd extension
1009 	     *
1010 	     * Same for Cygwin, but only for .exe and .com extension.
1011 	     */
1012 	    {
1013 #ifdef __CYGWIN__
1014 		static const char *rext[] = { ".exe", ".com" };
1015 #else
1016 		static const char *rext[] = { ".exe", ".bat", ".com", ".cmd" };
1017 #endif
1018 		size_t exti = Strlen(item.s);
1019 
1020 		if (exti > 4) {
1021 		    char *ext = short2str(&item.s[exti -= 4]);
1022 		    size_t i;
1023 
1024 		    for (i = 0; i < sizeof(rext) / sizeof(rext[0]); i++)
1025 			if (strcasecmp(ext, rext[i]) == 0) {
1026 			    item.len = exti;
1027 			    Strbuf_terminate(&item);
1028 			    break;
1029 			}
1030 		}
1031 	    }
1032 #endif /* _UWIN || __CYGWIN__ */
1033 	    exec_check = flags & TW_EXEC_CHK;
1034 	    dir_ok = flags & TW_DIR_OK;
1035 	    break;
1036 
1037 	default:
1038 	    break;
1039 	}
1040 
1041 	if (done) {
1042 	    done = FALSE;
1043 	    continue;
1044 	}
1045 
1046 	switch (command) {
1047 
1048 	case SPELL:		/* correct the spelling of the last bit */
1049 	    if (name_length == 0) {/* zero-length word can't be misspelled */
1050 		exp_name->len = 0; /* (not trying is important for ~) */
1051 		Strbuf_terminate(exp_name);
1052 		d = 0;
1053 		done = TRUE;
1054 		break;
1055 	    }
1056 	    if (gpat && !Gmatch(item.s, pat))
1057 		break;
1058 	    /*
1059 	     * Swapped the order of the spdist() arguments as suggested
1060 	     * by eeide@asylum.cs.utah.edu (Eric Eide)
1061 	     */
1062 	    nd = spdist(target, item.s); /* test the item against original */
1063 	    if (nd <= d && nd != 4) {
1064 		if (!(exec_check && !executable(exp_dir->s, item.s, dir_ok))) {
1065 		    exp_name->len = 0;
1066 		    Strbuf_append(exp_name, item.s);
1067 		    Strbuf_terminate(exp_name);
1068 		    d = nd;
1069 		    if (d == 0)	/* if found it exactly */
1070 			done = TRUE;
1071 		}
1072 	    }
1073 	    else if (nd == 4) {
1074 	        if (spdir(exp_name, exp_dir->s, item.s, target)) {
1075 		    if (exec_check &&
1076 			!executable(exp_dir->s, exp_name->s, dir_ok))
1077 			break;
1078 #ifdef notdef
1079 		    /*
1080 		     * We don't want to stop immediately, because
1081 		     * we might find an exact/better match later.
1082 		     */
1083 		    d = 0;
1084 		    done = TRUE;
1085 #endif
1086 		    d = 3;
1087 		}
1088 	    }
1089 	    break;
1090 
1091 	case LIST:
1092 	case RECOGNIZE:
1093 	case RECOGNIZE_ALL:
1094 	case RECOGNIZE_SCROLL:
1095 
1096 	    if ((vp = adrof(STRcomplete)) != NULL && vp->vec != NULL)
1097 		for (cp = vp->vec; *cp; cp++) {
1098 		    if (Strcmp(*cp, STREnhance) == 0)
1099 			enhanced = 2;
1100 		    else if (Strcmp(*cp, STRigncase) == 0)
1101 			igncase = 1;
1102 		    else if (Strcmp(*cp, STRenhance) == 0)
1103 			enhanced = 1;
1104 		}
1105 
1106 	    if (enhanced || igncase) {
1107 	        if (!is_prefixmatch(target, item.s, enhanced))
1108 		    break;
1109      	    } else {
1110 	        if (!is_prefix(target, item.s))
1111 		    break;
1112 	    }
1113 
1114 	    if (exec_check && !executable(exp_dir->s, item.s, dir_ok))
1115 		break;
1116 
1117 	    if (dir_check && !isadirectory(exp_dir->s, item.s))
1118 		break;
1119 
1120 	    if (text_check && isadirectory(exp_dir->s, item.s))
1121 		break;
1122 
1123 	    /*
1124 	     * Only pattern match directories if we're checking
1125 	     * for directories.
1126 	     */
1127 	    if (gpat && !Gmatch(item.s, pat) &&
1128 		(dir_check || !isadirectory(exp_dir->s, item.s)))
1129 		    break;
1130 
1131 	    /*
1132 	     * Remove duplicates in command listing and completion
1133              * AFEB added code for TW_LOGNAME and TW_USER cases
1134 	     */
1135 	    if (looking == TW_COMMAND || looking == TW_LOGNAME
1136 		|| looking == TW_USER || command == LIST) {
1137 		buf.len = 0;
1138 		Strbuf_append(&buf, item.s);
1139 		switch (looking) {
1140 		case TW_COMMAND:
1141 		    if (!(dir_ok && exec_check))
1142 			break;
1143 		    if (filetype(exp_dir->s, item.s) == '/')
1144 			Strbuf_append1(&buf, '/');
1145 		    break;
1146 
1147 		case TW_FILE:
1148 		case TW_DIRECTORY:
1149 		    Strbuf_append1(&buf, filetype(exp_dir->s, item.s));
1150 		    break;
1151 
1152 		default:
1153 		    break;
1154 		}
1155 		Strbuf_terminate(&buf);
1156 		if ((looking == TW_COMMAND || looking == TW_USER
1157                      || looking == TW_LOGNAME) && tw_item_find(buf.s))
1158 		    break;
1159 		else {
1160 		    /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
1161 		    tw_item_add(&buf);
1162 		    if (command == LIST)
1163 			numitems++;
1164 		}
1165 	    }
1166 
1167 	    if (command == RECOGNIZE || command == RECOGNIZE_ALL ||
1168 		command == RECOGNIZE_SCROLL) {
1169 		if (ignoring && ignored(item.s)) {
1170 		    nignored++;
1171 		    break;
1172 		}
1173 		else if (command == RECOGNIZE_SCROLL) {
1174 		    add_scroll_tab(item.s);
1175 		    cnt++;
1176 		}
1177 
1178 		if (match_unique_match || is_set(STRrecexact)) {
1179 		    if (StrQcmp(target, item.s) == 0) {	/* EXACT match */
1180 			exp_name->len = 0;
1181 			Strbuf_append(exp_name, item.s);
1182 			Strbuf_terminate(exp_name);
1183 			numitems = 1;	/* fake into expanding */
1184 			non_unique_match = TRUE;
1185 			done = TRUE;
1186 			break;
1187 		    }
1188 		}
1189 		if (recognize(exp_name, item.s, name_length, ++numitems,
1190 		    enhanced, igncase))
1191 		    if (command != RECOGNIZE_SCROLL)
1192 			done = TRUE;
1193 		if (enhanced && exp_name->len < name_length) {
1194 		    exp_name->len = 0;
1195 		    Strbuf_append(exp_name, target);
1196 		    Strbuf_terminate(exp_name);
1197 		}
1198 	    }
1199 	    break;
1200 
1201 	default:
1202 	    break;
1203 	}
1204 #ifdef TDEBUG
1205 	xprintf("done item = %S\n", item.s);
1206 #endif
1207     }
1208     cleanup_until(&item);
1209 
1210     if (command == RECOGNIZE_SCROLL) {
1211 	if ((cnt <= curchoice) || (curchoice == -1)) {
1212 	    curchoice = -1;
1213 	    nignored = 0;
1214 	    numitems = 0;
1215 	} else if (numitems > 1) {
1216 	    if (curchoice < -1)
1217 		curchoice = cnt - 1;
1218 	    choose_scroll_tab(exp_name, cnt);
1219 	    numitems = 1;
1220 	}
1221     }
1222     free_scroll_tab();
1223 
1224     if (command == SPELL)
1225 	return d;
1226     else {
1227 	if (ignoring && numitems == 0 && nignored > 0)
1228 	    return -nignored;
1229 	else
1230 	    return numitems;
1231     }
1232 }
1233 
1234 
1235 /* tw_suffix():
1236  *	Find and return the appropriate suffix character
1237  */
1238 /*ARGSUSED*/
1239 static Char
1240 tw_suffix(int looking, struct Strbuf *word, const Char *exp_dir, Char *exp_name)
1241 {
1242     Char *ptr;
1243     Char *dol;
1244     struct varent *vp;
1245 
1246     (void) strip(exp_name);
1247 
1248     switch (looking) {
1249 
1250     case TW_LOGNAME:
1251 	return '/';
1252 
1253     case TW_VARIABLE:
1254 	/*
1255 	 * Don't consider array variables or empty variables
1256 	 */
1257 	if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) {
1258 	    if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' ||
1259 		vp->vec[1] != NULL)
1260 		return ' ';
1261 	}
1262 	else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0')
1263 	    return ' ';
1264 
1265 	if ((dol = Strrchr(word->s, '$')) != 0 &&
1266 	    dol[1] == '{' && Strchr(dol, '}') == NULL)
1267 	  return '}';
1268 
1269 	return isadirectory(exp_dir, ptr) ? '/' : ' ';
1270 
1271 
1272     case TW_DIRECTORY:
1273 	return '/';
1274 
1275     case TW_COMMAND:
1276     case TW_FILE:
1277 	return isadirectory(exp_dir, exp_name) ? '/' : ' ';
1278 
1279     case TW_ALIAS:
1280     case TW_VARLIST:
1281     case TW_WORDLIST:
1282     case TW_SHELLVAR:
1283     case TW_ENVVAR:
1284     case TW_USER:
1285     case TW_BINDING:
1286     case TW_LIMIT:
1287     case TW_SIGNAL:
1288     case TW_JOB:
1289     case TW_COMPLETION:
1290     case TW_TEXT:
1291     case TW_GRPNAME:
1292 	return ' ';
1293 
1294     default:
1295 	return '\0';
1296     }
1297 } /* end tw_suffix */
1298 
1299 
1300 /* tw_fixword():
1301  *	Repair a word after a spalling or a recognizwe
1302  */
1303 static void
1304 tw_fixword(int looking, struct Strbuf *word, Char *dir, Char *exp_name)
1305 {
1306     Char *ptr;
1307 
1308     switch (looking) {
1309     case TW_LOGNAME:
1310 	word->len = 0;
1311 	Strbuf_append1(word, '~');
1312 	break;
1313 
1314     case TW_VARIABLE:
1315 	if ((ptr = Strrchr(word->s, '$')) != NULL) {
1316 	    if (ptr[1] == '{') ptr++;
1317 	    word->len = ptr + 1 - word->s; /* Delete after the dollar */
1318 	} else
1319 	    word->len = 0;
1320 	break;
1321 
1322     case TW_DIRECTORY:
1323     case TW_FILE:
1324     case TW_TEXT:
1325 	word->len = 0;
1326 	Strbuf_append(word, dir);		/* put back dir part */
1327 	break;
1328 
1329     default:
1330 	word->len = 0;
1331 	break;
1332     }
1333 
1334     (void) quote(exp_name);
1335     Strbuf_append(word, exp_name);		/* add extended name */
1336     Strbuf_terminate(word);
1337 } /* end tw_fixword */
1338 
1339 
1340 /* tw_collect():
1341  *	Collect items. Return -1 in case we were interrupted or
1342  *	the return value of tw_collect
1343  *	This is really a wrapper for tw_collect_items, serving two
1344  *	purposes:
1345  *		1. Handles interrupt cleanups.
1346  *		2. Retries if we had no matches, but there were ignored matches
1347  */
1348 static int
1349 tw_collect(COMMAND command, int looking, struct Strbuf *exp_dir,
1350 	   struct Strbuf *exp_name, Char *target, Char *pat, int flags,
1351 	   DIR *dir_fd)
1352 {
1353     volatile int ni;
1354     jmp_buf_t osetexit;
1355 
1356 #ifdef TDEBUG
1357     xprintf("target = %S\n", target);
1358 #endif
1359     ni = 0;
1360     getexit(osetexit);
1361     for (;;) {
1362 	volatile size_t omark;
1363 
1364 	(*tw_start_entry[looking])(dir_fd, pat);
1365 	InsideCompletion = 1;
1366 	if (setexit()) {
1367 	    cleanup_pop_mark(omark);
1368 	    resexit(osetexit);
1369 	    /* interrupted, clean up */
1370 	    haderr = 0;
1371 	    ni = -1; /* flag error */
1372 	    break;
1373 	}
1374 	omark = cleanup_push_mark();
1375 	ni = tw_collect_items(command, looking, exp_dir, exp_name, target, pat,
1376 			      ni >= 0 ? flags : flags & ~TW_IGN_OK);
1377 	cleanup_pop_mark(omark);
1378 	resexit(osetexit);
1379         if (ni >= 0)
1380 	    break;
1381     }
1382     InsideCompletion = 0;
1383 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
1384     /* Compiler bug? (from PWP) */
1385     if ((looking == TW_LOGNAME) || (looking == TW_USER))
1386 	tw_logname_end();
1387     else if (looking == TW_GRPNAME)
1388 	tw_grpname_end();
1389     else
1390 	tw_dir_end();
1391 #else /* !(SOLARIS2 && i386 && !__GNUC__) */
1392     (*tw_end_entry[looking])();
1393 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */
1394     return(ni);
1395 } /* end tw_collect */
1396 
1397 
1398 /* tw_list_items():
1399  *	List the items that were found
1400  *
1401  *	NOTE instead of looking at numerical vars listmax and listmaxrows
1402  *	we can look at numerical var listmax, and have a string value
1403  *	listmaxtype (or similar) than can have values 'items' and 'rows'
1404  *	(by default interpreted as 'items', for backwards compatibility)
1405  */
1406 static void
1407 tw_list_items(int looking, int numitems, int list_max)
1408 {
1409     Char *ptr;
1410     int max_items = 0;
1411     int max_rows = 0;
1412 
1413     if (numitems == 0)
1414 	return;
1415 
1416     if ((ptr = varval(STRlistmax)) != STRNULL) {
1417 	while (*ptr) {
1418 	    if (!Isdigit(*ptr)) {
1419 		max_items = 0;
1420 		break;
1421 	    }
1422 	    max_items = max_items * 10 + *ptr++ - '0';
1423 	}
1424 	if ((max_items > 0) && (numitems > max_items) && list_max)
1425 	    max_items = numitems;
1426 	else
1427 	    max_items = 0;
1428     }
1429 
1430     if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) {
1431 	int rows;
1432 
1433 	while (*ptr) {
1434 	    if (!Isdigit(*ptr)) {
1435 		max_rows = 0;
1436 		break;
1437 	    }
1438 	    max_rows = max_rows * 10 + *ptr++ - '0';
1439 	}
1440 	if (max_rows != 0 && looking != TW_JOB)
1441 	    rows = find_rows(tw_item_get(), numitems, TRUE);
1442 	else
1443 	    rows = numitems; /* underestimate for lines wider than the termH */
1444 	if ((max_rows > 0) && (rows > max_rows) && list_max)
1445 	    max_rows = rows;
1446 	else
1447 	    max_rows = 0;
1448     }
1449 
1450 
1451     if (max_items || max_rows) {
1452 	char    	 tc, *sname;
1453 	const char	*name;
1454 	int maxs;
1455 
1456 	if (max_items) {
1457 	    name = CGETS(30, 5, "items");
1458 	    maxs = max_items;
1459 	}
1460 	else {
1461 	    name = CGETS(30, 6, "rows");
1462 	    maxs = max_rows;
1463 	}
1464 
1465 	sname = strsave(name);
1466 	cleanup_push(sname, xfree);
1467 	xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "),
1468 		maxs, sname);
1469 	cleanup_until(sname);
1470 	flush();
1471 	/* We should be in Rawmode here, so no \n to catch */
1472 	(void) xread(SHIN, &tc, 1);
1473 	xprintf("%c\r\n", tc);	/* echo the char, do a newline */
1474 	/*
1475 	 * Perhaps we should use the yesexpr from the
1476 	 * actual locale
1477 	 */
1478 	if (strchr(CGETS(30, 13, "Yy"), tc) == NULL)
1479 	    return;
1480     }
1481 
1482     if (looking != TW_SIGNAL)
1483 	qsort(tw_item_get(), numitems, sizeof(Char *), fcompare);
1484     if (looking != TW_JOB)
1485 	print_by_column(STRNULL, tw_item_get(), numitems, TRUE);
1486     else {
1487 	/*
1488 	 * print one item on every line because jobs can have spaces
1489 	 * and it is confusing.
1490 	 */
1491 	int i;
1492 	Char **w = tw_item_get();
1493 
1494 	for (i = 0; i < numitems; i++) {
1495 	    xprintf("%S", w[i]);
1496 	    if (Tty_raw_mode)
1497 		xputchar('\r');
1498 	    xputchar('\n');
1499 	}
1500     }
1501 } /* end tw_list_items */
1502 
1503 
1504 /* t_search():
1505  *	Perform a RECOGNIZE, LIST or SPELL command on string "word".
1506  *
1507  *	Return value:
1508  *		>= 0:   SPELL command: "distance" (see spdist())
1509  *		                other: No. of items found
1510  *  		 < 0:   Error (message or beep is output)
1511  */
1512 /*ARGSUSED*/
1513 int
1514 t_search(struct Strbuf *word, COMMAND command, int looking, int list_max,
1515 	 Char *pat, eChar suf)
1516 {
1517     int     numitems,			/* Number of items matched */
1518 	    flags = 0,			/* search flags */
1519 	    gpat = pat[0] != '\0',	/* Glob pattern search */
1520 	    res;			/* Return value */
1521     struct Strbuf exp_dir = Strbuf_INIT;/* dir after ~ expansion */
1522     struct Strbuf dir = Strbuf_INIT;	/* /x/y/z/ part in /x/y/z/f */
1523     struct Strbuf exp_name = Strbuf_INIT;/* the recognized (extended) */
1524     Char   *name,			/* f part in /d/d/d/f name */
1525            *target;			/* Target to expand/correct/list */
1526     DIR    *dir_fd = NULL;
1527 
1528     /*
1529      * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
1530      * dump core when interrupted
1531      */
1532     tw_item_free();
1533 
1534     non_unique_match = FALSE;	/* See the recexact code below */
1535 
1536     extract_dir_and_name(word->s, &dir, &name);
1537     cleanup_push(&dir, Strbuf_cleanup);
1538     cleanup_push(&name, xfree_indirect);
1539 
1540     /*
1541      *  SPECIAL HARDCODED COMPLETIONS:
1542      *    foo$variable                -> TW_VARIABLE
1543      *    ~user                       -> TW_LOGNAME
1544      *
1545      */
1546     if ((*word->s == '~') && (Strchr(word->s, '/') == NULL)) {
1547 	looking = TW_LOGNAME;
1548 	target = name;
1549 	gpat = 0;	/* Override pattern mechanism */
1550     }
1551     else if ((target = Strrchr(name, '$')) != 0 &&
1552 	     (target[1] != '{' || Strchr(target, '}') == NULL) &&
1553 	     (Strchr(name, '/') == NULL)) {
1554 	target++;
1555 	if (target[0] == '{') target++;
1556 	looking = TW_VARIABLE;
1557 	gpat = 0;	/* Override pattern mechanism */
1558     }
1559     else
1560 	target = name;
1561 
1562     /*
1563      * Try to figure out what we should be looking for
1564      */
1565     if (looking & TW_PATH) {
1566 	gpat = 0;	/* pattern holds the pathname to be used */
1567 	Strbuf_append(&exp_dir, pat);
1568 	if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/')
1569 	    Strbuf_append1(&exp_dir, '/');
1570 	Strbuf_append(&exp_dir, dir.s);
1571     }
1572     Strbuf_terminate(&exp_dir);
1573     cleanup_push(&exp_dir, Strbuf_cleanup);
1574 
1575     switch (looking & ~TW_PATH) {
1576     case TW_NONE:
1577 	res = -1;
1578 	goto err_dir;
1579 
1580     case TW_ZERO:
1581 	looking = TW_FILE;
1582 	break;
1583 
1584     case TW_COMMAND:
1585 	if (Strchr(word->s, '/') || (looking & TW_PATH)) {
1586 	    looking = TW_FILE;
1587 	    flags |= TW_EXEC_CHK;
1588 	    flags |= TW_DIR_OK;
1589 	}
1590 #ifdef notdef
1591 	/* PWP: don't even bother when doing ALL of the commands */
1592 	if (looking == TW_COMMAND && word->len == 0) {
1593 	    res = -1;
1594 	    goto err_dir;
1595 	}
1596 #endif
1597 	break;
1598 
1599 
1600     case TW_VARLIST:
1601     case TW_WORDLIST:
1602 	gpat = 0;	/* pattern holds the name of the variable */
1603 	break;
1604 
1605     case TW_EXPLAIN:
1606 	if (command == LIST && pat != NULL) {
1607 	    xprintf("%S", pat);
1608 	    if (Tty_raw_mode)
1609 		xputchar('\r');
1610 	    xputchar('\n');
1611 	}
1612 	res = 2;
1613 	goto err_dir;
1614 
1615     default:
1616 	break;
1617     }
1618 
1619     /*
1620      * let fignore work only when we are not using a pattern
1621      */
1622     flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK;
1623 
1624 #ifdef TDEBUG
1625     xprintf(CGETS(30, 8, "looking = %d\n"), looking);
1626 #endif
1627 
1628     switch (looking) {
1629 	Char *user_name;
1630 
1631     case TW_ALIAS:
1632     case TW_SHELLVAR:
1633     case TW_ENVVAR:
1634     case TW_BINDING:
1635     case TW_LIMIT:
1636     case TW_SIGNAL:
1637     case TW_JOB:
1638     case TW_COMPLETION:
1639     case TW_GRPNAME:
1640 	break;
1641 
1642 
1643     case TW_VARIABLE:
1644 	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1645 	    goto err_dir;
1646 	break;
1647 
1648     case TW_DIRECTORY:
1649 	flags |= TW_DIR_CHK;
1650 
1651 #ifdef notyet
1652 	/*
1653 	 * This is supposed to expand the directory stack.
1654 	 * Problems:
1655 	 * 1. Slow
1656 	 * 2. directories with the same name
1657 	 */
1658 	flags |= TW_DIR_OK;
1659 #endif
1660 #ifdef notyet
1661 	/*
1662 	 * Supposed to do delayed expansion, but it is inconsistent
1663 	 * from a user-interface point of view, since it does not
1664 	 * immediately obey addsuffix
1665 	 */
1666 	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1667 	    goto err_dir;
1668 	if (isadirectory(exp_dir.s, name)) {
1669 	    if (exp_dir.len != 0 || name[0] != '\0') {
1670 		Strbuf_append(&dir, name);
1671 		if (dir.s[dir.len - 1] != '/')
1672 		    Strbuf_append1(&dir, '/');
1673 		Strbuf_terminate(&dir);
1674 		if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1675 		    goto err_dir;
1676 		if (word->len != 0 && word->s[word->len - 1] != '/') {
1677 		    Strbuf_append1(word, '/');
1678 		    Strbuf_terminate(word);
1679 		}
1680 		name[0] = '\0';
1681 	    }
1682 	}
1683 #endif
1684 	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1685 	    goto err_dir;
1686 	break;
1687 
1688     case TW_TEXT:
1689 	flags |= TW_TEXT_CHK;
1690 	/*FALLTHROUGH*/
1691     case TW_FILE:
1692 	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
1693 	    goto err_dir;
1694 	break;
1695 
1696     case TW_PATH | TW_TEXT:
1697     case TW_PATH | TW_FILE:
1698     case TW_PATH | TW_DIRECTORY:
1699     case TW_PATH | TW_COMMAND:
1700 	if ((dir_fd = opendir(short2str(exp_dir.s))) == NULL) {
1701  	    if (command == RECOGNIZE)
1702  		xprintf("\n");
1703  	    xprintf("%S: %s", exp_dir.s, strerror(errno));
1704  	    if (command != RECOGNIZE)
1705  		xprintf("\n");
1706  	    NeedsRedraw = 1;
1707 	    res = -1;
1708 	    goto err_dir;
1709 	}
1710 	if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') {
1711 	    Strbuf_append1(&exp_dir, '/');
1712 	    Strbuf_terminate(&exp_dir);
1713 	}
1714 
1715 	looking &= ~TW_PATH;
1716 
1717 	switch (looking) {
1718 	case TW_TEXT:
1719 	    flags |= TW_TEXT_CHK;
1720 	    break;
1721 
1722 	case TW_FILE:
1723 	    break;
1724 
1725 	case TW_DIRECTORY:
1726 	    flags |= TW_DIR_CHK;
1727 	    break;
1728 
1729 	case TW_COMMAND:
1730 	    xfree(name);
1731 	    target = name = Strsave(word->s);	/* so it can match things */
1732 	    break;
1733 
1734 	default:
1735 	    abort();	/* Cannot happen */
1736 	    break;
1737 	}
1738 	break;
1739 
1740     case TW_LOGNAME:
1741 	user_name = word->s + 1;
1742 	goto do_user;
1743 
1744 	/*FALLTHROUGH*/
1745     case TW_USER:
1746 	user_name = word->s;
1747     do_user:
1748 	/*
1749 	 * Check if the spelling was already correct
1750 	 * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
1751 	 */
1752 	if (command == SPELL && xgetpwnam(short2str(user_name)) != NULL) {
1753 #ifdef YPBUGS
1754 	    fix_yp_bugs();
1755 #endif /* YPBUGS */
1756 	    res = 0;
1757 	    goto err_dir;
1758 	}
1759 	xfree(name);
1760 	target = name = Strsave(user_name);
1761 	break;
1762 
1763     case TW_COMMAND:
1764     case TW_VARLIST:
1765     case TW_WORDLIST:
1766 	target = name = Strsave(word->s);	/* so it can match things */
1767 	break;
1768 
1769     default:
1770 	xprintf(CGETS(30, 9,
1771 		"\n%s internal error: I don't know what I'm looking for!\n"),
1772 		progname);
1773 	NeedsRedraw = 1;
1774 	res = -1;
1775 	goto err_dir;
1776     }
1777 
1778     cleanup_push(&exp_name, Strbuf_cleanup);
1779     numitems = tw_collect(command, looking, &exp_dir, &exp_name, target, pat,
1780 			  flags, dir_fd);
1781     if (numitems == -1)
1782 	goto end;
1783 
1784     switch (command) {
1785     case RECOGNIZE:
1786     case RECOGNIZE_ALL:
1787     case RECOGNIZE_SCROLL:
1788 	if (numitems <= 0)
1789 	    break;
1790 
1791 	Strbuf_terminate(&exp_name);
1792 	tw_fixword(looking, word, dir.s, exp_name.s);
1793 
1794 	if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) {
1795 	    switch (suf) {
1796 	    case 0: 	/* Automatic suffix */
1797 		Strbuf_append1(word,
1798 			       tw_suffix(looking, word, exp_dir.s, exp_name.s));
1799 		break;
1800 
1801 	    case CHAR_ERR:	/* No suffix */
1802 		break;
1803 
1804 	    default:	/* completion specified suffix */
1805 		Strbuf_append1(word, suf);
1806 		break;
1807 	    }
1808 	    Strbuf_terminate(word);
1809 	}
1810 	break;
1811 
1812     case LIST:
1813 	tw_list_items(looking, numitems, list_max);
1814 	tw_item_free();
1815 	break;
1816 
1817     case SPELL:
1818 	Strbuf_terminate(&exp_name);
1819 	tw_fixword(looking, word, dir.s, exp_name.s);
1820 	break;
1821 
1822     default:
1823 	xprintf("Bad tw_command\n");
1824 	numitems = 0;
1825     }
1826  end:
1827     res = numitems;
1828  err_dir:
1829     cleanup_until(&dir);
1830     return res;
1831 } /* end t_search */
1832 
1833 
1834 /* extract_dir_and_name():
1835  * 	parse full path in file into 2 parts: directory and file names
1836  * 	Should leave final slash (/) at end of dir.
1837  */
1838 static void
1839 extract_dir_and_name(const Char *path, struct Strbuf *dir, Char **name)
1840 {
1841     Char *p;
1842 
1843     p = Strrchr(path, '/');
1844 #ifdef WINNT_NATIVE
1845     if (p == NULL)
1846 	p = Strrchr(path, ':');
1847 #endif /* WINNT_NATIVE */
1848     if (p == NULL)
1849 	*name = Strsave(path);
1850     else {
1851 	p++;
1852 	*name = Strsave(p);
1853 	Strbuf_appendn(dir, path, p - path);
1854     }
1855     Strbuf_terminate(dir);
1856 } /* end extract_dir_and_name */
1857 
1858 
1859 /* dollar():
1860  * 	expand "/$old1/$old2/old3/"
1861  * 	to "/value_of_old1/value_of_old2/old3/"
1862  */
1863 Char *
1864 dollar(const Char *old)
1865 {
1866     struct Strbuf buf = Strbuf_INIT;
1867 
1868     while (*old) {
1869 	if (*old != '$')
1870 	    Strbuf_append1(&buf, *old++);
1871 	else {
1872 	    if (expdollar(&buf, &old, QUOTE) == 0) {
1873 		xfree(buf.s);
1874 		return NULL;
1875 	    }
1876 	}
1877     }
1878     return Strbuf_finish(&buf);
1879 } /* end dollar */
1880 
1881 
1882 /* tilde():
1883  * 	expand ~person/foo to home_directory_of_person/foo
1884  *	or =<stack-entry> to <dir in stack entry>
1885  */
1886 static int
1887 tilde(struct Strbuf *new, Char *old)
1888 {
1889     Char *o, *p;
1890 
1891     new->len = 0;
1892     switch (old[0]) {
1893     case '~': {
1894 	Char *name, *home;
1895 
1896 	old++;
1897 	for (o = old; *o && *o != '/'; o++)
1898 	    continue;
1899 	name = Strnsave(old, o - old);
1900 	home = gethdir(name);
1901 	xfree(name);
1902 	if (home == NULL)
1903 	    goto err;
1904 	Strbuf_append(new, home);
1905 	xfree(home);
1906 	/* If the home directory expands to "/", we do
1907 	 * not want to create "//" by appending a slash from o.
1908 	 */
1909 	if (new->s[0] == '/' && new->len == 1 && *o == '/')
1910 	    ++o;
1911 	Strbuf_append(new, o);
1912 	break;
1913     }
1914 
1915     case '=':
1916 	if ((p = globequal(old)) == NULL)
1917 	    goto err;
1918 	if (p != old) {
1919 	    Strbuf_append(new, p);
1920 	    xfree(p);
1921 	    break;
1922 	}
1923 	/*FALLTHROUGH*/
1924 
1925     default:
1926 	Strbuf_append(new, old);
1927 	break;
1928     }
1929     Strbuf_terminate(new);
1930     return 0;
1931 
1932  err:
1933     Strbuf_terminate(new);
1934     return -1;
1935 } /* end tilde */
1936 
1937 
1938 /* expand_dir():
1939  *	Open the directory given, expanding ~user and $var
1940  *	Optionally normalize the path given
1941  */
1942 static int
1943 expand_dir(const Char *dir, struct Strbuf *edir, DIR **dfd, COMMAND cmd)
1944 {
1945     Char   *nd = NULL;
1946     Char *tdir;
1947 
1948     tdir = dollar(dir);
1949     cleanup_push(tdir, xfree);
1950     if (tdir == NULL ||
1951 	(tilde(edir, tdir) != 0) ||
1952 	!(nd = dnormalize(edir->len ? edir->s : STRdot,
1953 			  symlinks == SYM_IGNORE || symlinks == SYM_EXPAND)) ||
1954 	((*dfd = opendir(short2str(nd))) == NULL)) {
1955 	xfree(nd);
1956 	if (cmd == SPELL || SearchNoDirErr) {
1957 	    cleanup_until(tdir);
1958 	    return (-2);
1959 	}
1960 	/*
1961 	 * From: Amos Shapira <amoss@cs.huji.ac.il>
1962 	 * Print a better message when completion fails
1963 	 */
1964 	xprintf("\n%S %s\n", edir->len ? edir->s : (tdir ? tdir : dir),
1965 		(errno == ENOTDIR ? CGETS(30, 10, "not a directory") :
1966 		(errno == ENOENT ? CGETS(30, 11, "not found") :
1967 		 CGETS(30, 12, "unreadable"))));
1968 	NeedsRedraw = 1;
1969 	cleanup_until(tdir);
1970 	return (-1);
1971     }
1972     cleanup_until(tdir);
1973     if (nd) {
1974 	if (*dir != '\0') {
1975 	    int slash;
1976 
1977 	    /*
1978 	     * Copy and append a / if there was one
1979 	     */
1980 	    slash = edir->len != 0 && edir->s[edir->len - 1] == '/';
1981 	    edir->len = 0;
1982 	    Strbuf_append(edir, nd);
1983 	    if (slash != 0 && edir->s[edir->len - 1] != '/')
1984 		Strbuf_append1(edir, '/');
1985 	    Strbuf_terminate(edir);
1986 	}
1987 	xfree(nd);
1988     }
1989     return 0;
1990 } /* end expand_dir */
1991 
1992 
1993 /* nostat():
1994  *	Returns true if the directory should not be stat'd,
1995  *	false otherwise.
1996  *	This way, things won't grind to a halt when you complete in /afs
1997  *	or very large directories.
1998  */
1999 static int
2000 nostat(Char *dir)
2001 {
2002     struct varent *vp;
2003     Char **cp;
2004 
2005     if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL)
2006 	return FALSE;
2007     for (; *cp != NULL; cp++) {
2008 	if (Strcmp(*cp, STRstar) == 0)
2009 	    return TRUE;
2010 	if (Gmatch(dir, *cp))
2011 	    return TRUE;
2012     }
2013     return FALSE;
2014 } /* end nostat */
2015 
2016 
2017 /* filetype():
2018  *	Return a character that signifies a filetype
2019  *	symbology from 4.3 ls command.
2020  */
2021 static  Char
2022 filetype(Char *dir, Char *file)
2023 {
2024     if (dir) {
2025 	Char *path;
2026 	char   *ptr;
2027 	struct stat statb;
2028 
2029 	if (nostat(dir)) return(' ');
2030 
2031 	path = Strspl(dir, file);
2032 	ptr = short2str(path);
2033 	xfree(path);
2034 
2035 	if (lstat(ptr, &statb) != -1) {
2036 #ifdef S_ISLNK
2037 	    if (S_ISLNK(statb.st_mode)) {	/* Symbolic link */
2038 		if (adrof(STRlistlinks)) {
2039 		    if (stat(ptr, &statb) == -1)
2040 			return ('&');
2041 		    else if (S_ISDIR(statb.st_mode))
2042 			return ('>');
2043 		    else
2044 			return ('@');
2045 		}
2046 		else
2047 		    return ('@');
2048 	    }
2049 #endif
2050 #ifdef S_ISSOCK
2051 	    if (S_ISSOCK(statb.st_mode))	/* Socket */
2052 		return ('=');
2053 #endif
2054 #ifdef S_ISFIFO
2055 	    if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
2056 		return ('|');
2057 #endif
2058 #ifdef S_ISHIDDEN
2059 	    if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
2060 		return ('+');
2061 #endif
2062 #ifdef S_ISCDF
2063 	    {
2064 		struct stat hpstatb;
2065 		char *p2;
2066 
2067 		p2 = strspl(ptr, "+");	/* Must append a '+' and re-stat(). */
2068 		if ((stat(p2, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode)) {
2069 		    xfree(p2);
2070 		    return ('+');	/* Context Dependent Files [hpux] */
2071 		}
2072 		xfree(p2);
2073 	    }
2074 #endif
2075 #ifdef S_ISNWK
2076 	    if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
2077 		return (':');
2078 #endif
2079 #ifdef S_ISCHR
2080 	    if (S_ISCHR(statb.st_mode))	/* char device */
2081 		return ('%');
2082 #endif
2083 #ifdef S_ISBLK
2084 	    if (S_ISBLK(statb.st_mode))	/* block device */
2085 		return ('#');
2086 #endif
2087 #ifdef S_ISDIR
2088 	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
2089 		return ('/');
2090 #endif
2091 	    if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
2092 		return ('*');
2093 	}
2094     }
2095     return (' ');
2096 } /* end filetype */
2097 
2098 
2099 /* isadirectory():
2100  *	Return trus if the file is a directory
2101  */
2102 static int
2103 isadirectory(const Char *dir, const Char *file)
2104      /* return 1 if dir/file is a directory */
2105      /* uses stat rather than lstat to get dest. */
2106 {
2107     if (dir) {
2108 	Char *path;
2109 	char *cpath;
2110 	struct stat statb;
2111 
2112 	path = Strspl(dir, file);
2113 	cpath = short2str(path);
2114 	xfree(path);
2115 	if (stat(cpath, &statb) >= 0) {	/* resolve through symlink */
2116 #ifdef S_ISSOCK
2117 	    if (S_ISSOCK(statb.st_mode))	/* Socket */
2118 		return 0;
2119 #endif
2120 #ifdef S_ISFIFO
2121 	    if (S_ISFIFO(statb.st_mode))	/* Named Pipe */
2122 		return 0;
2123 #endif
2124 	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
2125 		return 1;
2126 	}
2127     }
2128     return 0;
2129 } /* end isadirectory */
2130 
2131 
2132 
2133 /* find_rows():
2134  * 	Return how many rows needed to print sorted down columns
2135  */
2136 static int
2137 find_rows(Char *items[], int count, int no_file_suffix)
2138 {
2139     int i, columns, rows;
2140     unsigned int maxwidth = 0;
2141 
2142     for (i = 0; i < count; i++)	/* find widest string */
2143 	maxwidth = max(maxwidth, (unsigned int) Strlen(items[i]));
2144 
2145     maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
2146     columns = (TermH + 1) / maxwidth;	/* PWP: terminal size change */
2147     if (!columns)
2148 	columns = 1;
2149     rows = (count + (columns - 1)) / columns;
2150 
2151     return rows;
2152 } /* end rows_needed_by_print_by_column */
2153 
2154 
2155 /* print_by_column():
2156  * 	Print sorted down columns or across columns when the first
2157  *	word of $listflags shell variable contains 'x'.
2158  *
2159  */
2160 void
2161 print_by_column(Char *dir, Char *items[], int count, int no_file_suffix)
2162 {
2163     int i, r, c, columns, rows;
2164     size_t w;
2165     unsigned int wx, maxwidth = 0;
2166     Char *val;
2167     int across;
2168 
2169     lbuffed = 0;		/* turn off line buffering */
2170 
2171 
2172     across = ((val = varval(STRlistflags)) != STRNULL) &&
2173 	     (Strchr(val, 'x') != NULL);
2174 
2175     for (i = 0; i < count; i++)	{ /* find widest string */
2176 	maxwidth = max(maxwidth, (unsigned int) NLSStringWidth(items[i]));
2177     }
2178 
2179     maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
2180     columns = TermH / maxwidth;		/* PWP: terminal size change */
2181     if (!columns || !isatty(didfds ? 1 : SHOUT))
2182 	columns = 1;
2183     rows = (count + (columns - 1)) / columns;
2184 
2185     i = -1;
2186     for (r = 0; r < rows; r++) {
2187 	for (c = 0; c < columns; c++) {
2188 	    i = across ? (i + 1) : (c * rows + r);
2189 
2190 	    if (i < count) {
2191 		wx = 0;
2192 		w = Strlen(items[i]);
2193 
2194 #ifdef COLOR_LS_F
2195 		if (no_file_suffix) {
2196 		    /* Print the command name */
2197 		    Char f = items[i][w - 1];
2198 		    items[i][w - 1] = 0;
2199 		    print_with_color(items[i], w - 1, f);
2200 		    items[i][w - 1] = f;
2201 		}
2202 		else {
2203 		    /* Print filename followed by '/' or '*' or ' ' */
2204 		    print_with_color(items[i], w, filetype(dir, items[i]));
2205 		    wx++;
2206 		}
2207 #else /* ifndef COLOR_LS_F */
2208 		if (no_file_suffix) {
2209 		    /* Print the command name */
2210 		    xprintf("%S", items[i]);
2211 		}
2212 		else {
2213 		    /* Print filename followed by '/' or '*' or ' ' */
2214 		    xprintf("%-S%c", items[i], filetype(dir, items[i]));
2215 		    wx++;
2216 		}
2217 #endif /* COLOR_LS_F */
2218 
2219 		if (c < (columns - 1)) {	/* Not last column? */
2220 		    w = NLSStringWidth(items[i]) + wx;
2221 		    for (; w < maxwidth; w++)
2222 			xputchar(' ');
2223 		}
2224 	    }
2225 	    else if (across)
2226 		break;
2227 	}
2228 	if (Tty_raw_mode)
2229 	    xputchar('\r');
2230 	xputchar('\n');
2231     }
2232 
2233     lbuffed = 1;		/* turn back on line buffering */
2234     flush();
2235 } /* end print_by_column */
2236 
2237 
2238 /* StrQcmp():
2239  *	Compare strings ignoring the quoting chars
2240  */
2241 int
2242 StrQcmp(const Char *str1, const Char *str2)
2243 {
2244     for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM);
2245 	 str1++, str2++)
2246 	continue;
2247     /*
2248      * The following case analysis is necessary so that characters which look
2249      * negative collate low against normal characters but high against the
2250      * end-of-string NUL.
2251      */
2252     if (*str1 == '\0' && *str2 == '\0')
2253 	return (0);
2254     else if (*str1 == '\0')
2255 	return (-1);
2256     else if (*str2 == '\0')
2257 	return (1);
2258     else
2259 	return ((*str1 & TRIM) - (*str2 & TRIM));
2260 } /* end StrQcmp */
2261 
2262 
2263 /* fcompare():
2264  * 	Comparison routine for qsort, (Char **, Char **)
2265  */
2266 int
2267 fcompare(const void *xfile1, const void *xfile2)
2268 {
2269     const Char *const *file1 = xfile1, *const *file2 = xfile2;
2270 
2271     return collate(*file1, *file2);
2272 } /* end fcompare */
2273 
2274 
2275 /* catn():
2276  *	Concatenate src onto tail of des.
2277  *	Des is a string whose maximum length is count.
2278  *	Always null terminate.
2279  */
2280 void
2281 catn(Char *des, const Char *src, int count)
2282 {
2283     while (*des && --count > 0)
2284 	des++;
2285     while (--count > 0)
2286 	if ((*des++ = *src++) == 0)
2287 	    return;
2288     *des = '\0';
2289 } /* end catn */
2290 
2291 
2292 /* copyn():
2293  *	 like strncpy but always leave room for trailing \0
2294  *	 and always null terminate.
2295  */
2296 void
2297 copyn(Char *des, const Char *src, size_t count)
2298 {
2299     while (--count != 0)
2300 	if ((*des++ = *src++) == 0)
2301 	    return;
2302     *des = '\0';
2303 } /* end copyn */
2304 
2305 
2306 /* tgetenv():
2307  *	like it's normal string counter-part
2308  */
2309 Char *
2310 tgetenv(Char *str)
2311 {
2312     Char  **var;
2313     size_t  len;
2314     int     res;
2315 
2316     len = Strlen(str);
2317     /* Search the STR_environ for the entry matching str. */
2318     for (var = STR_environ; var != NULL && *var != NULL; var++)
2319 	if (Strlen(*var) >= len && (*var)[len] == '=') {
2320 	  /* Temporarily terminate the string so we can copy the variable
2321 	     name. */
2322 	    (*var)[len] = '\0';
2323 	    res = StrQcmp(*var, str);
2324 	    /* Restore the '=' and return a pointer to the value of the
2325 	       environment variable. */
2326 	    (*var)[len] = '=';
2327 	    if (res == 0)
2328 		return (&((*var)[len + 1]));
2329 	}
2330     return (NULL);
2331 } /* end tgetenv */
2332 
2333 
2334 struct scroll_tab_list *scroll_tab = 0;
2335 
2336 static void
2337 add_scroll_tab(Char *item)
2338 {
2339     struct scroll_tab_list *new_scroll;
2340 
2341     new_scroll = xmalloc(sizeof(struct scroll_tab_list));
2342     new_scroll->element = Strsave(item);
2343     new_scroll->next = scroll_tab;
2344     scroll_tab = new_scroll;
2345 }
2346 
2347 static void
2348 choose_scroll_tab(struct Strbuf *exp_name, int cnt)
2349 {
2350     struct scroll_tab_list *loop;
2351     int tmp = cnt;
2352     Char **ptr;
2353 
2354     ptr = xmalloc(sizeof(Char *) * cnt);
2355     cleanup_push(ptr, xfree);
2356 
2357     for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next)
2358 	ptr[--tmp] = loop->element;
2359 
2360     qsort(ptr, cnt, sizeof(Char *), fcompare);
2361 
2362     exp_name->len = 0;
2363     Strbuf_append(exp_name, ptr[curchoice]);
2364     Strbuf_terminate(exp_name);
2365     cleanup_until(ptr);
2366 }
2367 
2368 static void
2369 free_scroll_tab(void)
2370 {
2371     struct scroll_tab_list *loop;
2372 
2373     while(scroll_tab) {
2374 	loop = scroll_tab;
2375 	scroll_tab = scroll_tab->next;
2376 	xfree(loop->element);
2377 	xfree(loop);
2378     }
2379 }
2380