/* $Header: /home/hyperion/mu/christos/src/sys/tcsh-6.00/RCS/tw.parse.c,v 3.0 1991/07/04 21:49:28 christos Exp $ */
/*
 * tw.parse.c: Everyone has taken a shot in this futile effort to
 *	       lexically analyze a csh line... Well we cannot good
 *	       a job as good as sh.lex.c; but we try. Amazing that
 *	       it works considering how many hands have touched this code
 */
/*-
 * Copyright (c) 1980, 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "config.h"
#if !defined(lint) && !defined(pdp11)
static char *rcsid() 
    { return "$Id: tw.parse.c,v 3.0 1991/07/04 21:49:28 christos Exp $"; }
#endif

#include "sh.h"
#include "tw.h"
#include "ed.h"

/* #define TENEDEBUG */

/* true if the path has relative elements */
static bool relatives_in_path;

static int maxitems = 0;
Char  **com_list = (Char **) NULL;	/* the pre-digested list of commands
					 * for speed and general usefullness */
int     numcommands = 0;
int     have_sorted = 0;

/* Set to TRUE if recexact is set and an exact match is found
 * along with other, longer, matches.
 */
int non_unique_match = FALSE;

#ifdef notdef
int     dirctr = 0;		/* -1 0 1 2 ... */

#endif
Char    dirflag[5];		/* ' nn\0' - dir #s -  . 1 2 ... */

static bool SearchNoDirErr = 0;	/* t_search returns -2 if dir is unreadable */

/* do the expand or list on the command line -- SHOULD BE REPLACED */

extern Char NeedsRedraw;	/* from ed.h */
extern int TermH;		/* from the editor routines */
extern int lbuffed;		/* from sh.print.c */
extern bool relatives_in_path;	/* set true if PATH has relative elements */

static	void	 free_items		__P((Char **, int));
static	void	 extract_dir_and_name	__P((Char *, Char *, Char *));
static	Char	*quote_meta		__P((Char *, bool));
static	Char	*getentry		__P((DIR *, int));
static	Char	*dollar			__P((Char *, Char *));
static	Char	*tilde			__P((Char *, Char *));
static	Char	 filetype		__P((Char *, Char *));
static	int	 t_glob			__P((Char ***));
static	int	 is_prefix		__P((Char *, Char *));
static	int	 is_suffix		__P((Char *, Char *));
static	int	 recognize		__P((Char *, Char *, int, int));
static	int	 ignored		__P((Char *));
static	void	 tw_get_comm_list	__P((void));
static	int	 isadirectory		__P((Char *, Char *));

/*
 * If we find a set command, then we break a=b to a= and word becomes
 * b else, we don't break a=b.
 */
#define isaset(c, w) ((w)[-1] == '=' && \
		      ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
		       ((c[3] == ' ' || (c)[3] == '\t'))))
/*
 * Return value for tenematch():
 *  > 1:    No. of items found
 *  = 1:    Exactly one match / spelling corrected
 *  = 0:    No match / spelling was correct
 *  < 0:    Error (incl spelling correction impossible)
 */
int
tenematch(in_line, in_li_size, num_read, command)
    Char   *in_line;		/* match string prefix */
    int     in_li_size;	/* max size of string */
    int     num_read;		/* # actually in in_line */
    COMMAND command;		/* LIST or RECOGNIZE or PRINT_HELP */

