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