text: /* $Header: /p/tcsh/cvsroot/tcsh/tw.parse.c,v 3.124 2007/07/02 15:48:48 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. 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 "sh.h"
  
  RCSID("$tcsh: tw.parse.c,v 3.124 2007/07/02 15:48:48 christos Exp $")
  
  #include "tw.h"
  #include "ed.h"
  #include "tc.h"
  
  #include <assert.h>
  
  #ifdef WINNT_NATIVE
  #include "nt.const.h"
  #endif /* WINNT_NATIVE */
  #define EVEN(x) (((x) & 1) != 1)
  
  #define DOT_NONE	0	/* Don't display dot files		*/
  #define DOT_NOT		1	/* Don't display dot or dot-dot		*/
  #define DOT_ALL		2	/* Display all dot files		*/
  
  /*  TW_NONE,	       TW_COMMAND,     TW_VARIABLE,    TW_LOGNAME,	*/
  /*  TW_FILE,	       TW_DIRECTORY,   TW_VARLIST,     TW_USER,		*/
  /*  TW_COMPLETION,     TW_ALIAS,       TW_SHELLVAR,    TW_ENVVAR,	*/
  /*  TW_BINDING,        TW_WORDLIST,    TW_LIMIT,       TW_SIGNAL	*/
  /*  TW_JOB,	       TW_EXPLAIN,     TW_TEXT,	       TW_GRPNAME	*/
  static void (*const tw_start_entry[]) (DIR *, const Char *) = {
      tw_file_start,     tw_cmd_start,   tw_var_start,   tw_logname_start, 
      tw_file_start,     tw_file_start,  tw_vl_start,    tw_logname_start, 
      tw_complete_start, tw_alias_start, tw_var_start,   tw_var_start,     
      tw_bind_start,     tw_wl_start,    tw_limit_start, tw_sig_start,
      tw_job_start,      tw_file_start,  tw_file_start,  tw_grpname_start
  };
  
  static int (*const tw_next_entry[]) (struct Strbuf *, struct Strbuf *,
  				     int *) = {
      tw_file_next,      tw_cmd_next,    tw_var_next,    tw_logname_next,  
      tw_file_next,      tw_file_next,   tw_var_next,    tw_logname_next,  
      tw_var_next,       tw_var_next,    tw_shvar_next,  tw_envvar_next,   
      tw_bind_next,      tw_wl_next,     tw_limit_next,  tw_sig_next,
      tw_job_next,       tw_file_next,   tw_file_next,   tw_grpname_next
  };
  
  static void (*const tw_end_entry[]) (void) = {
      tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
      tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end, 
      tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
      tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
      tw_dir_end,	       tw_dir_end,     tw_dir_end,    tw_grpname_end
  };
  
  /* #define TDEBUG */
  
  /* Set to TRUE if recexact is set and an exact match is found
   * along with other, longer, matches.
   */
  
  int curchoice = -1;
  
  int match_unique_match = FALSE;
  int non_unique_match = FALSE;
  static int SearchNoDirErr = 0;	/* t_search returns -2 if dir is unreadable */
  
  /* state so if a completion is interrupted, the input line doesn't get
     nuked */
  int InsideCompletion = 0;
  
  /* do the expand or list on the command line -- SHOULD BE REPLACED */
  
  static	void	 extract_dir_and_name	(const Char *, struct Strbuf *,
  					 Char **);
  static	int	 insert_meta		(const Char *, const Char *,
  					 const Char *, int);
  static	int	 tilde			(struct Strbuf *, Char *);
  static  int      expand_dir		(const Char *, struct Strbuf *, DIR **,
  					 COMMAND);
  static	int	 nostat			(Char *);
  static	Char	 filetype		(Char *, Char *);
  static	int	 t_glob			(Char ***, int);
  static	int	 c_glob			(Char ***);
  static	int	 is_prefix		(Char *, Char *);
  static	int	 is_prefixmatch		(Char *, Char *, int);
  static	int	 is_suffix		(Char *, Char *);
  static	int	 recognize		(struct Strbuf *, const Char *, size_t,
  					 int, int, int);
  static	int	 ignored		(Char *);
  static	int	 isadirectory		(const Char *, const Char *);
  static  int      tw_collect_items	(COMMAND, int, struct Strbuf *,
  					 struct Strbuf *, Char *, const Char *,
  					 int);
  static  int      tw_collect		(COMMAND, int, struct Strbuf *,
  					 struct Strbuf *, Char *, Char *, int,
  					 DIR *);
  static	Char 	 tw_suffix		(int, const Char *, Char *);
  static	void 	 tw_fixword		(int, struct Strbuf *, Char *, Char *);
  static	void	 tw_list_items		(int, int, int);
  static 	void	 add_scroll_tab		(Char *);
  static 	void 	 choose_scroll_tab	(struct Strbuf *, int);
  static	void	 free_scroll_tab	(void);
  static	int	 find_rows		(Char *[], int, int);
  
  #ifdef notdef
  /*
   * If we find a set command, then we break a=b to a= and word becomes
   * b else, we don't break a=b. [don't use that; splits words badly and
   * messes up tw_complete()]
   */
  #define isaset(c, w) ((w)[-1] == '=' && \
  		      ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
  		       ((c[3] == ' ' || (c)[3] == '\t'))))
  #endif
  
  /* TRUE if character must be quoted */
  #define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#')
  /* TRUE if double quotes don't protect character */
  #define tricky_dq(w) (cmap(w, _DOL | _QB))
  
  /* tenematch():
   *	Return:
   *		> 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(Char *inputline, int num_read, COMMAND command)
  {
      struct Strbuf qline = Strbuf_INIT;
      Char    qu = 0, *pat = STRNULL;
      size_t wp, word, wordp, cmd_start, oword = 0, ocmd_start = 0;
      Char   *str_end, *cp;
      Char   *word_start;
      Char   *oword_start = NULL;
      eChar suf = 0;
      int     looking;		/* what we are looking for		*/
      int     search_ret;		/* what search returned for debugging 	*/
      int     backq = 0;
  
      str_end = &inputline[num_read];
      cleanup_push(&qline, Strbuf_cleanup);
  
      word_start = inputline;
      word = cmd_start = 0;
      for (cp = inputline; cp < str_end; cp++) {
          if (!cmap(qu, _ESC)) {
  	    if (cmap(*cp, _QF|_ESC)) {
  		if (qu == 0 || qu == *cp) {
  		    qu ^= *cp;
  		    continue;
  		}
  	    }
  	    if (qu != '\'' && cmap(*cp, _QB)) {
  		if ((backq ^= 1) != 0) {
  		    ocmd_start = cmd_start;
  		    oword_start = word_start;
  		    oword = word;
  		    word_start = cp + 1;
  		    word = cmd_start = qline.len + 1;
  		}
  		else {
  		    cmd_start = ocmd_start;
  		    word_start = oword_start;
  		    word = oword;
  		}
  		Strbuf_append1(&qline, *cp);
  		continue;
  	    }
  	}
  	if (iscmdmeta(*cp))
  	    cmd_start = qline.len + 1;
  
  	/* Don't quote '/' to make the recognize stuff work easily */
  	/* Don't quote '$' in double quotes */
  
  	if (cmap(*cp, _ESC) && cp < str_end - 1 && cp[1] == HIST)
  	    Strbuf_append1(&qline, *++cp | QUOTE);
  	else if (qu && (tricky(*cp) || *cp == '~') && !(qu == '\"' && tricky_dq(*cp)))
  	    Strbuf_append1(&qline, *cp | QUOTE);
  	else
  	    Strbuf_append1(&qline, *cp);
  	if (ismetahash(qline.s[qline.len - 1])
  	    /* || isaset(qline.s + cmd_start, qline.s + qline.len) */)
  	    word = qline.len, word_start = cp + 1;
  	if (cmap(qu, _ESC))
  	    qu = 0;
        }
      Strbuf_terminate(&qline);
      wp = qline.len;
  
      /*
       *  SPECIAL HARDCODED COMPLETIONS:
       *    first word of command       -> TW_COMMAND
       *    everything else             -> TW_ZERO
       *
       */
      looking = starting_a_command(qline.s + word - 1, qline.s) ?
  	TW_COMMAND : TW_ZERO;
  
      wordp = word;
  
  #ifdef TDEBUG
      {
  	const Char *p;
  
  	xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking);
  	xprintf("\ncmd_start:%S:\n", qline.s + cmd_start);
  	xprintf("qline:%S:\n", qline.s);
  	xprintf("qline:");
  	for (p = qline.s; *p; p++)
  	    xprintf("%c", *p & QUOTE ? '-' : ' ');
  	xprintf(":\n");
  	xprintf("word:%S:\n", qline.s + word);
  	xprintf("word:");
  	for (p = qline.s + word; *p; p++)
  	    xprintf("%c", *p & QUOTE ? '-' : ' ');
  	xprintf(":\n");
      }
  #endif
  
      if ((looking == TW_COMMAND || looking == TW_ZERO) &&
          (command == RECOGNIZE || command == LIST || command == SPELL ||
  	 command == RECOGNIZE_SCROLL)) {
  	Char *p;
  
  #ifdef TDEBUG
  	xprintf(CGETS(30, 2, "complete %d "), looking);
  #endif
  	p = qline.s + wordp;
  	looking = tw_complete(qline.s + cmd_start, &p, &pat, looking, &suf);
  	wordp = p - qline.s;
  #ifdef TDEBUG
  	xprintf(CGETS(30, 3, "complete %d %S\n"), looking, pat);
  #endif
      }
  
      switch (command) {
  	Char   *bptr;
  	Char   *items[2], **ptr;
  	int     i, count;
  
      case RECOGNIZE:
      case RECOGNIZE_SCROLL:
      case RECOGNIZE_ALL: {
  	struct Strbuf wordbuf = Strbuf_INIT;
  	Char   *slshp;
  
  	if (adrof(STRautocorrect)) {
  	    if ((slshp = Strrchr(qline.s + wordp, '/')) != NULL &&
  		slshp[1] != '\0') {
  		SearchNoDirErr = 1;
  		for (bptr = qline.s + wordp; bptr < slshp; bptr++) {
  		    /*
  		     * do not try to correct spelling of words containing
  		     * globbing characters
  		     */
  		    if (isglob(*bptr)) {
  			SearchNoDirErr = 0;
  			break;
  		    }
  		}
  	    }
  	}
  	else
  	    slshp = STRNULL;
  	Strbuf_append(&wordbuf, qline.s + wordp);
  	Strbuf_terminate(&wordbuf);
  	cleanup_push(&wordbuf, Strbuf_cleanup);
  	search_ret = t_search(&wordbuf, command, looking, 1, pat, suf);
  	qline.len = wordp;
  	Strbuf_append(&qline, wordbuf.s);
  	Strbuf_terminate(&qline);
  	cleanup_until(&wordbuf);
  	SearchNoDirErr = 0;
  
  	if (search_ret == -2) {
  	    Char *rword;
  
  	    rword = Strsave(slshp);
  	    cleanup_push(rword, xfree);
  	    if (slshp != STRNULL)
  		*slshp = '\0';
  	    wordbuf = Strbuf_init;
  	    Strbuf_append(&wordbuf, qline.s + wordp);
  	    Strbuf_terminate(&wordbuf);
  	    cleanup_push(&wordbuf, Strbuf_cleanup);
  	    search_ret = spell_me(&wordbuf, looking, pat, suf);
  	    if (search_ret == 1) {
  		Strbuf_append(&wordbuf, rword);
  		Strbuf_terminate(&wordbuf);
  		wp = wordp + wordbuf.len;
  		search_ret = t_search(&wordbuf, command, looking, 1, pat, suf);
  	    }
  	    qline.len = wordp;
  	    Strbuf_append(&qline, wordbuf.s);
  	    Strbuf_terminate(&qline);
  	    cleanup_until(rword);
  	}
  	if (qline.s[wp] != '\0' &&
  	    insert_meta(word_start, str_end, qline.s + word, !qu) < 0)
  	    goto err;		/* error inserting */
  	break;
      }
  
      case SPELL: {
  	struct Strbuf wordbuf = Strbuf_INIT;
  
  	for (bptr = word_start; bptr < str_end; bptr++) {
  	    /*
  	     * do not try to correct spelling of words containing globbing
  	     * characters
  	     */
  	    if (isglob(*bptr)) {
  		search_ret = 0;
  		goto end;
  	    }
  	}
  	Strbuf_append(&wordbuf, qline.s + wordp);
  	Strbuf_terminate(&wordbuf);
  	cleanup_push(&wordbuf, Strbuf_cleanup);
  	search_ret = spell_me(&wordbuf, looking, pat, suf);
  	qline.len = wordp;
  	Strbuf_append(&qline, wordbuf.s);
  	Strbuf_terminate(&qline);
  	cleanup_until(&wordbuf);
  	if (search_ret == 1) {
  	    if (insert_meta(word_start, str_end, qline.s + word, !qu) < 0)
  		goto err;		/* error inserting */
  	}
  	break;
      }
  
      case PRINT_HELP:
  	do_help(qline.s + cmd_start);
  	search_ret = 1;
  	break;
  
      case GLOB:
      case GLOB_EXPAND:
  	items[0] = Strsave(qline.s + wordp);
  	items[1] = NULL;
  	cleanup_push(items[0], xfree);
  	ptr = items;
  	count = (looking == TW_COMMAND && Strchr(qline.s + wordp, '/') == 0) ?
  		c_glob(&ptr) :
  		t_glob(&ptr, looking == TW_COMMAND);
  	cleanup_until(items[0]);
  	if (ptr != items)
  	    cleanup_push(ptr, blk_cleanup);
  	if (count > 0) {
  	    if (command == GLOB)
  		print_by_column(STRNULL, ptr, count, 0);
  	    else {
  		DeleteBack(str_end - word_start);/* get rid of old word */
  		for (i = 0; i < count; i++)
  		    if (ptr[i] && *ptr[i]) {
  			(void) quote(ptr[i]);
  			if (insert_meta(0, 0, ptr[i], 0) < 0 ||
  			    InsertStr(STRspace) < 0) {
  			    if (ptr != items)
  				cleanup_until(ptr);
  			    goto err;		/* error inserting */
  			}
  		    }
  	    }
  	}
  	if (ptr != items)
  	    cleanup_until(ptr);
  	search_ret = count;
  	break;
  
      case VARS_EXPAND:
  	bptr = dollar(qline.s + word);
  	if (bptr != NULL) {
  	    if (insert_meta(word_start, str_end, bptr, !qu) < 0) {
  		xfree(bptr);
  		goto err;		/* error inserting */
  	    }
  	    xfree(bptr);
  	    search_ret = 1;
  	    break;
  	}
  	search_ret = 0;
  	break;
  
      case PATH_NORMALIZE:
  	if ((bptr = dnormalize(qline.s + wordp, symlinks == SYM_IGNORE ||
  			       symlinks == SYM_EXPAND)) != NULL) {
  	    if (insert_meta(word_start, str_end, bptr, !qu) < 0) {
  		xfree(bptr);
  		goto err;		/* error inserting */
  	    }
  	    xfree(bptr);
  	    search_ret = 1;
  	    break;
  	}
  	search_ret = 0;
  	break;
  
      case COMMAND_NORMALIZE: {
  	Char *p;
  	int found;
  
  	found = cmd_expand(qline.s + wordp, &p);
  	
  	if (!found) {
  	    xfree(p);
  	    search_ret = 0;
  	    break;
  	}
  	if (insert_meta(word_start, str_end, p, !qu) < 0) {
  	    xfree(p);
  	    goto err;		/* error inserting */
  	}
  	xfree(p);
  	search_ret = 1;
  	break;
      }
  
      case LIST:
      case LIST_ALL: {
  	struct Strbuf wordbuf = Strbuf_INIT;
  
  	Strbuf_append(&wordbuf, qline.s + wordp);
  	Strbuf_terminate(&wordbuf);
  	cleanup_push(&wordbuf, Strbuf_cleanup);
  	search_ret = t_search(&wordbuf, LIST, looking, 1, pat, suf);
  	qline.len = wordp;
  	Strbuf_append(&qline, wordbuf.s);
  	Strbuf_terminate(&qline);
  	cleanup_until(&wordbuf);
  	break;
      }
  
      default:
  	xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname);
  	search_ret = 1;
      }
   end:
      cleanup_until(&qline);
      return search_ret;
  
   err:
      cleanup_until(&qline);
      return -1;
  } /* end tenematch */
  
  
  /* t_glob():
   * 	Return a list of files that match the pattern
   */
  static int
  t_glob(Char ***v, int cmd)
  {
      jmp_buf_t osetexit;
      int gflag;
  
      if (**v == 0)
  	return (0);
      gflag = tglob(*v);
      if (gflag) {
  	size_t omark;
  
  	getexit(osetexit);	/* make sure to come back here */
  	omark = cleanup_push_mark();
  	if (setexit() == 0)
  	    *v = globall(*v, gflag);
  	cleanup_pop_mark(omark);
  	resexit(osetexit);
  	if (haderr) {
  	    haderr = 0;
  	    NeedsRedraw = 1;
  	    return (-1);
  	}
  	if (*v == 0)
  	    return (0);
      }
      else
  	return (0);
  
      if (cmd) {
  	Char **av = *v, *p;
  	int fwd, i;
  
  	for (i = 0, fwd = 0; av[i] != NULL; i++) 
  	    if (!executable(NULL, av[i], 0)) {
  		fwd++;		
  		p = av[i];
  		av[i] = NULL;
  		xfree(p);
  	    }
  	    else if (fwd) 
  		av[i - fwd] = av[i];
  
  	if (fwd)
  	    av[i - fwd] = av[i];
      }
  
      return blklen(*v);
  } /* end t_glob */
  
  
  /* c_glob():
   * 	Return a list of commands that match the pattern
   */
  static int
  c_glob(Char ***v)
  {
      struct blk_buf av = BLK_BUF_INIT;
      struct Strbuf cmd = Strbuf_INIT, dir = Strbuf_INIT;
      Char *pat = **v;
      int flag;
  
      if (pat == NULL)
  	return (0);
  
      cleanup_push(&av, bb_cleanup);
      cleanup_push(&cmd, Strbuf_cleanup);
      cleanup_push(&dir, Strbuf_cleanup);
  
      tw_cmd_start(NULL, NULL);
      while (cmd.len = 0, tw_cmd_next(&cmd, &dir, &flag) != 0) {
  	Strbuf_terminate(&cmd);
  	if (Gmatch(cmd.s, pat))
  	    bb_append(&av, Strsave(cmd.s));
      }
      tw_dir_end();
      *v = bb_finish(&av);
      cleanup_ignore(&av);
      cleanup_until(&av);
  
      return av.len;
  } /* end c_glob */
  
  
  /* insert_meta():
   *      change the word before the cursor.
   *        cp must point to the start of the unquoted word.
   *        cpend to the end of it.
   *        word is the text that has to be substituted.
   *      strategy:
   *        try to keep all the quote characters of the user's input.
   *        change quote type only if necessary.
   */
  static int
  insert_meta(const Char *cp, const Char *cpend, const Char *word,
  	    int closequotes)
  {
      struct Strbuf buffer = Strbuf_INIT;
      Char *bptr;
      const Char *wptr;
      int in_sync = (cp != NULL);
      Char qu = 0;
      int ndel = (int) (cp ? cpend - cp : 0);
      Char w, wq;
      int res;
  
      for (wptr = word;;) {
  	if (cp >= cpend)
  	    in_sync = 0;
  	if (in_sync && !cmap(qu, _ESC) && cmap(*cp, _QF|_ESC))
  	    if (qu == 0 || qu == *cp) {
  		qu ^= *cp;
  		Strbuf_append1(&buffer, *cp++);
  		continue;
  	    }
  	w = *wptr;
  	if (w == 0)
  	    break;
  
  	wq = w & QUOTE;
  	w &= ~QUOTE;
  
  	if (cmap(w, _ESC | _QF))
  	    wq = QUOTE;		/* quotes are always quoted */
  
  	if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) {
  	    /* We have to unquote the character */
  	    in_sync = 0;
  	    if (cmap(qu, _ESC))
  		buffer.s[buffer.len - 1] = w;
  	    else {
  		Strbuf_append1(&buffer, qu);
  		Strbuf_append1(&buffer, w);
  		if (wptr[1] == 0)
  		    qu = 0;
  		else
  		    Strbuf_append1(&buffer, qu);
  	    }
  	} else if (qu && w == qu) {
  	    in_sync = 0;
  	    if (buffer.len != 0 && buffer.s[buffer.len - 1] == qu) {
  		/* User misunderstanding :) */
  		buffer.s[buffer.len - 1] = '\\';
  		Strbuf_append1(&buffer, w);
  		qu = 0;
  	    } else {
  		Strbuf_append1(&buffer, qu);
  		Strbuf_append1(&buffer, '\\');
  		Strbuf_append1(&buffer, w);
  		Strbuf_append1(&buffer, qu);
  	    }
  	}
  	else if (wq && qu == '\"' && tricky_dq(w)) {
  	    in_sync = 0;
  	    Strbuf_append1(&buffer, qu);
  	    Strbuf_append1(&buffer, '\\');
  	    Strbuf_append1(&buffer, w);
  	    Strbuf_append1(&buffer, qu);
  	} else if (wq &&
  		   ((!qu && (tricky(w) || (w == HISTSUB && buffer.len == 0))) ||
  		    (!cmap(qu, _ESC) && w == HIST))) {
  	    in_sync = 0;
  	    Strbuf_append1(&buffer, '\\');
  	    Strbuf_append1(&buffer, w);
  	} else {
  	    if (in_sync && *cp++ != w)
  		in_sync = 0;
  	    Strbuf_append1(&buffer, w);
  	}
  	wptr++;
  	if (cmap(qu, _ESC))
  	    qu = 0;
      }
      if (closequotes && qu && !cmap(qu, _ESC))
  	Strbuf_append1(&buffer, w);
      bptr = Strbuf_finish(&buffer);
      if (ndel)
  	DeleteBack(ndel);
      res = InsertStr(bptr);
      xfree(bptr);
      return res;
  } /* end insert_meta */
  
  
  
  /* is_prefix():
   *	return true if check matches initial chars in template
   *	This differs from PWB imatch in that if check is null
   *	it matches anything
   */
  static int
  is_prefix(Char *check, Char *template)
  {
      for (; *check; check++, template++)
  	if ((*check & TRIM) != (*template & TRIM))
  	    return (FALSE);
      return (TRUE);
  } /* end is_prefix */
  
  
  /* is_prefixmatch():
   *	return true if check matches initial chars in template
   *	This differs from PWB imatch in that if check is null
   *	it matches anything
   * and matches on shortening of commands
   */
  static int
  is_prefixmatch(Char *check, Char *template, int igncase)
  {
      Char MCH1, MCH2;
  
      for (; *check; check++, template++) {
  	if ((*check & TRIM) != (*template & TRIM)) {
              MCH1 = (*check & TRIM);
              MCH2 = (*template & TRIM);
              MCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1;
              MCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2;
              if (MCH1 != MCH2) {
                  if (!igncase && ((*check & TRIM) == '-' || 
  				 (*check & TRIM) == '.' ||
  				 (*check & TRIM) == '_')) {
                      MCH1 = MCH2 = (*check & TRIM);
                      if (MCH1 == '_') {
                          MCH2 = '-';
                      } else if (MCH1 == '-') {
                          MCH2 = '_';
                      }
                      for (;*template && (*template & TRIM) != MCH1 &&
  				       (*template & TRIM) != MCH2; template++)
  			continue;
                      if (!*template) {
  	                return (FALSE);
                      }
                  } else {
  	            return (FALSE);
                  }
              }
          }
      }
      return (TRUE);
  } /* end is_prefixmatch */
  
  
  /* is_suffix():
   *	Return true if the chars in template appear at the
   *	end of check, I.e., are it's suffix.
   */
  static int
  is_suffix(Char *check, Char *template)
  {
      Char *t, *c;
  
      t = Strend(template);
      c = Strend(check);
      for (;;) {
  	if (t == template)
  	    return 1;
  	if (c == check || (*--t & TRIM) != (*--c & TRIM))
  	    return 0;
      }
  } /* end is_suffix */
  
  
  /* ignored():
   *	Return true if this is an ignored item
   */
  static int
  ignored(Char *item)
  {
      struct varent *vp;
      Char **cp;
  
      if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
  	return (FALSE);
      for (; *cp != NULL; cp++)
  	if (is_suffix(item, *cp))
  	    return (TRUE);
      return (FALSE);
  } /* end ignored */
  
  
  
  /* starting_a_command():
   *	return true if the command starting at wordstart is a command
   */
  int
  starting_a_command(Char *wordstart, Char *inputline)
  {
      Char *ptr, *ncmdstart;
      int     count, bsl;
      static  Char
              cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
              cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
  
      /*
       * Find if the number of backquotes is odd or even.
       */
      for (ptr = wordstart, count = 0;
  	 ptr >= inputline;
  	 count += (*ptr-- == '`'))
  	continue;
      /*
       * 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 >= inputline; wordstart--) {
  	if (*wordstart == '\0')
  	    continue;
  	if (Strchr(ncmdstart, *wordstart)) {
  	    for (ptr = wordstart, bsl = 0; *(--ptr) == '\\'; bsl++);
  	    if (bsl & 1) {
  		wordstart--;
  		continue;
  	    } else
  		break;
  	}
  	/*
  	 * found white space
  	 */
  	if ((ptr = Strchr(cmdalive, *wordstart)) != NULL)
  	    count = 1;
  	if (count == 1 && !ptr)
  	    return (FALSE);
      }
  
      if (wordstart > inputline)
  	switch (*wordstart) {
  	case '&':		/* Look for >& */
  	    while (wordstart > inputline &&
  		   (*--wordstart == ' ' || *wordstart == '\t'))
  		continue;
  	    if (*wordstart == '>')
  		return (FALSE);
  	    break;
  	case '(':		/* check for foreach, if etc. */
  	    while (wordstart > inputline &&
  		   (*--wordstart == ' ' || *wordstart == '\t'))
  		continue;
  	    if (!iscmdmeta(*wordstart) &&
  		(*wordstart != ' ' && *wordstart != '\t'))
  		return (FALSE);
  	    break;
  	default:
  	    break;
  	}
      return (TRUE);
  } /* end starting_a_command */
  
  
  /* recognize():
   *	Object: extend what user typed up to an ambiguity.
   *	Algorithm:
   *	On first match, copy full item (assume it'll be the only match)
   *	On subsequent matches, shorten exp_name to the first
   *	character mismatch between exp_name and item.
   *	If we shorten it back to the prefix length, stop searching.
   */
  static int
  recognize(struct Strbuf *exp_name, const Char *item, size_t name_length,
  	  int numitems, int enhanced, int igncase)
  {
      Char MCH1, MCH2;
      Char *x;
      const Char *ent;
      size_t len = 0;
  
      if (numitems == 1) {	/* 1st match */
  	exp_name->len = 0;
  	Strbuf_append(exp_name, item);
  	Strbuf_terminate(exp_name);
  	return (0);
      }
      if (!enhanced && !igncase) {
  	for (x = exp_name->s, ent = item; *x && (*x & TRIM) == (*ent & TRIM);
  	     x++, ent++)
  	    len++;
      } else {
  	for (x = exp_name->s, ent = item; *x; x++, ent++) {
  	    MCH1 = *x & TRIM;
  	    MCH2 = *ent & TRIM;
              MCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1;
              MCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2;
  	    if (MCH1 != MCH2)
  		break;
  	    len++;
  	}
  	if (*x || !*ent)	/* Shorter or exact match */
  	    memcpy(exp_name->s, item, len * sizeof(*exp_name->s));
      }
      *x = '\0';		/* Shorten at 1st char diff */
      exp_name->len = x - exp_name->s;
      if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) && len == name_length)	/* Ambiguous to prefix? */
  	return (-1);	/* So stop now and save time */
      return (0);
  } /* end recognize */
  
  
  /* tw_collect_items():
   *	Collect items that match target.
   *	SPELL command:
   *		Returns the spelling distance of the closest match.
   *	else
   *		Returns the number of items found.
   *		If none found, but some ignored items were found,
   *		It returns the -number of ignored items.
   */
  static int
  tw_collect_items(COMMAND command, int looking, struct Strbuf *exp_dir,
  		 struct Strbuf *exp_name, Char *target, const Char *pat,
  		 int flags)
  {
      int done = FALSE;			 /* Search is done */
      int showdots;			 /* Style to show dot files */
      int nignored = 0;			 /* Number of fignored items */
      int numitems = 0;			 /* Number of matched items */
      size_t name_length = Strlen(target); /* Length of prefix (file name) */
      int exec_check = flags & TW_EXEC_CHK;/* need to check executability	*/
      int dir_check  = flags & TW_DIR_CHK; /* Need to check for directories */
      int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */
      int dir_ok     = flags & TW_DIR_OK;  /* Ignore directories? */
      int gpat       = flags & TW_PAT_OK;	 /* Match against a pattern */
      int ignoring   = flags & TW_IGN_OK;	 /* Use fignore? */
      int d = 4, nd;			 /* Spelling distance */
      Char *ptr;
      struct varent *vp;
      struct Strbuf buf = Strbuf_INIT, item = Strbuf_INIT;
      int enhanced = 0;
      int cnt = 0;
      int igncase = 0;
  
  
      flags = 0;
  
      showdots = DOT_NONE;
      if ((ptr = varval(STRlistflags)) != STRNULL)
  	while (*ptr) 
  	    switch (*ptr++) {
  	    case 'a':
  		showdots = DOT_ALL;
  		break;
  	    case 'A':
  		showdots = DOT_NOT;
  		break;
  	    default:
  		break;
  	    }
  
      cleanup_push(&item, Strbuf_cleanup);
      cleanup_push(&buf, Strbuf_cleanup);
      while (!done &&
  	   (item.len = 0,
  	    tw_next_entry[looking](&item, exp_dir, &flags) != 0)) {
  	Strbuf_terminate(&item);
  #ifdef TDEBUG
  	xprintf("item = %S\n", item.s);
  #endif
  	switch (looking) {
  	case TW_FILE:
  	case TW_DIRECTORY:
  	case TW_TEXT:
  	    /*
  	     * Don't match . files on null prefix match
  	     */
  	    if (showdots == DOT_NOT && (ISDOT(item.s) || ISDOTDOT(item.s)))
  		done = TRUE;
  	    if (name_length == 0 && item.s[0] == '.' && showdots == DOT_NONE)
  		done = TRUE;
  	    break;
  
  	case TW_COMMAND:
  #if defined(_UWIN) || defined(__CYGWIN__)
  	    /*
  	     * Turn foo.{exe,com,bat,cmd} into foo since UWIN's readdir returns
  	     * the file with the .exe, .com, .bat, .cmd extension
  	     */
  	    {
  		static const char *rext[] = { ".exe", ".bat", ".com", ".cmd" };
  		size_t exti = Strlen(item.s);
  
  		if (exti > 4) {
  		    char *ext = short2str(&item.s[exti -= 4]);
  		    size_t i;
  
  		    for (i = 0; i < sizeof(rext) / sizeof(rext[0]); i++)
  			if (strcasecmp(ext, rext[i]) == 0) {
  			    item.len = exti;
  			    Strbuf_terminate(&item);
  			    break;
  			}
  		}
  	    }
  #endif /* _UWIN || __CYGWIN__ */
  	    exec_check = flags & TW_EXEC_CHK;
  	    dir_ok = flags & TW_DIR_OK;
  	    break;
  
  	default:
  	    break;
  	}
  
  	if (done) {
  	    done = FALSE;
  	    continue;
  	}
  
  	switch (command) {
  
  	case SPELL:		/* correct the spelling of the last bit */
  	    if (name_length == 0) {/* zero-length word can't be misspelled */
  		exp_name->len = 0; /* (not trying is important for ~) */
  		Strbuf_terminate(exp_name);
  		d = 0;
  		done = TRUE;
  		break;
  	    }
  	    if (gpat && !Gmatch(item.s, pat))
  		break;
  	    /*
  	     * Swapped the order of the spdist() arguments as suggested
  	     * by eeide@asylum.cs.utah.edu (Eric Eide)
  	     */
  	    nd = spdist(target, item.s); /* test the item against original */
  	    if (nd <= d && nd != 4) {
  		if (!(exec_check && !executable(exp_dir->s, item.s, dir_ok))) {
  		    exp_name->len = 0;
  		    Strbuf_append(exp_name, item.s);
  		    Strbuf_terminate(exp_name);
  		    d = nd;
  		    if (d == 0)	/* if found it exactly */
  			done = TRUE;
  		}
  	    }
  	    else if (nd == 4) {
  	        if (spdir(exp_name, exp_dir->s, item.s, target)) {
  		    if (exec_check &&
  			!executable(exp_dir->s, exp_name->s, dir_ok))
  			break;
  #ifdef notdef
  		    /*
  		     * We don't want to stop immediately, because
  		     * we might find an exact/better match later.
  		     */
  		    d = 0;
  		    done = TRUE;
  #endif
  		    d = 3;
  		}
  	    }
  	    break;
  
  	case LIST:
  	case RECOGNIZE:
  	case RECOGNIZE_ALL:
  	case RECOGNIZE_SCROLL:
  
  	    if ((vp = adrof(STRcomplete)) != NULL && vp->vec != NULL) {
  		Char **cp;
  		for (cp = vp->vec; *cp; cp++) {
  		    if (Strcmp(*cp, STRigncase) == 0)
  			igncase = 1;
  		    if (Strcmp(*cp, STRenhance) == 0)
  			enhanced = 1;
  		}
  	    }
  
  	    if (enhanced || igncase) {
  	        if (!is_prefixmatch(target, item.s, igncase))
  		    break;
       	    } else {
  	        if (!is_prefix(target, item.s))
  		    break;
  	    }
  
  	    if (exec_check && !executable(exp_dir->s, item.s, dir_ok))
  		break;
  
  	    if (dir_check && !isadirectory(exp_dir->s, item.s))
  		break;
  
  	    if (text_check && isadirectory(exp_dir->s, item.s))
  		break;
  
  	    /*
  	     * Only pattern match directories if we're checking
  	     * for directories.
  	     */
  	    if (gpat && !Gmatch(item.s, pat) &&
  		(dir_check || !isadirectory(exp_dir->s, item.s)))
  		    break;
  
  	    /*
  	     * Remove duplicates in command listing and completion
               * AFEB added code for TW_LOGNAME and TW_USER cases
  	     */
  	    if (looking == TW_COMMAND || looking == TW_LOGNAME
  		|| looking == TW_USER || command == LIST) {
  		buf.len = 0;
  		Strbuf_append(&buf, item.s);
  		switch (looking) {
  		case TW_COMMAND:
  		    if (!(dir_ok && exec_check))
  			break;
  		    if (filetype(exp_dir->s, item.s) == '/')
  			Strbuf_append1(&buf, '/');
  		    break;
  
  		case TW_FILE:
  		case TW_DIRECTORY:
  		    Strbuf_append1(&buf, filetype(exp_dir->s, item.s));
  		    break;
  
  		default:
  		    break;
  		}
  		Strbuf_terminate(&buf);
  		if ((looking == TW_COMMAND || looking == TW_USER
                       || looking == TW_LOGNAME) && tw_item_find(buf.s))
  		    break;
  		else {
  		    /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
  		    tw_item_add(&buf);
  		    if (command == LIST)
  			numitems++;
  		}
  	    }
  		    
  	    if (command == RECOGNIZE || command == RECOGNIZE_ALL ||
  		command == RECOGNIZE_SCROLL) {
  		if (ignoring && ignored(item.s)) {
  		    nignored++;
  		    break;
  		}
  		else if (command == RECOGNIZE_SCROLL) {
  		    add_scroll_tab(item.s);
  		    cnt++;
  		}
  
  		if (match_unique_match || is_set(STRrecexact)) {
  		    if (StrQcmp(target, item.s) == 0) {	/* EXACT match */
  			exp_name->len = 0;
  			Strbuf_append(exp_name, item.s);
  			Strbuf_terminate(exp_name);
  			numitems = 1;	/* fake into expanding */
  			non_unique_match = TRUE;
  			done = TRUE;
  			break;
  		    }
  		}
  		if (recognize(exp_name, item.s, name_length, ++numitems,
  		    enhanced, igncase))
  		    if (command != RECOGNIZE_SCROLL)
  			done = TRUE;
  		if (enhanced && exp_name->len < name_length) {
  		    exp_name->len = 0;
  		    Strbuf_append(exp_name, target);
  		    Strbuf_terminate(exp_name);
  		}
  	    }
  	    break;
  
  	default:
  	    break;
  	}
  #ifdef TDEBUG
  	xprintf("done item = %S\n", item.s);
  #endif
      }
      cleanup_until(&item);
  
      if (command == RECOGNIZE_SCROLL) {
  	if ((cnt <= curchoice) || (curchoice == -1)) {
  	    curchoice = -1;
  	    nignored = 0;
  	    numitems = 0;
  	} else if (numitems > 1) {
  	    if (curchoice < -1)
  		curchoice = cnt - 1;
  	    choose_scroll_tab(exp_name, cnt);
  	    numitems = 1;
  	}
      }
      free_scroll_tab();
  
      if (command == SPELL)
  	return d;
      else {
  	if (ignoring && numitems == 0 && nignored > 0) 
  	    return -nignored;
  	else
  	    return numitems;
      }
  }
  
  
  /* tw_suffix():
   *	Find and return the appropriate suffix character
   */
  /*ARGSUSED*/
  static Char
  tw_suffix(int looking, const Char *exp_dir, Char *exp_name)
  {
      Char *ptr;
      struct varent *vp;
  
      (void) strip(exp_name);
  
      switch (looking) {
  
      case TW_LOGNAME:
  	return '/';
  
      case TW_VARIABLE:
  	/*
  	 * Don't consider array variables or empty variables
  	 */
  	if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) {
  	    if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' ||
  		vp->vec[1] != NULL) 
  		return ' ';
  	}
  	else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0')
  	    return ' ';
  
  	return isadirectory(exp_dir, ptr) ? '/' : ' ';
  
  
      case TW_DIRECTORY:
  	return '/';
  
      case TW_COMMAND:
      case TW_FILE:
  	return isadirectory(exp_dir, exp_name) ? '/' : ' ';
  
      case TW_ALIAS:
      case TW_VARLIST:
      case TW_WORDLIST:
      case TW_SHELLVAR:
      case TW_ENVVAR:
      case TW_USER:
      case TW_BINDING:
      case TW_LIMIT:
      case TW_SIGNAL:
      case TW_JOB:
      case TW_COMPLETION:
      case TW_TEXT:
      case TW_GRPNAME:
  	return ' ';
  
      default:
  	return '\0';
      }
  } /* end tw_suffix */
  
  
  /* tw_fixword():
   *	Repair a word after a spalling or a recognizwe
   */
  static void
  tw_fixword(int looking, struct Strbuf *word, Char *dir, Char *exp_name)
  {
      Char *ptr;
  
      switch (looking) {
      case TW_LOGNAME:
  	word->len = 0;
  	Strbuf_append1(word, '~');
  	break;
  
      case TW_VARIABLE:
  	if ((ptr = Strrchr(word->s, '$')) != NULL) {
  	    word->len = ptr + 1 - word->s; /* Delete after the dollar */
  	} else
  	    word->len = 0;
  	break;
  
      case TW_DIRECTORY:
      case TW_FILE:
      case TW_TEXT:
  	word->len = 0;
  	Strbuf_append(word, dir);		/* put back dir part */
  	break;
  
      default:
  	word->len = 0;
  	break;
      }
  
      (void) quote(exp_name);
      Strbuf_append(word, exp_name);		/* add extended name */
      Strbuf_terminate(word);
  } /* end tw_fixword */
  
  
  /* tw_collect():
   *	Collect items. Return -1 in case we were interrupted or
   *	the return value of tw_collect
   *	This is really a wrapper for tw_collect_items, serving two
   *	purposes:
   *		1. Handles interrupt cleanups.
   *		2. Retries if we had no matches, but there were ignored matches
   */
  static int
  tw_collect(COMMAND command, int looking, struct Strbuf *exp_dir,
  	   struct Strbuf *exp_name, Char *target, Char *pat, int flags,
  	   DIR *dir_fd)
  {
      volatile int ni;
      jmp_buf_t osetexit;
  
  #ifdef TDEBUG
      xprintf("target = %S\n", target);
  #endif
      ni = 0;
      getexit(osetexit);
      for (;;) {
  	volatile size_t omark;
  
  	(*tw_start_entry[looking])(dir_fd, pat);
  	InsideCompletion = 1;
  	if (setexit()) {
  	    cleanup_pop_mark(omark);
  	    resexit(osetexit);
  	    /* interrupted, clean up */
  	    haderr = 0;
  	    ni = -1; /* flag error */
  	    break;
  	}
  	omark = cleanup_push_mark();
  	ni = tw_collect_items(command, looking, exp_dir, exp_name, target, pat,
  			      ni >= 0 ? flags : flags & ~TW_IGN_OK);
  	cleanup_pop_mark(omark);
  	resexit(osetexit);
          if (ni >= 0)
  	    break;
      }
      InsideCompletion = 0;
  #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
      /* Compiler bug? (from PWP) */
      if ((looking == TW_LOGNAME) || (looking == TW_USER))
  	tw_logname_end();
      else if (looking == TW_GRPNAME)
  	tw_grpname_end();
      else
  	tw_dir_end();
  #else /* !(SOLARIS2 && i386 && !__GNUC__) */
      (*tw_end_entry[looking])();
  #endif /* !(SOLARIS2 && i386 && !__GNUC__) */
      return(ni);
  } /* end tw_collect */
  
  
  /* tw_list_items():
   *	List the items that were found
   *
   *	NOTE instead of looking at numerical vars listmax and listmaxrows
   *	we can look at numerical var listmax, and have a string value
   *	listmaxtype (or similar) than can have values 'items' and 'rows'
   *	(by default interpreted as 'items', for backwards compatibility)
   */
  static void
  tw_list_items(int looking, int numitems, int list_max)
  {
      Char *ptr;
      int max_items = 0;
      int max_rows = 0;
  
      if (numitems == 0)
  	return;
  
      if ((ptr = varval(STRlistmax)) != STRNULL) {
  	while (*ptr) {
  	    if (!Isdigit(*ptr)) {
  		max_items = 0;
  		break;
  	    }
  	    max_items = max_items * 10 + *ptr++ - '0';
  	}
  	if ((max_items > 0) && (numitems > max_items) && list_max)
  	    max_items = numitems;
  	else
  	    max_items = 0;
      }
  
      if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) {
  	int rows;
  
  	while (*ptr) {
  	    if (!Isdigit(*ptr)) {
  		max_rows = 0;
  		break;
  	    }
  	    max_rows = max_rows * 10 + *ptr++ - '0';
  	}
  	if (max_rows != 0 && looking != TW_JOB)
  	    rows = find_rows(tw_item_get(), numitems, TRUE);
  	else
  	    rows = numitems; /* underestimate for lines wider than the termH */
  	if ((max_rows > 0) && (rows > max_rows) && list_max)
  	    max_rows = rows;
  	else
  	    max_rows = 0;
      }
  
  
      if (max_items || max_rows) {
  	char    	 tc, *sname;
  	const char	*name;
  	int maxs;
  
  	if (max_items) {
  	    name = CGETS(30, 5, "items");
  	    maxs = max_items;
  	}
  	else {
  	    name = CGETS(30, 6, "rows");
  	    maxs = max_rows;
  	}
  
  	sname = strsave(name);
  	cleanup_push(sname, xfree);
  	xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "),
  		maxs, sname);
  	cleanup_until(sname);
  	flush();
  	/* We should be in Rawmode here, so no \n to catch */
  	(void) xread(SHIN, &tc, 1);
  	xprintf("%c\r\n", tc);	/* echo the char, do a newline */
  	/*
  	 * Perhaps we should use the yesexpr from the
  	 * actual locale
  	 */
  	if (strchr(CGETS(30, 13, "Yy"), tc) == NULL)
  	    return;
      }
  
      if (looking != TW_SIGNAL)
  	qsort(tw_item_get(), numitems, sizeof(Char *), fcompare);
      if (looking != TW_JOB)
  	print_by_column(STRNULL, tw_item_get(), numitems, TRUE);
      else {
  	/*
  	 * print one item on every line because jobs can have spaces
  	 * and it is confusing.
  	 */
  	int i;
  	Char **w = tw_item_get();
  
  	for (i = 0; i < numitems; i++) {
  	    xprintf("%S", w[i]);
  	    if (Tty_raw_mode)
  		xputchar('\r');
  	    xputchar('\n');
  	}
      }
  } /* end tw_list_items */
  
  
  /* t_search():
   *	Perform a RECOGNIZE, LIST or SPELL 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(struct Strbuf *word, COMMAND command, int looking, int list_max,
  	 Char *pat, eChar suf)
  {
      int     numitems,			/* Number of items matched */
  	    flags = 0,			/* search flags */
  	    gpat = pat[0] != '\0',	/* Glob pattern search */
  	    res;			/* Return value */
      struct Strbuf exp_dir = Strbuf_INIT;/* dir after ~ expansion */
      struct Strbuf dir = Strbuf_INIT;	/* /x/y/z/ part in /x/y/z/f */
      struct Strbuf exp_name = Strbuf_INIT;/* the recognized (extended) */
      Char   *name,			/* f part in /d/d/d/f name */
             *target;			/* Target to expand/correct/list */
      DIR    *dir_fd = NULL;
  
      /*
       * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
       * dump core when interrupted
       */
      tw_item_free();
  
      non_unique_match = FALSE;	/* See the recexact code below */
  
      extract_dir_and_name(word->s, &dir, &name);
      cleanup_push(&dir, Strbuf_cleanup);
      cleanup_push(&name, xfree_indirect);
  
      /*
       *  SPECIAL HARDCODED COMPLETIONS:
       *    foo$variable                -> TW_VARIABLE
       *    ~user                       -> TW_LOGNAME
       *
       */
      if ((*word->s == '~') && (Strchr(word->s, '/') == NULL)) {
  	looking = TW_LOGNAME;
  	target = name;
  	gpat = 0;	/* Override pattern mechanism */
      }
      else if ((target = Strrchr(name, '$')) != 0 && 
  	     (Strchr(name, '/') == NULL)) {
  	target++;
  	looking = TW_VARIABLE;
  	gpat = 0;	/* Override pattern mechanism */
      }
      else
  	target = name;
  
      /*
       * Try to figure out what we should be looking for
       */
      if (looking & TW_PATH) {
  	gpat = 0;	/* pattern holds the pathname to be used */
  	Strbuf_append(&exp_dir, pat);
  	if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/')
  	    Strbuf_append1(&exp_dir, '/');
  	Strbuf_append(&exp_dir, dir.s);
      }
      Strbuf_terminate(&exp_dir);
      cleanup_push(&exp_dir, Strbuf_cleanup);
  
      switch (looking & ~TW_PATH) {
      case TW_NONE:
  	res = -1;
  	goto err_dir;
  
      case TW_ZERO:
  	looking = TW_FILE;
  	break;
  
      case TW_COMMAND:
  	if (Strchr(word->s, '/') || (looking & TW_PATH)) {
  	    looking = TW_FILE;
  	    flags |= TW_EXEC_CHK;
  	    flags |= TW_DIR_OK;
  	}
  #ifdef notdef
  	/* PWP: don't even bother when doing ALL of the commands */
  	if (looking == TW_COMMAND && word->len == 0) {
  	    res = -1;
  	    goto err_dir;
  	}
  #endif
  	break;
  
  
      case TW_VARLIST:
      case TW_WORDLIST:
  	gpat = 0;	/* pattern holds the name of the variable */
  	break;
  
      case TW_EXPLAIN:
  	if (command == LIST && pat != NULL) {
  	    xprintf("%S", pat);
  	    if (Tty_raw_mode)
  		xputchar('\r');
  	    xputchar('\n');
  	}
  	res = 2;
  	goto err_dir;
  
      default:
  	break;
      }
  
      /*
       * let fignore work only when we are not using a pattern
       */
      flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK;
  
  #ifdef TDEBUG
      xprintf(CGETS(30, 8, "looking = %d\n"), looking);
  #endif
  
      switch (looking) {
  	Char *user_name;
  
      case TW_ALIAS:
      case TW_SHELLVAR:
      case TW_ENVVAR:
      case TW_BINDING:
      case TW_LIMIT:
      case TW_SIGNAL:
      case TW_JOB:
      case TW_COMPLETION:
      case TW_GRPNAME:
  	break;
  
  
      case TW_VARIABLE:
  	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
  	    goto err_dir;
  	break;
  
      case TW_DIRECTORY:
  	flags |= TW_DIR_CHK;
  
  #ifdef notyet
  	/*
  	 * This is supposed to expand the directory stack.
  	 * Problems:
  	 * 1. Slow
  	 * 2. directories with the same name
  	 */
  	flags |= TW_DIR_OK;
  #endif
  #ifdef notyet
  	/*
  	 * Supposed to do delayed expansion, but it is inconsistent
  	 * from a user-interface point of view, since it does not
  	 * immediately obey addsuffix
  	 */
  	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
  	    goto err_dir;
  	if (isadirectory(exp_dir.s, name)) {
  	    if (exp_dir.len != 0 || name[0] != '\0') {
  		Strbuf_append(&dir, name);
  		if (dir.s[dir.len - 1] != '/')
  		    Strbuf_append1(&dir, '/');
  		Strbuf_terminate(&dir);
  		if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
  		    goto err_dir;
  		if (word->len != 0 && word->s[word->len - 1] != '/') {
  		    Strbuf_append1(word, '/');
  		    Strbuf_terminate(word);
  		}
  		name[0] = '\0';
  	    }
  	}
  #endif
  	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
  	    goto err_dir;
  	break;
  
      case TW_TEXT:
  	flags |= TW_TEXT_CHK;
  	/*FALLTHROUGH*/
      case TW_FILE:
  	if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0)
  	    goto err_dir;
  	break;
  
      case TW_PATH | TW_TEXT:
      case TW_PATH | TW_FILE:
      case TW_PATH | TW_DIRECTORY:
      case TW_PATH | TW_COMMAND:
  	if ((dir_fd = opendir(short2str(exp_dir.s))) == NULL) {
   	    if (command == RECOGNIZE)
   		xprintf("\n");
   	    xprintf("%S: %s", exp_dir.s, strerror(errno));
   	    if (command != RECOGNIZE)
   		xprintf("\n");
   	    NeedsRedraw = 1;
  	    res = -1;
  	    goto err_dir;
  	}
  	if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') {
  	    Strbuf_append1(&exp_dir, '/');
  	    Strbuf_terminate(&exp_dir);
  	}
  
  	looking &= ~TW_PATH;
  
  	switch (looking) {
  	case TW_TEXT:
  	    flags |= TW_TEXT_CHK;
  	    break;
  
  	case TW_FILE:
  	    break;
  
  	case TW_DIRECTORY:
  	    flags |= TW_DIR_CHK;
  	    break;
  
  	case TW_COMMAND:
  	    xfree(name);
  	    target = name = Strsave(word->s);	/* so it can match things */
  	    break;
  
  	default:
  	    abort();	/* Cannot happen */
  	    break;
  	}
  	break;
  
      case TW_LOGNAME:
  	user_name = word->s + 1;
  	goto do_user;
  
  	/*FALLTHROUGH*/
      case TW_USER:
  	user_name = word->s;
      do_user:
  	/*
  	 * Check if the spelling was already correct
  	 * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
  	 */
  	if (command == SPELL && xgetpwnam(short2str(user_name)) != NULL) {
  #ifdef YPBUGS
  	    fix_yp_bugs();
  #endif /* YPBUGS */
  	    res = 0;
  	    goto err_dir;
  	}
  	xfree(name);
  	target = name = Strsave(user_name);
  	break;
  
      case TW_COMMAND:
      case TW_VARLIST:
      case TW_WORDLIST:
  	target = name = Strsave(word->s);	/* so it can match things */
  	break;
  
      default:
  	xprintf(CGETS(30, 9,
  		"\n%s internal error: I don't know what I'm looking for!\n"),
  		progname);
  	NeedsRedraw = 1;
  	res = -1;
  	goto err_dir;
      }
  
      cleanup_push(&exp_name, Strbuf_cleanup);
      numitems = tw_collect(command, looking, &exp_dir, &exp_name, target, pat,
  			  flags, dir_fd);
      if (numitems == -1)
  	goto end;
  
      switch (command) {
      case RECOGNIZE:
      case RECOGNIZE_ALL:
      case RECOGNIZE_SCROLL:
  	if (numitems <= 0) 
  	    break;
  
  	Strbuf_terminate(&exp_name);
  	tw_fixword(looking, word, dir.s, exp_name.s);
  
  	if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) {
  	    switch (suf) {
  	    case 0: 	/* Automatic suffix */
  		Strbuf_append1(word,
  			       tw_suffix(looking, exp_dir.s, exp_name.s));
  		break;
  
  	    case CHAR_ERR:	/* No suffix */
  		break;
  
  	    default:	/* completion specified suffix */
  		Strbuf_append1(word, suf);
  		break;
  	    }
  	    Strbuf_terminate(word);
  	}
  	break;
  
      case LIST:
  	tw_list_items(looking, numitems, list_max);
  	tw_item_free();
  	break;
  
      case SPELL:
  	Strbuf_terminate(&exp_name);
  	tw_fixword(looking, word, dir.s, exp_name.s);
  	break;
  
      default:
  	xprintf("Bad tw_command\n");
  	numitems = 0;
      }
   end:
      res = numitems;
   err_dir:
      cleanup_until(&dir);
      return res;
  } /* end t_search */
  
  
  /* extract_dir_and_name():
   * 	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(const Char *path, struct Strbuf *dir, Char **name)
  {
      Char *p;
  
      p = Strrchr(path, '/');
  #ifdef WINNT_NATIVE
      if (p == NULL)
  	p = Strrchr(path, ':');
  #endif /* WINNT_NATIVE */
      if (p == NULL)
  	*name = Strsave(path);
      else {
  	p++;
  	*name = Strsave(p);
  	Strbuf_appendn(dir, path, p - path);
      }
      Strbuf_terminate(dir);
  } /* end extract_dir_and_name */
  
  
  /* dollar():
   * 	expand "/$old1/$old2/old3/"
   * 	to "/value_of_old1/value_of_old2/old3/"
   */
  Char *
  dollar(const Char *old)
  {
      struct Strbuf buf = Strbuf_INIT;
  
      while (*old) {
  	if (*old != '$')
  	    Strbuf_append1(&buf, *old++);
  	else {
  	    if (expdollar(&buf, &old, QUOTE) == 0) {
  		xfree(buf.s);
  		return NULL;
  	    }
  	}
      }
      return Strbuf_finish(&buf);
  } /* end dollar */
  
  
  /* tilde():
   * 	expand ~person/foo to home_directory_of_person/foo
   *	or =<stack-entry> to <dir in stack entry>
   */
  static int
  tilde(struct Strbuf *new, Char *old)
  {
      Char *o, *p;
  
      new->len = 0;
      switch (old[0]) {
      case '~': {
  	Char *name, *home;
  
  	old++;
  	for (o = old; *o && *o != '/'; o++)
  	    continue;
  	name = Strnsave(old, o - old);
  	home = gethdir(name);
  	xfree(name);
  	if (home == NULL)
  	    goto err;
  	Strbuf_append(new, home);
  	xfree(home);
  	/* If the home directory expands to "/", we do
  	 * not want to create "//" by appending a slash from o.
  	 */
  	if (new->s[0] == '/' && new->len == 1 && *o == '/')
  	    ++o;
  	Strbuf_append(new, o);
  	break;
      }
  
      case '=':
  	if ((p = globequal(old)) == NULL)
  	    goto err;
  	if (p != old) {
  	    Strbuf_append(new, p);
  	    xfree(p);
  	    break;
  	}
  	/*FALLTHROUGH*/
  
      default:
  	Strbuf_append(new, old);
  	break;
      }
      Strbuf_terminate(new);
      return 0;
  
   err:
      Strbuf_terminate(new);
      return -1;
  } /* end tilde */
  
  
  /* expand_dir():
   *	Open the directory given, expanding ~user and $var
   *	Optionally normalize the path given
   */
  static int
  expand_dir(const Char *dir, struct Strbuf *edir, DIR **dfd, COMMAND cmd)
  {
      Char   *nd = NULL;
      Char *tdir;
  
      tdir = dollar(dir);
      cleanup_push(tdir, xfree);
      if (tdir == NULL ||
  	(tilde(edir, tdir) != 0) ||
  	!(nd = dnormalize(edir->len ? edir->s : STRdot,
  			  symlinks == SYM_IGNORE || symlinks == SYM_EXPAND)) ||
  	((*dfd = opendir(short2str(nd))) == NULL)) {
  	xfree(nd);
  	if (cmd == SPELL || SearchNoDirErr) {
  	    cleanup_until(tdir);
  	    return (-2);
  	}
  	/*
  	 * From: Amos Shapira <amoss@cs.huji.ac.il>
  	 * Print a better message when completion fails
  	 */
  	xprintf("\n%S %s\n", edir->len ? edir->s : (tdir ? tdir : dir),
  		(errno == ENOTDIR ? CGETS(30, 10, "not a directory") :
  		(errno == ENOENT ? CGETS(30, 11, "not found") :
  		 CGETS(30, 12, "unreadable"))));
  	NeedsRedraw = 1;
  	cleanup_until(tdir);
  	return (-1);
      }
      cleanup_until(tdir);
      if (nd) {
  	if (*dir != '\0') {
  	    int slash;
  
  	    /*
  	     * Copy and append a / if there was one
  	     */
  	    slash = edir->len != 0 && edir->s[edir->len - 1] == '/';
  	    edir->len = 0;
  	    Strbuf_append(edir, nd);
  	    if (slash != 0 && edir->s[edir->len - 1] != '/')
  		Strbuf_append1(edir, '/');
  	    Strbuf_terminate(edir);
  	}
  	xfree(nd);
      }
      return 0;
  } /* end expand_dir */
  
  
  /* nostat():
   *	Returns true if the directory should not be stat'd,
   *	false otherwise.
   *	This way, things won't grind to a halt when you complete in /afs
   *	or very large directories.
   */
  static int
  nostat(Char *dir)
  {
      struct varent *vp;
      Char **cp;
  
      if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL)
  	return FALSE;
      for (; *cp != NULL; cp++) {
  	if (Strcmp(*cp, STRstar) == 0)
  	    return TRUE;
  	if (Gmatch(dir, *cp))
  	    return TRUE;
      }
      return FALSE;
  } /* end nostat */
  
  
  /* filetype():
   *	Return a character that signifies a filetype
   *	symbology from 4.3 ls command.
   */
  static  Char
  filetype(Char *dir, Char *file)
  {
      if (dir) {
  	Char *path;
  	char   *ptr;
  	struct stat statb;
  
  	if (nostat(dir)) return(' ');
  
  	path = Strspl(dir, file);
  	ptr = short2str(path);
  	xfree(path);
  
  	if (lstat(ptr, &statb) != -1) {
  #ifdef S_ISLNK
  	    if (S_ISLNK(statb.st_mode)) {	/* Symbolic link */
  		if (adrof(STRlistlinks)) {
  		    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
  	    {
  		struct stat hpstatb;
  		char *p2;
  
  		p2 = strspl(ptr, "+");	/* Must append a '+' and re-stat(). */
  		if ((stat(p2, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode)) {
  		    xfree(p2);
  		    return ('+');	/* Context Dependent Files [hpux] */
  		}
  		xfree(p2);
  	    }
  #endif
  #ifdef S_ISNWK
  	    if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
  		return (':');
  #endif
  #ifdef S_ISCHR
  	    if (S_ISCHR(statb.st_mode))	/* char device */
  		return ('%');
  #endif
  #ifdef S_ISBLK
  	    if (S_ISBLK(statb.st_mode))	/* block device */
  		return ('#');
  #endif
  #ifdef S_ISDIR
  	    if (S_ISDIR(statb.st_mode))	/* normal Directory */
  		return ('/');
  #endif
  	    if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
  		return ('*');
  	}
      }
      return (' ');
  } /* end filetype */
  
  
  /* isadirectory():
   *	Return trus if the file is a directory
   */
  static int
  isadirectory(const Char *dir, const Char *file)
       /* return 1 if dir/file is a directory */
       /* uses stat rather than lstat to get dest. */
  {
      if (dir) {
  	Char *path;
  	char *cpath;
  	struct stat statb;
  
  	path = Strspl(dir, file);
  	cpath = short2str(path);
  	xfree(path);
  	if (stat(cpath, &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;
  } /* end isadirectory */
  
  
  
  /* find_rows():
   * 	Return how many rows needed to print sorted down columns
   */
  static int
  find_rows(Char *items[], int count, int no_file_suffix)
  {
      int i, columns, rows;
      unsigned int maxwidth = 0;
  
      for (i = 0; i < count; i++)	/* find widest string */
  	maxwidth = max(maxwidth, (unsigned int) 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;
  
      return rows;
  } /* end rows_needed_by_print_by_column */
  
  
  /* print_by_column():
   * 	Print sorted down columns or across columns when the first
   *	word of $listflags shell variable contains 'x'.
   *
   */
  void
  print_by_column(Char *dir, Char *items[], int count, int no_file_suffix)
  {
      int i, r, c, columns, rows;
      size_t w;
      unsigned int wx, maxwidth = 0;
      Char *val;
      int across;
  
      lbuffed = 0;		/* turn off line buffering */
  
      
      across = ((val = varval(STRlistflags)) != STRNULL) && 
  	     (Strchr(val, 'x') != NULL);
  
      for (i = 0; i < count; i++)	{ /* find widest string */
  	maxwidth = max(maxwidth, (unsigned int) NLSStringWidth(items[i]));
      }
  
      maxwidth += no_file_suffix ? 1 : 2;	/* for the file tag and space */
      columns = TermH / maxwidth;		/* PWP: terminal size change */
      if (!columns || !isatty(didfds ? 1 : SHOUT))
  	columns = 1;
      rows = (count + (columns - 1)) / columns;
  
      i = -1;
      for (r = 0; r < rows; r++) {
  	for (c = 0; c < columns; c++) {
  	    i = across ? (i + 1) : (c * rows + r);
  
  	    if (i < count) {
  		wx = 0;
  		w = Strlen(items[i]);
  
  #ifdef COLOR_LS_F
  		if (no_file_suffix) {
  		    /* Print the command name */
  		    Char f = items[i][w - 1];
  		    items[i][w - 1] = 0;
  		    print_with_color(items[i], w - 1, f);
  		    items[i][w - 1] = f;
  		}
  		else {
  		    /* Print filename followed by '/' or '*' or ' ' */
  		    print_with_color(items[i], w, filetype(dir, items[i]));
  		    wx++;
  		}
  #else /* ifndef COLOR_LS_F */
  		if (no_file_suffix) {
  		    /* Print the command name */
  		    xprintf("%S", items[i]);
  		}
  		else {
  		    /* Print filename followed by '/' or '*' or ' ' */
  		    xprintf("%-S%c", items[i], filetype(dir, items[i]));
  		    wx++;
  		}
  #endif /* COLOR_LS_F */
  
  		if (c < (columns - 1)) {	/* Not last column? */
  		    w = NLSStringWidth(items[i]) + wx;
  		    for (; w < maxwidth; w++)
  			xputchar(' ');
  		}
  	    }
  	    else if (across)
  		break;
  	}
  	if (Tty_raw_mode)
  	    xputchar('\r');
  	xputchar('\n');
      }
  
      lbuffed = 1;		/* turn back on line buffering */
      flush();
  } /* end print_by_column */
  
  
  /* StrQcmp():
   *	Compare strings ignoring the quoting chars
   */
  int
  StrQcmp(const Char *str1, const Char *str2)
  {
      for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM); 
  	 str1++, str2++)
  	continue;
      /*
       * 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));
  } /* end StrQcmp */
  
  
  /* fcompare():
   * 	Comparison routine for qsort, (Char **, Char **)
   */
  int
  fcompare(const void *xfile1, const void *xfile2)
  {
      const Char *const *file1 = xfile1, *const *file2 = xfile2;
  
      return collate(*file1, *file2);
  } /* end fcompare */
  
  
  /* catn():
   *	Concatenate src onto tail of des.
   *	Des is a string whose maximum length is count.
   *	Always null terminate.
   */
  void
  catn(Char *des, const Char *src, int count)
  {
      while (*des && --count > 0)
  	des++;
      while (--count > 0)
  	if ((*des++ = *src++) == 0)
  	    return;
      *des = '\0';
  } /* end catn */
  
  
  /* copyn():
   *	 like strncpy but always leave room for trailing \0
   *	 and always null terminate.
   */
  void
  copyn(Char *des, const Char *src, size_t count)
  {
      while (--count != 0)
  	if ((*des++ = *src++) == 0)
  	    return;
      *des = '\0';
  } /* end copyn */
  
  
  /* tgetenv():
   *	like it's normal string counter-part
   */
  Char *
  tgetenv(Char *str)
  {
      Char  **var;
      size_t  len;
      int     res;
  
      len = Strlen(str);
      /* Search the STR_environ for the entry matching str. */
      for (var = STR_environ; var != NULL && *var != NULL; var++)
  	if (Strlen(*var) >= len && (*var)[len] == '=') {
  	  /* Temporarily terminate the string so we can copy the variable
  	     name. */
  	    (*var)[len] = '\0';
  	    res = StrQcmp(*var, str);
  	    /* Restore the '=' and return a pointer to the value of the
  	       environment variable. */
  	    (*var)[len] = '=';
  	    if (res == 0)
  		return (&((*var)[len + 1]));
  	}
      return (NULL);
  } /* end tgetenv */
  
  
  struct scroll_tab_list *scroll_tab = 0;
  
  static void
  add_scroll_tab(Char *item)
  {
      struct scroll_tab_list *new_scroll;
  
      new_scroll = xmalloc(sizeof(struct scroll_tab_list));
      new_scroll->element = Strsave(item);
      new_scroll->next = scroll_tab;
      scroll_tab = new_scroll;
  }
  
  static void
  choose_scroll_tab(struct Strbuf *exp_name, int cnt)
  {
      struct scroll_tab_list *loop;
      int tmp = cnt;
      Char **ptr;
  
      ptr = xmalloc(sizeof(Char *) * cnt);
      cleanup_push(ptr, xfree);
  
      for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next)
  	ptr[--tmp] = loop->element;
  
      qsort(ptr, cnt, sizeof(Char *), fcompare);
  
      exp_name->len = 0;
      Strbuf_append(exp_name, ptr[curchoice]);
      Strbuf_terminate(exp_name);
      cleanup_until(ptr);
  }
  
  static void
  free_scroll_tab(void)
  {
      struct scroll_tab_list *loop;
  
      while(scroll_tab) {
  	loop = scroll_tab;
  	scroll_tab = scroll_tab->next;
  	xfree(loop->element);
  	xfree(loop);
      }
  }
  