{
    Char    word[FILSIZ + 1];
    register Char *str_end, *word_start, *cmd_start, *wp;
    Char   *cmd_st;
    int     space_left;
    int     is_a_cmd;		/* UNIX command rather than filename */
    int     search_ret;		/* what search returned for debugging */
    int     in_single, in_double;	/* In single or in_double quotes */

    str_end = &in_line[num_read];

    /*
     * space backward looking for the beginning of this command
     */
    for (cmd_st = str_end; cmd_st > in_line; --cmd_st)
	if (iscmdmeta(cmd_st[-1])
	    && ((cmd_st - 1 == in_line) || (cmd_st[-2] != '\\')))
	    break;
    /* step forward over leading spaces */
    while (*cmd_st != '\0' && (*cmd_st == ' ' || *cmd_st == '\t'))
	cmd_st++;

    /*
     * Find LAST occurence of a delimiter in the in_line. The word start is
     * one character past it.
     */
    for (word_start = str_end; word_start > in_line; --word_start) {
	if ((ismeta(word_start[-1]) || isaset(cmd_st, word_start)) &&
	    (word_start[-1] != '#') && (word_start[-1] != '$') &&
	    ((word_start - 1 == in_line) || (word_start[-2] != '\\')))
	    break;
    }



#ifdef	masscomp
    /*
     * Avoid a nasty message from the RTU 4.1A & RTU 5.0 comp_r concerning
     * the "overuse of registers". According to the comp_r release notes,
     * incorrect code may be produced unless the offending expression is
     * rewritten. Therefore, we can't just ignore it, DAS DEC-90.
     */
    space_left = in_li_size;
    space_left -= word_start - in_line + 1;
#else
    space_left = in_li_size - (word_start - in_line) - 1;
#endif

    is_a_cmd = starting_a_command(word_start, in_line);
#ifdef TENEDEBUG
    xprintf("starting_a_command %d\n", is_a_cmd);
#endif

    /*
     * Quote args
     */
    in_double = 0;
    in_single = 0;
    for (cmd_start = word_start, wp = word; cmd_start < str_end && wp <= word + FILSIZ; cmd_start++)
	switch (*cmd_start) {
	case '\'':
	    if (!in_double) {
		if (in_single)
		    in_single = 0;
		else
		    in_single = QUOTE;
		/*
		 * Move the word_start further, cause the quotes so far have no
		 * effect.
		 */
		if (cmd_start == word_start)
		    word_start++;
	    }
	    else
		*wp++ = *cmd_start | QUOTE;
	    break;
	case '"':
	    if (!in_single) {
		if (in_double)
		    in_double = 0;
		else
		    in_double = QUOTE;
		/*
		 * Move the word_start further, cause the quotes so far have no
		 * effect.
		 */
		if (cmd_start == word_start)
		    word_start++;
	    }
	    else
		*wp++ = *cmd_start | QUOTE;
	    break;
	case '/':
	    /*
	     * This is so that the recognize stuff works easily
	     */
	    *wp++ = *cmd_start;
	    break;
	case '\\':
	    if (in_single || in_double)
		*wp++ = *cmd_start | QUOTE;
	    else
		*wp++ = *++cmd_start | QUOTE;
	    break;
	default:
	    *wp++ = *cmd_start | in_single;
	    break;
	}
    if (wp > word + FILSIZ)
	return (-1);
    *wp = '\0';


#ifdef TENEDEBUG
    xprintf("\ncmd_st:%s:\n", short2str(cmd_st));
    xprintf("word:%s:\n", short2str(word));
    xprintf("word:");
    for (wp = word; *wp; wp++)
	xprintf("%c", *wp & QUOTE ? '-' : ' ');
    xprintf(":\n");
#endif
    switch ((int) command) {
	Char    buffer[FILSIZ + 1], *bptr;
	Char   *slshp;
	Char   *items[2], **ptr;
	int     i, count;

    case RECOGNIZE:
	if (adrof(STRa_correct)) {
	    if ((slshp = Strrchr(word, '/')) != NULL && slshp[1] != NULL) {
		SearchNoDirErr = 1;
		for (bptr = word; bptr < slshp; bptr++) {
		    /*
		     * do not try to correct spelling of words containing
		     * globbing characters
		     */
		    if (isglob(*bptr)) {
			SearchNoDirErr = 0;
			break;
		    }
		}
	    }
	}
	else
	    slshp = STRNULL;
	search_ret = t_search(word, wp, command, space_left, is_a_cmd, 1);
	SearchNoDirErr = 0;

	if (search_ret == -2) {
	    Char    rword[FILSIZ + 1];

	    (void) Strcpy(rword, slshp);
	    if (slshp != STRNULL)
		*slshp = NULL;
	    if ((search_ret = spell_me(word, sizeof(word), is_a_cmd)) == 1) {
		DeleteBack(str_end - word_start);/* get rid of old word */
		(void) Strcat(word, rword);
		if (InsertStr(word) < 0)	/* insert newly spelled word */
		    return -1;	/* error inserting */
		wp = word + Strlen(word);
		search_ret = t_search(word, wp, command, space_left,
				      is_a_cmd, 1);
	    }
	}

	/*
	 * Change by Christos Zoulas: if the name has metachars in it, quote
	 * the metachars, but only if we are outside quotes.
	 */
	if (*wp && InsertStr((in_single || in_double) ?
			     wp : quote_meta(wp,
					     (bool) is_set(STRaddsuffix))) < 0)
	    /* put it in the input buffer */
	    return -1;		/* error inserting */
	return search_ret;

    case SPELL:
	for (bptr = word_start; bptr < str_end; bptr++) {
	    /*
	     * do not try to correct spelling of words containing globbing
	     * characters
	     */
	    if (isglob(*bptr))
		return 0;
	}
	if ((search_ret = spell_me(word, sizeof(word), is_a_cmd)) == 1) {
	    DeleteBack(str_end - word_start);	/* get rid of old word */
	    if (InsertStr(word) < 0)	/* insert newly spelled word */
		return -1;	/* error inserting */
	}
	return search_ret;

    case PRINT_HELP:
	do_help(cmd_st);
	return 1;

    case GLOB:
    case GLOB_EXPAND:
	(void) Strncpy(buffer, word, FILSIZ + 1);
	items[0] = buffer;
	items[1] = NULL;
	ptr = items;
	if (is_a_cmd) {
	    xprintf("Sorry no globbing for commands yet..\n");
	    return 0;
	}
	if ((count = t_glob(&ptr)) > 0) {
	    if (command == GLOB)
		print_by_column(STRNULL, ptr, count, is_a_cmd);
	    else {
		DeleteBack(str_end - word_start);/* get rid of old word */
		for (i = 0; i < count; i++)
		    if (ptr[i] && *ptr[i]) {
			if (InsertStr((in_single || in_double) ?
				      ptr[i] : quote_meta(ptr[i], 0)) < 0 ||
			    InsertStr(STRspace) < 0) {
			    blkfree(ptr);
			    return (-1);
			}
		    }
	    }
	    blkfree(ptr);
	}
	return count;

    case VARS_EXPAND:
	if (dollar(buffer, word)) {
	    DeleteBack(str_end - word_start);
	    if (InsertStr((in_single || in_double) ?
			  buffer : quote_meta(buffer, 0)) < 0)
		return (-1);
	    return (1);
	}
	return (0);

    case LIST:
	search_ret = t_search(word, wp, command, space_left, is_a_cmd, 1);
	return search_ret;

    default:
	xprintf("tcsh: Internal match error.\n");
	return 1;

    }
}




