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