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