static int
t_glob(v)
    register Char ***v;
{
    jmp_buf osetexit;

    if (**v == 0)
	return (0);
    gflag = 0, tglob(*v);
    if (gflag) {
	getexit(osetexit);	/* make sure to come back here */
	if (setexit() == 0)
	    *v = globall(*v);
	resexit(osetexit);
	gargv = 0;
	if (haderr) {
	    haderr = 0;
	    NeedsRedraw = 1;
	    return (-1);
	}
	if (*v == 0)
	    return (0);
    }
    else
	return (0);

    return ((int)gargc);
}


/*
 * quote (\) the meta-characters in a word
 * except trailing space if trail_space is set
 * return pointer to quoted word in static storage
 */
static Char *
quote_meta(word, trail_space)
    Char   *word;
    bool    trail_space;
{
    static Char buffer[2 * FILSIZ + 1], *bptr, *wptr;

    for (bptr = buffer, wptr = word; *wptr != '\0';) {
	if ((cmap(*wptr, _META | _DOL | _Q | _ESC | _GLOB) || *wptr == HIST ||
	     *wptr == HISTSUB) &&
	    (*wptr != ' ' || !trail_space || *(wptr + 1) != '\0'))
	    *bptr++ = '\\';
	*bptr++ = *wptr++;
    }
    *bptr = '\0';
    return (buffer);
}


/*
 * return true if check items initial chars in template
 * This differs from PWB imatch in that if check is null
 * it items anything
 */

static int
is_prefix(check, template)
    register Char *check, *template;
{
    for (; *check; check++, template++)
	if ((*check & TRIM) != (*template & TRIM))
	    return (FALSE);
    return (TRUE);
}

/*
 *  Return true if the chars in template appear at the
 *  end of check, I.e., are it's suffix.
 */
static int
is_suffix(check, template)
    register Char *check, *template;
{
    register Char *t, *c;

    for (t = template; *t++;);
    for (c = check; *c++;);
    for (;;) {
	if (t == template)
	    return 1;
	--t;
	--c;
	if (c == check || (*t & TRIM) != (*c & TRIM))
	    return 0;
    }
}

static int
ignored(entry)
    register Char *entry;
{
    struct varent *vp;
    register Char **cp;

    if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
	return (FALSE);
    for (; *cp != NULL; cp++)
	if (is_suffix(entry, *cp))
	    return (TRUE);
    return (FALSE);
}

/* return true if the command starting at wordstart is a command */

#define EVEN(x) (((x) & 1) != 1)

int
starting_a_command(wordstart, in_line)
    register Char *wordstart, *in_line;
{
    register Char *ptr, *ncmdstart;
    int     count;
    static  Char
            cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
            cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};

    /*
     * Find if the number of backquotes is odd or even.
     */
    for (ptr = wordstart, count = 0;
	 ptr >= in_line;
	 count += (*ptr-- == '`'));
    /*
     * if the number of backquotes is even don't include the backquote char in
     * the list of command starting delimiters [if it is zero, then it does not
     * matter]
     */
    ncmdstart = cmdstart + EVEN(count);

    /*
     * look for the characters previous to this word if we find a command
     * starting delimiter we break. if we find whitespace and another previous
     * word then we are not a command
     * 
     * count is our state machine: 0 looking for anything 1 found white-space
     * looking for non-ws
     */
    for (count = 0; wordstart >= in_line; wordstart--) {
	if (*wordstart == '\0')
	    continue;
	if (Strchr(ncmdstart, *wordstart))
	    break;
	/*
	 * found white space
	 */
	if (ptr = Strchr(cmdalive, *wordstart))
	    count = 1;
	if (count == 1 && !ptr)
	    return (FALSE);
    }

    if (wordstart > in_line)
	switch (*wordstart) {
	case '&':		/* Look for >& */
	    while (wordstart > in_line &&
		   (*--wordstart == ' ' || *wordstart == '\t'));
	    if (*wordstart == '>')
		return (FALSE);
	    break;
	case '(':		/* check for foreach, if etc. */
	    while (wordstart > in_line &&
		   (*--wordstart == ' ' || *wordstart == '\t'));
	    if (!iscmdmeta(*wordstart) &&
		(*wordstart != ' ' && *wordstart != '\t'))
		return (FALSE);
	    break;
	default:
	    break;
	}
    return (TRUE);
}



/*
 * Object: extend what user typed up to an ambiguity.
 * Algorithm:
 * On first match, copy full entry (assume it'll be the only match)
 * On subsequent matches, shorten extended_name to the first
 * character mismatch between extended_name and entry.
 * If we shorten it back to the prefix length, stop searching.
 */
static int
recognize(extended_name, entry, name_length, numitems)
    Char   *extended_name, *entry;
    int     name_length, numitems;
{
    if (numitems == 1)		/* 1st match */
	copyn(extended_name, entry, MAXNAMLEN);
    else {			/* 2nd and subsequent matches */
	register Char *x, *ent;
	register int len = 0;

	for (x = extended_name, ent = entry;
	     *x && (*x & TRIM) == (*ent & TRIM); x++, len++, ent++);
	*x = '\0';		/* Shorten at 1st char diff */
	if (len == name_length)	/* Ambiguous to prefix? */
	    return (-1);	/* So stop now and save time */
    }
    return (0);
}



/*
 * Perform a RECOGNIZE or LIST command on string "word".
 *
 * Return value:
 *  >= 0:   SPELL command: "distance" (see spdist())
 *          other:         No. of items found
 *  < 0:    Error (message or beep is output)
 */

/*ARGSUSED*/
int
t_search(word, wp, command, max_word_length, look_command, list_max)
    Char   *word, *wp;		/* original end-of-word */
    COMMAND command;
    int     max_word_length, look_command, list_max;
{
    register ignoring = 1, nignored = 0;
    register name_length,	/* Length of prefix (file name) */
            look_lognames;	/* True if looking for login names */
    int     showpathn;		/* True if we want path number */
    Char    tilded_dir[FILSIZ + 1],	/* dir after ~ expansion */
            dollar_dir[FILSIZ + 1],	/* dir after $ expansion */
            dir[FILSIZ + 1],	/* /x/y/z/ part in /x/y/z/f */
            name[MAXNAMLEN + 1],/* f part in /d/d/d/f */
            extended_name[MAXNAMLEN + 1],	/* the recognized (extended)
						 * name */
           *entry = NULL,	/* single directory entry or logname */
           *target;		/* Target to expand/correct/list */
    int     next_command = 0;	/* the next command to take out of */

    /* the list of commands */
    int     look_shellvar,	/* true if looking for $foo */
            look_file;	/* true if looking for a file name */
    Char  **pathv;		/* pointer to PATH elements */
    struct varent *v_ptr = NULL;/* current shell variable position */
    Char  **env_ptr = NULL;	/* current env. variable position */

    int     d = 4, nd;		/* distance and new distance to command for
				 * SPELL */
    int     exec_check = 0, dir_ok = 0;	/* need to check
					 * executability/directory */

    static  Char		/* For unset path		 */
    *       pv[2] = {STRNULL, NULL};
    static  DIR
    *       dir_fd = NULL;
    static  Char
    **      items = NULL;	/* file names when doing a LIST */

    /*
     * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
     * dump core when interrupted
     */
    static int numitems;

    pathv = (v_ptr = adrof(STRPATH)) == NULL ? pv : v_ptr->vec;

    if (items != NULL)
	FREE_ITEMS(items, numitems);
    numitems = 0;
    if (dir_fd != NULL)
	FREE_DIR(dir_fd);

    non_unique_match = FALSE;	/* See the recexact code below */

    extract_dir_and_name(word, dir, name);
    look_lognames = (*word == '~') && (Strchr(word, '/') == NULL);
    look_shellvar = (target = Strrchr(name, '$')) &&
	(Strchr(name, '/') == NULL);
    look_file = (!look_command && !look_lognames &&
			!look_shellvar) || Strchr(word, '/');

    /* PWP: don't even bother when doing ALL of the commands */
    if (look_command && (*word == '\0')) {
	Beep();
	return (-1);
    }
    tilded_dir[0] = '\0';
    dollar_dir[0] = '\0';

    if (look_shellvar) {	/* Looking for a shell var? */
	v_ptr = tw_shell_list_start();
	env_ptr = tw_env_list_start();
	target++;
    }
    else
	target = name;
    if (look_shellvar || look_file) {
	Char   *nd = NULL;

	/* Open the directory */
	/* expand ~user/... and variables stuff */
	if ((dollar(dollar_dir, dir) == 0) ||
	    (tilde(tilded_dir, dollar_dir) == 0) ||
	    !(nd = dnormalize(*tilded_dir ? tilded_dir : STRdot)) ||
	    ((dir_fd = opendir(short2str(nd))) == NULL)) {
	    xfree((ptr_t) nd);
	    if (SearchNoDirErr)
		return (-2);
	    xprintf("\n%s unreadable\n",
		    *tilded_dir ? short2str(tilded_dir) :
		    (*dollar_dir ? short2str(dollar_dir) : short2str(dir)));
	    NeedsRedraw = 1;
	    return (-1);
	}
	if (nd) {
	    if (*tilded_dir != '\0') {
		Char   *s, *d, *p;

		/*
		 * Copy and append a / if there was one
		 */
		for (p = tilded_dir; *p; p++);
		if (*--p == '/') {
		    for (p = nd; *p; p++);
		    if (*--p != '/')
			p = NULL;
		}
		for (d = tilded_dir, s = nd; *d++ = *s++;);
		if (!p) {
		    *d-- = '\0';
		    *d = '/';
		}
	    }
	    xfree((ptr_t) nd);
	}
    }
    else if (look_lognames) {	/* Looking for login names? */
	(void) setpwent();	/* Open passwd file */
	copyn(name, &word[1], MAXNAMLEN);	/* name sans ~ */
    }
    else if (look_command) {
	if (!numcommands)	/* if we have no list of commands */
	    tw_get_comm_list();
	if (!have_sorted) {	/* if we haven't sorted them yet */
	    tw_builtins_add();
	    tw_aliases_add();
	    tw_sort_comms();	/* re-build the command path for twenex.c */
	}
	copyn(target, word, MAXNAMLEN);	/* so it can match things */
    }
    else {
	xprintf("\ntcsh internal error: I don't know what I'm looking for!\n");
	NeedsRedraw = 1;
	return (-1);
    }


again:
    name_length = Strlen(target);
    showpathn = look_command && is_set(STRl_pathnum);

    while (1) {
	if (look_shellvar) {
	    if ((entry = tw_n_shell_var(&v_ptr)) == NULL)
		if ((entry = tw_n_env_var(&env_ptr)) == NULL)
		    break;
	}
	else if (look_file || look_lognames) {
	    if ((entry = getentry(dir_fd, look_lognames)) == NULL) {
		break;
	    }

	    /*
	     * Don't match . files on null prefix match
	     */
	    if (name_length == 0 && entry[0] == '.' &&
		!look_lognames && !is_set(STRshowdots))
		continue;
	    if (look_command && !look_lognames) {
		exec_check = 1;
		dir_ok = 1;
	    }
	}
	else if (look_command) {
#ifdef  NOTDEF			/* Not possible */
	    if (numcommands == 0) {
		dohash();
	    }
#endif
	    /* searching . added by Andreas Luik <luik@isaak.isa.de> */

	    if ((next_command < numcommands) &&
		(entry = com_list[next_command]) == NULL)
		next_command = numcommands;
	    if (next_command >= numcommands) {	/* search relative elems */
		if (!relatives_in_path)
		    break;	/* we don't need to do it */
		while ((dir_fd == NULL ||
			(entry = getentry(dir_fd, FALSE)) == NULL) &&
		       *pathv) {
		    if (dir_fd != NULL)
			FREE_DIR(dir_fd);
		    entry = NULL;
		    while (*pathv && pathv[0][0] == '/')
			pathv++;
		    if (*pathv) {
			if (pathv[0][0] == '\0' ||
			    (pathv[0][0] == '.' && pathv[0][1] == '\0')) {
			    *tilded_dir = '\0';
			    dir_fd = opendir(".");
			}
			else {
			    copyn(tilded_dir, *pathv, FILSIZ);
			    catn(tilded_dir, STRslash, FILSIZ);
			    dir_fd = opendir(short2str(*pathv));
			}
			pathv++;
		    }
		}
		if (entry == NULL)
		    break;	/* end of PATH */
		/*
		 * executability check for other than "." should perhaps be
		 * conditional on recognize_only_executables?
		 */
		exec_check = 1;
		dir_ok = 0;
	    }
	    else
		next_command++;
	}

	if (command == SPELL) {	/* correct the spelling of the last bit */
	    if (name_length == 0) {/* zero-length word can't be misspelled */
		extended_name[0] = '\0';/* (not trying is important for ~) */
		d = 0;
		break;
	    }
	    nd = spdist(entry, target);	/* test the entry against original */
	    if (nd <= d && nd != 4) {
		if (exec_check && !executable(tilded_dir, entry, dir_ok))
		    continue;
		(void) Strcpy(extended_name, entry);
		d = nd;
		if (d == 0)	/* if found it exactly */
		    break;
	    }
	    else if (nd == 4) {
		if (spdir(extended_name, tilded_dir, entry, target)) {
		    if (exec_check &&
			!executable(tilded_dir, extended_name, dir_ok))
			continue;
		    d = 0;
		    break;
		}
	    }
	}
	else if (command == LIST) {	/* LIST command */
	    register int length;
	    register long i;
	    register Char **ni, **p2;

	    if (!is_prefix(target, entry))
		continue;
	    if (exec_check && !executable(tilded_dir, entry, dir_ok))
		continue;

	    if (items == NULL || maxitems == 0) {
		items = (Char **) xmalloc((size_t) (sizeof(items[0]) *
						    (ITEMS_START + 1)));
		maxitems = ITEMS_START;
		for (i = 0, p2 = items; i < maxitems; i++)
		    *p2++ = NULL;
	    }
	    else if (numitems >= maxitems) {
		ni = (Char **) xrealloc((ptr_t) items, (size_t)
				(sizeof(items[0])) * (maxitems + ITEMS_INCR));
		items = ni;
		maxitems += ITEMS_INCR;
	    }


	    length = Strlen(entry) + 1;
	    if (showpathn)
		length += Strlen(dirflag);
	    if (!look_lognames && !look_shellvar)
		length++;

	    /* safety check */
	    items[numitems] = (Char *) xmalloc((size_t) (length * sizeof(Char)));

	    copyn(items[numitems], entry, MAXNAMLEN);

	    if (!look_lognames && !look_shellvar
		&& !(look_command && !look_file)) {
		Char    typestr[2];

		typestr[0] = filetype(tilded_dir, entry);
		typestr[1] = '\0';
		catn(items[numitems], typestr, MAXNAMLEN);
	    }

	    if (showpathn)
		catn(items[numitems], dirflag, MAXNAMLEN);
	    numitems++;
	}
	else {			/* RECOGNIZE command */
	    if (!is_prefix(target, entry))
		continue;
	    if (exec_check && !executable(tilded_dir, entry, dir_ok))
		continue;

	    if (ignoring && ignored(entry)) {
		nignored++;
		continue;
	    }
	    if (is_set(STRrecexact)) {
		if (StrQcmp(target, entry) == 0) {	/* EXACT match */
		    copyn(extended_name, entry, MAXNAMLEN);
		    numitems = 1;	/* fake into expanding */
		    non_unique_match = TRUE;
		    break;
		}
	    }
	    if (recognize(extended_name, entry, name_length, ++numitems))
		break;
	}
    }

    if (ignoring && numitems == 0 && nignored > 0) {
	ignoring = 0;
	nignored = 0;
	if (look_lognames)
	    (void) setpwent();
	else
	    rewinddir(dir_fd);
	goto again;
    }
    if (look_lognames) {
#ifdef YPBUGS
	fix_yp_bugs();
#endif				/* YPBUGS */
	(void) endpwent();
    }
    else if (look_file || look_shellvar ||
	     (look_command && relatives_in_path)) {
	if (dir_fd != NULL)
	    FREE_DIR(dir_fd);
    }

    if (command == RECOGNIZE) {
	if (numitems > 0) {
	    if (look_lognames)
		copyn(word, STRtilde, 1);
	    else if (look_shellvar) {
		Char   *ptr = Strrchr(word, '$');

		*++ptr = '\0';	/* Delete after the dollar */
	    }
	    else if (look_file)
		copyn(word, dir, max_word_length);	/* put back dir part */
	    else
		word[0] = '\0';
	    catn(word, extended_name, max_word_length);	/* add extended name */
	    if (is_set(STRaddsuffix)) {
		if (numitems == 1) {
		    if (look_lognames) {	/* add / */
			catn(word, STRslash, max_word_length);
		    }
		    else if (look_shellvar) {
			struct varent *vp = adrof(extended_name);
			Char   *stp;

			/*
			 * Don't consider array variables or empty variables
			 */
			if (vp) {
			    if (!(stp = vp->vec[0]) || vp->vec[0][0] == '\0' ||
				vp->vec[1]) {
				catn(word, STRspace, max_word_length);
				stp = NULL;
			    }
			    else
				stp = vp->vec[0];
			}
			else if ((stp = Getenv(extended_name)) == NULL)
			    catn(word, STRspace, max_word_length);
			if (stp != NULL) {
			    *--target = '\0';
			    (void) Strcat(tilded_dir, name);
			    if (isadirectory(tilded_dir, stp))
				catn(word, STRslash, max_word_length);
			    else
				catn(word, STRspace, max_word_length);
			}
		    }
		    else if (look_file) {
			if (isadirectory(tilded_dir, extended_name)) {
			    catn(word, STRslash, max_word_length);
			}
			else {
			    catn(word, STRspace, max_word_length);
			}
		    }
		    else {	/* prob. looking for a command */
			catn(word, STRspace, max_word_length);
		    }
		}
	    }
	}
	return (numitems);	/* at the end */
    }
    else if (command == LIST) {
	register int max_items = 0;
	register Char *cp;

	if (cp = value(STRl_max)) {
	    while (*cp) {
		if (!Isdigit(*cp)) {
		    max_items = 0;
		    break;
		}
		max_items = max_items * 10 + *cp++ - '0';
	    }
	}

	if ((max_items > 0) && (numitems > max_items) && list_max) {
	    char    tc;

	    xprintf("There are %d items, list them anyway? [n/y] ", numitems);
	    flush();
	    /* We should be in Rawmode here, so no \n to catch */
	    (void) read(SHIN, &tc, 1);
	    xprintf("%c\r\n", tc);	/* echo the char, do a newline */
	    if ((tc != 'y') && (tc != 'Y'))
		goto done_list;
	}
	qsort((ptr_t) items, (size_t) numitems, sizeof(items[1]), 
	      (int (*) __P((const void *, const void *))) fcompare);

	print_by_column(STRNULL, items, numitems, TRUE);

done_list:
	if (items != NULL)
	    FREE_ITEMS(items, numitems);
	return (numitems);
    }
    else if (command == SPELL) {
	if (look_lognames)
	    copyn(word, STRtilde, 1);
	else if (look_shellvar) {
	    Char   *ptr = Strrchr(word, '$');

	    *++ptr = '\0';	/* Delete after the dollar */
	}
	else if (look_file)
	    copyn(word, dir, max_word_length);	/* put back dir part */
	else
	    word[0] = '\0';
	catn(word, extended_name, max_word_length);	/* add extended name */
	return (d);
    }
    else {
	xprintf("Bad tw_command\n");
	return (0);
    }
}



/* stuff for general command line hacking */

/*
 * Strip next directory from path; return ptr to next unstripped directory.
 */

#ifdef notdef
Char * extct_dir_from_path(path, dir)
    Char   *path, dir[];
{
    register Char *d = dir;

    while (*path && (*path == ' ' || *path == ':'))
	path++;
    while (*path && (*path != ' ' && *path != ':'))
	*(d++) = *(path++);
    while (*path && (*path == ' ' || *path == ':'))
	path++;

    ++dirctr;
    if (*dir == '.')
	(void) Strcpy(dirflag, STRdotsp);
    else {
	dirflag[0] = ' ';
	if (dirctr <= 9) {
	    dirflag[1] = '0' + dirctr;
	    dirflag[2] = '\0';
	}
	else {
	    dirflag[1] = '0' + dirctr / 10;
	    dirflag[2] = '0' + dirctr % 10;
	    dirflag[3] = '\0';
	}
    }
    *(d++) = '/';
    *d = 0;

    return path;
}

#endif


static void
free_items(items, numitems)
    register Char **items;
    register int numitems;
{
    register int i;

/*     for (i = 0; items[i] != (Char *)NULL; i++) */
    for (i = 0; i < numitems; i++)
	xfree((ptr_t) items[i]);
    xfree((ptr_t) items);
    maxitems = 0;
}


/*
 * parse full path in file into 2 parts: directory and file names
 * Should leave final slash (/) at end of dir.
 */
static void
extract_dir_and_name(path, dir, name)
    Char   *path, *dir, *name;
{
    register Char *p;

    p = Strrchr(path, '/');
    if (p == NULL) {
	copyn(name, path, MAXNAMLEN);
	dir[0] = '\0';
    }
    else {
	p++;
	copyn(name, p, MAXNAMLEN);
	copyn(dir, path, p - path);
    }
}

static Char *
getentry(dir_fd, look_lognames)
    DIR    *dir_fd;
    int     look_lognames;
{
    register struct passwd *pw;
    static Char retname[MAXPATHLEN];

    register struct dirent *dirp;

    if (look_lognames) {	/* Is it login names we want? */

	pw = getpwent();

	if (pw == NULL) {
#ifdef YPBUGS
	    fix_yp_bugs();
#endif
	    return (NULL);
	}
	(void) Strcpy(retname, str2short(pw->pw_name));
	return (retname);
    }
    else {			/* It's a dir entry we want */
	if (dirp = readdir(dir_fd)) {
	    (void) Strcpy(retname, str2short(dirp->d_name));
	    return (retname);
	}
	return (NULL);
    }
}

/*
 * expand "/$old1/$old2/old3/"
 * to "/value_of_old1/value_of_old2/old3/"
 */
static Char *
dollar(new, old)
    Char   *new, *old;
{
    Char   *var, *val, *p, save;
    int     space;

    for (space = FILSIZ, p = new; *old && space > 0;)
	if (*old != '$') {
	    *p++ = *old++;
	    space--;
	}
	else {
	    struct varent *vp;

	    /* found a variable, expand it */
	    for (var = ++old; alnum(*old); old++);
	    save = *old;
	    *old = '\0';
	    vp = adrof(var);
	    val = (!vp) ? Getenv(var) : NULL;
	    *old = save;
	    /*
	     * Don't expand array variables
	     */
	    if (vp) {
		if (!vp->vec[0] || vp->vec[1]) {
		    *new = '\0';
		    return (NULL);
		}
		else
		    val = vp->vec[0];
	    }
	    else if (!val) {
		*new = '\0';
		return (NULL);
	    }
	    for (; space > 0 && *val; space--)
		*p++ = *val++;
	}
    *p = '\0';
    return (new);
}

/*
 * expand "old" file name with possible tilde usage
 *		~person/mumble
 * expands to
 *		home_directory_of_person/mumble
 * into string "new".
 */

static Char *
tilde(new, old)
    Char   *new, *old;
{
    register Char *o, *p;

    if ((old[0] != '~') && (old[0] != '=')) {
	(void) Strcpy(new, old);
	return (new);
    }

    new[0] = '\0';
    for (p = new, o = &old[1]; *o && *o != '/'; *p++ = *o++);
    *p = '\0';

    if (old[0] == '~') {
	if (gethdir(new))
	    return (NULL);
    }
    else {			/* '=' stack expansion */
	if (!Isdigit(old[1]) && old[1] != '-')
	    return (NULL);
	if (!getstakd(new, (old[1] == '-') ? -1 : old[1] - '0'))
	    return (NULL);
    }
    (void) Strcat(new, o);
    return (new);
}

static  Char
filetype(dir, file)		/* symbology from 4.3 ls command */
    Char   *dir, *file;
{
    if (dir) {
	Char    path[512];
	char   *ptr;
	struct stat statb;

	(void) Strcpy(path, dir);
	catn(path, file, sizeof(path) / sizeof(Char));

	if (lstat(ptr = short2str(path), &statb) != -1)
	    /* see above #define of lstat */
	{
#ifdef S_ISLNK
	    if (S_ISLNK(statb.st_mode)) {	/* Symbolic link */
		if (adrof(STRl_links)) {
		    if (stat(ptr, &statb) == -1)
			return ('&');
		    else if (S_ISDIR(statb.st_mode))
			return ('>');
		    else
			return ('@');
		}
		else
		    return ('@');
	    }
#endif
#ifdef S_ISSOCK
	    if (S_ISSOCK(statb.st_mode))	/* Socket */
		return ('=');
#endif
#ifdef S_ISFIFO
	    if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
		return ('|');
#endif
#ifdef S_ISHIDDEN
	    if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
		return ('+');
#endif
#ifdef S_ISCDF	
	    if (S_ISCDF(statb.st_mode))	/* Context Dependent Files [hpux] */
		return ('+');
#endif 
#ifdef S_ISNWK
	    if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
		return (':');
#endif
	    if (S_ISCHR(statb.st_mode))	/* char device */
		return ('%');
	    if (S_ISBLK(statb.st_mode))	/* block device */
		return ('#');
	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
		return ('/');
	    if (statb.st_mode & 0111)
		return ('*');
	}
    }
    return (' ');
}

static int
isadirectory(dir, file)		/* return 1 if dir/file is a directory */
    Char   *dir, *file;		/* uses stat rather than lstat to get dest. */
{
    if (dir) {
	Char    path[MAXPATHLEN];
	struct stat statb;

	(void) Strcpy(path, dir);
	catn(path, file, sizeof(path) / sizeof(Char));
	if (stat(short2str(path), &statb) >= 0) {	/* resolve through
							 * symlink */
#ifdef S_ISSOCK
	    if (S_ISSOCK(statb.st_mode))	/* Socket */
		return 0;
#endif
#ifdef S_ISFIFO
	    if (S_ISFIFO(statb.st_mode))	/* Named Pipe */
		return 0;
#endif
	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
		return 1;
	}
    }
    return 0;
}

/*
 * Print sorted down columns
 */
void
print_by_column(dir, items, count, no_file_suffix)
    register Char *dir, *items[];
    int     count, no_file_suffix;
{
    register int i, r, c, w, maxwidth = 0, columns, rows;
    extern int Tty_raw_mode;

    lbuffed = 0;		/* turn off line buffering */

    for (i = 0; i < count; i++)	/* find widest string */
	maxwidth = max(maxwidth, Strlen(items[i]));

    maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
    columns = (TermH + 1) / maxwidth;	/* PWP: terminal size change */
    if (!columns)
	columns = 1;
    rows = (count + (columns - 1)) / columns;

    for (r = 0; r < rows; r++) {
	for (c = 0; c < columns; c++) {
	    i = c * rows + r;

	    if (i < count) {
		w = Strlen(items[i]);

		if (no_file_suffix) {
		    /* Print the command name */
		    xprintf("%s", short2str(items[i]));
		}
		else {
		    /* Print filename followed by '/' or '*' or ' ' */
		    xprintf("%s%c", short2str(items[i]),
			    filetype(dir, items[i]));
		    w++;
		}

		if (c < (columns - 1))	/* Not last column? */
		    for (; w < maxwidth; w++)
			xputchar(' ');
	    }
	}
	if (Tty_raw_mode)
	    xputchar('\r');
	xputchar('\n');
    }

    lbuffed = 1;		/* turn back on line buffering */
    flush();
}


int
StrQcmp(str1, str2)
    register Char *str1, *str2;
{
    for (; *str1 && (*str1 & TRIM) == (*str2 & TRIM); str1++, str2++);
    /*
     * The following case analysis is necessary so that characters which look
     * negative collate low against normal characters but high against the
     * end-of-string NUL.
     */
    if (*str1 == '\0' && *str2 == '\0')
	return (0);
    else if (*str1 == '\0')
	return (-1);
    else if (*str2 == '\0')
	return (1);
    else
	return ((*str1 & TRIM) - (*str2 & TRIM));
}

/*
 * For qsort()
 */
int
fcompare(file1, file2)
    Char  **file1, **file2;
{
#if defined(NLS) && !defined(NOSTRCOLL)
    char    buf[2048];

    (void) strcpy(buf, short2str(*file1));
    return ((int) strcoll(buf, short2str(*file2)));
#else
    return (StrQcmp(*file1, *file2));
#endif
}

/*
 * Concatenate src onto tail of des.
 * Des is a string whose maximum length is count.
 * Always null terminate.
 */

void
catn(des, src, count)
    register Char *des, *src;
    register count;
{
    while (--count >= 0 && *des)
	des++;
    while (--count >= 0)
	if ((*des++ = *src++) == 0)
	    return;
    *des = '\0';
}

/*
 * like strncpy but always leave room for trailing \0
 * and always null terminate.
 */
void
copyn(des, src, count)
    register Char *des, *src;
    register count;
{
    while (--count >= 0)
	if ((*des++ = *src++) == 0)
	    return;
    *des = '\0';
}

static void
tw_get_comm_list()
{				/* stolen from sh.exec.c dohash() */
    register DIR *dirp;
    register struct dirent *dp;
    register Char *dir;
    register Char **pv;
    struct varent *v = adrof(STRpath);

    relatives_in_path = 0;	/* set to false until we know better */
    tw_clear_comm_list();
    if (v == 0)			/* if no path */
	return;

    if (adrof(STRrecognize_only_executables)) {
	for (pv = v->vec; *pv; pv++) {
	    if (pv[0][0] != '/') {
		relatives_in_path = 1;
		continue;
	    }
	    dirp = opendir(short2str(*pv));
	    if (dirp == NULL)
		continue;

	    dir = Strspl(*pv, STRslash);
	    while ((dp = readdir(dirp)) != NULL) {
		/* the call to executable() may make this a bit slow */
		if (dp->d_ino != 0 &&
		    executable(dir, str2short(dp->d_name), 0))
		    tw_comm_name_add(str2short(dp->d_name));
	    }
	    (void) closedir(dirp);
	    xfree((ptr_t) dir);
	}
    }
    else {
	for (pv = v->vec; *pv; pv++) {
	    if (pv[0][0] != '/') {
		relatives_in_path = 1;
		continue;
	    }
	    dirp = opendir(short2str(*pv));
	    if (dirp == NULL)
		continue;

	    while ((dp = readdir(dirp)) != NULL) {
		if (dp->d_ino != 0)
		    tw_comm_name_add(str2short(dp->d_name));
	    }
	    (void) closedir(dirp);
	}
    }
}
