DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - download
Index: ┃ T c

⟦154c209f0⟧ TextFile

    Length: 29213 (0x721d)
    Types: TextFile
    Names: »complete.c«

Derivation

└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
    └─ ⟦this⟧ »EUUGD11/euug-87hel/sec8/mcp/src/complete.c« 

TextFile

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <setjmp.h>
#include <strings.h>
#include <errno.h>
#include "sysdep.h"
#include "macros.h"
#include "mem.h"
#include "history.h"
#include "lists.h"

#ifdef BSD4_3
char	*getwd();
#endif

#define swap(a, b)	{ char t; t=a; a=b; b=t; }
#define SCMPN(a, b)	!strncmp((a), (b), strlen(a))

extern	jmp_buf in_continue;
extern	char Working_Directory[];

char	*BACKSPACE = "\b \b", *dirof(), *fileof(), *pathcomplete();
static	char line[BUFSIZ+1], hline[BUFSIZ+1];

struct	hist currh = { hline, (char *)0, 0, 0, 0, 0, (struct list *)0 };
struct	list History;

static	char *exprv[] = { 0, 0 };

#ifdef SENDMAIL
extern	struct list Aliases;
#endif
#ifdef HELPDIR
extern	struct list Terms;
#endif
extern	struct list AllCommands, Commands, Classes, Groups, Sigs;
extern	struct list Null_List, Users, Vigs, Ranges;
extern	int DevTty;

/*
 * Get the last part of a command.
 */
char *
tail(s)
char *s;

{
	char *p, *sp;

	sp = rindex(s, ' ');
	if (!sp) {
	    p = rindex(s, '-');
	    return p ? ++p : s;
	}
	else while (p = index(s, '-')) {
	    if (p > sp) break;
	    s = p + 1;
	}
	return s;
}

/*
 *  Count how many characters in s1 until s1 differs with s2
 */
int nmatch(s1, s2)
char *s1, *s2;

{
	register int i;

	for (i = 0; s1[i] && s2[i] && s1[i] == s2[i]; i++)
		;
	return(i);
}

/*
 * Command and argument completion.  The variable s will be changed to reflect
 * what it was completed to.  The variable *iscomplete will contain a 1 if s
 * was expanded completely.
 *
 * One of these conditions will be satisfied and the apprpriate steps taken.
 *
 * 1)	If c_list is empty return immediately.
 * 2)	If s is an empty string and c_list contains more than one string,
 *	return an empty string with no change.
 * 3)	If c_list only contains one string and s prefixes it then
 *	completely expand s to that string.
 * 4)	If s is exactly equal to one of the strings in c_list, merely
 *	report that s in completely expanded.
 * 5)	If s prefixes none of the strings in c_list then chop characters
 *	out of s until it will prefix at least one of the strings in c_list.
 * 6)	If s uniquely prefixes a string in c_list then completely expand
 *	s to that string.
 * 7)	If s prefixes more than one string in c_list expand s to the point
 *	where the strings differ.
 *
 * Note: the strings in c_list MUST ALREADY be sorted in collating
 *	 sequence!
 */
char *
complete(s, c_list, iscomplete)
char *s;
struct list *c_list;
int *iscomplete;

{
	static char delta[LONG_BUF * 2];
	int i, lindex, n_matched, s_len, found;

	delta[0] = '\0'; *iscomplete = 0;
	/*
	 * Check for condition 1.  If no completion list, forget it.
	 */
	if (c_list->l_count == 0)
		return delta;
	/*
	 * Check for condition 2.  If s is empty and there is more than one
	 * word in the completion list, forget it.
	 */
	if (*s == '\0' && c_list->l_count > 1)
		return delta;
	/*
	 * Check for condition 3.  If there is only one word in the
	 * completion list and s prefixes it, this is the one.
	 */
	s_len = strlen(s);
	if (c_list->l_count==1&&!strncmp(s,(char *)c_list->l_list[0],s_len)) {
		(void) strcpy(delta, &(((char *)c_list->l_list[0])[s_len]));
		(void) strcat(delta, " ");
		(void) strcat(s, delta);
		*iscomplete = 1;
		return delta;
	}
	/*
	 * Binary search the completion list.
	 * If s is not found, all the strings that s prefixes (if any)
	 * will have indices >= lindex .
	 */
	lindex = search_list(c_list, s, strcmp, &found);
	/*
	 * Check for condition 4.  If we got a perfect match, skidaddle.
	 */
	if (found) {
		(void) strcpy(delta, " ");
		(void) strcat(s, delta);
		*iscomplete = 1;
		return delta;
	}
	/*
	 * Check for condition 5.  Hacksaw the garbage until we recognize
	 * something.
	 */
	n_matched = 0;
	for (i=lindex-1; i < lindex+2; i++) {
		if (i < 0)
			continue;
		if (i >= c_list->l_count)
			break;
		n_matched=max(n_matched, nmatch(s,(char *)c_list->l_list[i]));
		/*
		 * If s prefixes c_list->l_list[lindex] or one of the words
		 * adjacent to it, we set lindex to its index so that lindex
		 * now indexes to the first word that s prefixes.
		 */
		if (n_matched == s_len) {
			lindex = i;
			break;
		}
	}
	if (n_matched < s_len) {
		for (i=0; i < s_len - n_matched; i++)
			(void) strcat(delta, BACKSPACE);
		s[n_matched] = '\0';
		return delta;
	}
	/*
	 * Check for condition 6.  If s can be unambigously expanded
	 * then do so.
	 */
	if (lindex+1 == c_list->l_count ||
		strncmp((char *)c_list->l_list[lindex],
		    (char *)c_list->l_list[lindex+1], s_len)) {
		(void) strcpy(delta, ((char *)c_list->l_list[lindex])+s_len);
		(void) strcat(delta, " ");
		(void) strcat(s, delta);
		*iscomplete = 1;
		return delta;
	}
	/*
	 * Gotta be condition 7.  Expand as far as possible, but
	 * don't set *iscomplete.
	 */
	for (i=lindex+1 ;; i++) {
		if (i == c_list->l_count) {
			break;
		}
		if (strncmp(s, (char *)c_list->l_list[i], s_len) != 0)
			break;
	}
	i--;
	n_matched = nmatch((char *)c_list->l_list[lindex],
			   (char *)c_list->l_list[i]);
	(void) strncpy(delta, ((char *)c_list->l_list[lindex])+s_len,
		n_matched-s_len);
	delta[n_matched - s_len] = '\0';
	(void) strcat(s, delta);
	return delta;
}

redraw(prompt, s)
char *prompt, *s;

{
	char_scr('\r');
	str_scr(prompt);
	str_scr(s);
	return;
}

#include <ctype.h>

/*
 * If this variable is non-zero then it must contain the length of
 * the latest visible completion help tag, e.g. "[Ambiguous]".
 * If zero then there is no floating completion tag at this time.
 */
static int MustEraseTag;

char
GET()

{
    extern int errno;
    int count, nready, rmask = 1;
    struct timeval t, *timeout = 0;
    char c;
    
    errno = 0;
    for (;;) {
	rmask = 1;
	if (MustEraseTag) {
	    t.tv_sec = 1;
	    t.tv_usec = 250000;
	    timeout = &t;
	}
#ifdef BSD4_3
	nready = select(1, (fd_set*)&rmask, (fd_set *)0, (fd_set *)0, timeout);
#else
	nready = select(1, &rmask, 0, 0, timeout);
#endif
	if (nready == -1 && errno != EINTR) {
	    perr("select");
	    panic((char *)0);
	}
	if (nready == 0 || MustEraseTag) {
	    erasetag();
	    timeout = 0;
	    if (!nready)
	      continue;
	}
	count = read(0, &c, 1);
	if (count == 1)
	  break;
	if (count == 0)
	  panic("EOF encountered");
	if (count == -1 && errno != EINTR) {
	    perr("read");
	    panic((char *)0);
	}
    }
    c &= 0177;
    return(c);
}

showtag(tag)
char *tag;

{
    register int i;

    MustEraseTag = strlen(tag) + 1;
    char_scr(' ');
    str_scr(tag);
    for (i=0; i < MustEraseTag; i++)
      char_scr('\b');
    return;
}

erasetag()

{
    register int i;

    for (i=0; i < MustEraseTag; i++)
      char_scr(' ');
    for (i=0; i < MustEraseTag; i++)
      char_scr('\b');
    MustEraseTag = 0;
    return;
}

/*
 *  Figure out which argument vector to use for argument completion.
 */
struct list *
picklist(cmd)
char *cmd;

{
	cmd = tail(cmd);
	if (SCMPN("user", cmd))
		return(&Users);
	else if (SCMPN("sig", cmd))
		return(&Sigs);
	else if (SCMPN("class", cmd))
		return(&Classes);
	else if (SCMPN("group", cmd))
		return(&Groups);
	else if (SCMPN("range", cmd))
		return(&Ranges);
	else if (SCMPN("vig", cmd))
		return(&Vigs);
	else if (SCMPN("command", cmd))
		return(&AllCommands);
#ifdef SENDMAIL
	else if (SCMPN("alias", cmd))
		return(&Aliases);
#endif
#ifdef HELPDIR
	else if (SCMPN("is", cmd))
		return(&Terms);
#endif
	else
		return(&Null_List);
}

#ifdef sun
#define	sighandler	(void (*)())
#else
#define	sighandler	(int (*)())
#endif

#ifdef sun
void
#endif
tstp_cleanup()

{
	char_scr('\r');
	nocbreak();
	(void) signal(SIGTSTP, sighandler SIG_DFL);
	(void) kill(getpid(), SIGTSTP);
	return;
}

#ifdef sun
void
#endif
input_continue()

{
	cbreak();
	(void) signal(SIGTSTP, tstp_cleanup);
	longjmp(in_continue, SIGCONT);
}

/*
 *  Get a line of input using command and smart argument completion.
 */
GetCommandLine(prompt, nargs, argc, argv)
char *prompt;
int nargs, *argc;
addr *argv;

{
	addr *tmpargv;
	char buf[LONG_BUF], *p;
	int c, iscomplete, indx, windex, hindex, i, end_of_line, h_len, d;
	int quote_open;
	struct list *c_list;
	struct hist *hi, hh;
	
	cbreak();
	MustEraseTag = 0;
	exprv[0] = buf;
	*argc = 0;
	*line = '\0';
	quote_open = end_of_line = windex = indx = 0;
	hindex = History.l_count;
	c_list = &Commands;
	(void) signal(SIGTSTP, tstp_cleanup);
	(void) signal(SIGCONT, input_continue);
	(void) setjmp(in_continue);
	str_scr(prompt);
	while (!end_of_line && indx < BUFSIZ) {
		c = GET();
		if (setjmp(in_continue) == SIGCONT) {
			redraw(prompt, line);
			continue;
		}
		switch (c) {
		case ':': 
			break;	/* ignore colons for pwd, group, etc. files */
		/*
		 * ^P goes one step back in the history list.
		 */
		case '\020':
			if (hindex == 0) {
			    showtag("[History begins here...]");
			    break;
			}
			if (hindex == History.l_count) {
				(void) strcpy(currh.h_line, line);
				currh.h_prompt = prompt;
				currh.h_argc = *argc;
				currh.h_index = indx;
				currh.h_windex = windex;
				currh.h_qopen = quote_open;
				currh.h_list = c_list;
			}
			hi = (struct hist *) History.l_list[--hindex];
			h_len = strlen(line) + strlen(prompt);
			for (i=strlen(hi->h_line); i < h_len; i++)
				str_scr(BACKSPACE);
			(void) strcpy(line, hi->h_line);
			prompt = hi->h_prompt;
			*argc = hi->h_argc;
			indx = hi->h_index;
			windex = hi->h_windex;
			quote_open = hi->h_qopen;
			c_list = hi->h_list;
			redraw(prompt, line);
			break;
		/*
		 * ^N goes one step forward in the history list.
		 */
		case '\016':
			if (!History.l_count || hindex == History.l_count) {
			    showtag("[End of history]");
			    break;
			}
			if (hindex == History.l_count-1) {
				h_len = strlen(line) + strlen(prompt);
				i=strlen(currh.h_line)+strlen(currh.h_prompt);
				for (; i < h_len; i++)
					str_scr(BACKSPACE);
				(void) strcpy(line, currh.h_line);
				prompt = currh.h_prompt;
				*argc = currh.h_argc;
				indx = currh.h_index;
				windex = currh.h_windex;
				quote_open = currh.h_qopen;
				c_list = currh.h_list;
				redraw(prompt, line);
				hindex++;
				break;
			}
			hi = (struct hist *) History.l_list[++hindex];
			h_len = strlen(line) + strlen(prompt);
			i = strlen(hi->h_line) + strlen(hi->h_prompt);
			for (; i < h_len; i++)
				str_scr(BACKSPACE);
			(void) strcpy(line, hi->h_line);
			prompt = hi->h_prompt;
			*argc = hi->h_argc;
			indx = hi->h_index;
			windex = hi->h_windex;
			quote_open = hi->h_qopen;
			c_list = hi->h_list;
			redraw(prompt, line);
			break;
		/* 
		 * For space bar, do completion only for the
		 * command (first) word.  Allow normal spaces
		 * only at the end of a word.
		 */
		case ' ': 
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (windex != 0 && windex != indx) {
				char_scr(c);
				line[indx] = c;
				line[indx + 1] = '\0';
				indx++;
				if (!quote_open) {
				    windex = indx;
				    (*argc)++;
				}
				break;
			}
			else if (windex == indx) /* balk at adjacent spaces */
				break;
			else
				/* FALL THROUGH */
					;
		/* 
		 * Word is completion done here.  Completion is activated
		 * by the TAB or the ESC key.  Activation by CR or LF only
		 * if completing first word.
		 */
		case '\r':
		case '\n':
			if (*argc != 0) {
				end_of_line++;
				break;
			}
			if (nargs && *argc == nargs) {
				end_of_line++;
				break;
			}
			/* FALL THROUGH */
		case '\t': 
		case '\033': 
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			p = complete(&line[windex], c_list, &iscomplete);
			str_scr(p);
			if (iscomplete) {
				if (windex == 0)
					c_list = picklist(line);
				windex = strlen(line);
				(*argc)++;
			}
			else if (*p == '\0' && indx != windex)
			  showtag("[Ambiguous]");
			indx = strlen(line);
			if ((c == '\r' || c == '\n') && iscomplete)
				end_of_line++;
			break;
		/*
		 * Ctrl-T transposes the two characters before the cursor
		 */
		case '\024':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			/* not enough characters */
			if (indx < 2)
				break;
			/* can't let space to be first character on a line */
			if (indx == 2 && line[1] == ' ')
				break;
			swap(line[indx-1], line[indx-2]);
			/*
			 * If one of the transposed characters was a space,
			 * we have either broken a word in two or merged
			 * the current word with the preceding one.
			 */
			if (line[indx-2] == ' ')
			    if (!quote_open && line[indx-1] == '"') {
				(*argc)++, windex--;
				c_list = picklist(line);
			    }
			else if (line[indx-1] == ' ')
			    if (!quote_open && line[indx-2] == '"') {
				(*argc)--, windex++;
				c_list = (*argc == 0) ?
					&Commands : picklist(line);
			    }
			str_scr("\b \b\b \b");
			str_scr(&line[indx-2]);
			break;
		/* 
		 * Show user possiblities if he wonders why
		 * a word isn't completing.
		 */
		case '?': 
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			str_scr("\r\n");
			(void) strcpy(buf, "^");
			(void) strcat(buf, &line[windex]);
			(void) strcat(buf, ".*");
			nocbreak();
			/*
			 * We don't want completion info going to stdout
			 * so we temporarily connect stdout to /dev/tty
			 */
			d = dup(1);
			(void) dup2(DevTty, 1);
			(void) showlist(c_list, (addr *)exprv);
			/*
			 * Now re-connect stdout to whatever is was before
			 */
			(void) dup2(d, 1);
			(void) close(d);

			cbreak();
			redraw(prompt, line);
			break;
		/* 
		 * Ctrl-R simply reprints the command line.
		 */
		case '\022': 
			redraw(prompt, line);
			break;
		/*
		 * Ctrl-W is accepted as the word-kill character.
		 */
		case '\027':
			if (indx == windex && windex != 0) {
				for (windex -= 2; windex >= 0; windex--) {
					if (line[windex] == '"') {
						quote_open = !quote_open;
						continue;
					}
					if (!quote_open&& line[windex] == ' ')
						break;
				}
				windex++;
				if (windex < 0)
					windex = 0;
				decr(*argc);
			}
			if (windex == 0)
				c_list = &Commands;
			for (; indx > windex; indx--)
				str_scr(BACKSPACE);
			line[indx] = '\0';
			break;
		/* 
		 * Backspace (^H) and del are accepted as erase
		 * characters, with the screen eraseure being
		 * accomplished via backspace-space-backpsace
		 * sequences
		 */
		case '\177':
		case '\b': 
			if (--indx < windex) {
				for (windex-=2; windex >= 0; windex--) {
					if (line[windex] == '"') {
						quote_open = !quote_open;
						continue;
					}
					if (!quote_open&& line[windex] == ' ')
						break;
				}
				windex++;
				if (windex < 0)
					windex = 0;
				decr(*argc);
			}
			if (windex == 0)
				c_list = &Commands;
			if (indx >= 0) {
				if (line[indx] == '"')
					quote_open = !quote_open;
				line[indx] = '\0';
				str_scr(BACKSPACE);
			}
			else
				indx = 0;
			break;
		/* 
		 * Ctrl-X and ctrl-U are accepted as line kill
		 * characters.
		 */
		case '\030':
		case '\025':
			if (indx == 0)
				break;
			for (; indx>0; indx--)
				str_scr(BACKSPACE);
			quote_open = windex = indx = 0;
			*line = '\0';
			c_list = &Commands;
			*argc = 0;
			break;
		default: 
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (c == '"')
				quote_open = !quote_open;
			/* 
			 * Ignore unrecognized control characters
			 */
			if (isprint(c)) {
				char_scr(c);
				line[indx] = c;
				line[indx + 1] = '\0';
			indx++;
			}
		break;
		}
	}
	(void) signal(SIGCONT, sighandler SIG_DFL);
	(void) signal(SIGTSTP, sighandler SIG_DFL);
	if (quote_open) {
		char_scr('"');
		line[indx++] = '"';
		line[indx] = '\0';
		quote_open = 0;
	}

	critical();

	savestr(&hh.h_prompt, prompt);
	savestr(&hh.h_line, line);
	hh.h_argc = *argc;
	hh.h_index = indx;
	hh.h_windex = windex;
	hh.h_qopen = quote_open;
	hh.h_list = c_list;
	trimhist();
	genlistadd(&History, (addr)&hh, sizeof (struct hist));

	non_critical();

	if (indx > windex)
		(*argc)++;
	(void) cut(line);
	tmpargv = mkargv(line, *argc);
	for (i=0; tmpargv[i]; i++)
		savestr((char **)&argv[i], (char *)tmpargv[i]);
	argv[i] = NIL;
	str_scr("\r\n");
	nocbreak();
	return;
}

GetLine(prompt, nargs, argc, argv, c_list)
char *prompt;
int nargs, *argc;
addr *argv;
struct list *c_list;

{
	addr *tmpargv;
	char buf[LONG_BUF], *p;
	int c, iscomplete, indx, windex, i, d, quote_open;

	cbreak();
	exprv[0] = buf;
	MustEraseTag = 0;
	*argc = quote_open = 0;
	*line = '\0';
	windex = indx = 0;
	(void) signal(SIGTSTP, tstp_cleanup);
	(void) signal(SIGCONT, input_continue);
	(void) setjmp(in_continue);
	str_scr(prompt);
	while ((c = GET()) != '\n' && indx < BUFSIZ) {
		if (setjmp(in_continue) == SIGCONT) {
			redraw(prompt, line);
			continue;
		}
		if (c == '\r') 
			break;
		switch ( c ) {
		case ':':
			break;	/* ignore colons for pwd, group, etc. files */
		case ' ':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (windex != indx) {
				char_scr(c);
				line[indx] = c;
				line[indx+1] = '\0';
				indx++;
				if (!quote_open) {
					windex = indx;
					(*argc)++;
				}
			}
			break;
		case '\t':
		case '\033':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			p = complete(&line[windex], c_list, &iscomplete);
			str_scr(p);
			if (iscomplete) {
				windex = strlen(line);
				(*argc)++;
			}
			else if (*p == '\b')
			  showtag("[No match]");
			else if (*p == '\0' && indx != windex)
			  showtag("[Ambiguous]");
			indx = strlen(line);
			break;
		/*
		 * Ctrl-T transposes the two characters before the cursor
		 */
		case '\024':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (indx < 2)
				break;
			if (indx == 2 && line[1] == ' ')
				break;
			swap(line[indx-1], line[indx-2]);
			/*
			 * If one of the transposed characters was a space,
			 * we have either broken a word in two or merged
			 * the current word with the preceding one.
			 */
			if (line[indx-2] == ' ')
			    if (!quote_open && line[indx-1] == '"')
				(*argc)++, windex--;
			else if (line[indx-1] == ' ')
			    if (!quote_open && line[indx-2] == '"')
				(*argc)--, windex++;
			str_scr("\b \b\b \b");
			str_scr(&line[indx-2]);
			break;
		/* 
		 * Show user possiblities if he wonders why
		 * a word isn't completing.
		 */
		case '?':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			str_scr("\r\n");
			(void) strcpy(buf, "^");
			(void) strcat(buf, &line[windex]);
			(void) strcat(buf, ".*");
			nocbreak();
			/*
			 * We don't want completion info going to stdout
			 * so we temporarily connect stdout to /dev/tty
			 */
			d = dup(1);
			(void) dup2(DevTty, 1);
			(void) showlist(c_list, (addr *)exprv);
			/*
			 * Now re-connect stdout to whatever is was before
			 */
			(void) dup2(d, 1);
			(void) close(d);

			cbreak();
			redraw(prompt, line);
			break;
		/* 
		 * Ctrl-R simply reprints the command line.
		 */
		case '\022':
			redraw(prompt, line);
			break;
		/*
		 * Ctrl-W is accepted as the word-kill character.
		 */
		case '\027':
			if (indx == windex && windex != 0) {
				for (windex -= 2; windex >= 0; windex--) {
					if (line[windex] == '"') {
						quote_open = !quote_open;
						continue;
					}
					if (!quote_open&& line[windex] == ' ')
						break;
				}
				windex++;
				if (windex < 0)
					windex = 0;
				decr(*argc);
			}
			for (; indx > windex; indx--)
				str_scr(BACKSPACE);
			line[indx] = '\0';
			break;
		/* 
		 * Backspace (^H) and del are accepted as erase
		 * characters, with the screen erasure being
		 * accomplished via backspace-space-backpsace
		 * sequences
		 */
		case '\177':
		case '\b':
			if ( --indx < windex ) {
				for (windex-=2; windex >= 0; windex--) {
					if (line[windex] == '"') {
						quote_open = !quote_open;
						continue;
					}
					if (!quote_open&& line[windex] == ' ')
						break;
				}
				windex++;
				if (windex < 0)
					windex = 0;
				decr(*argc);
			}
			if (indx >= 0) {
				if (line[indx] == '"')
					quote_open = !quote_open;
				line[indx] = '\0';
				str_scr(BACKSPACE);
			}
			else
				indx = 0;
			break;
		/* 
		 * Ctrl-X and ctrl-U are accepted as line kill
		 * characters.
		 */
		case '\030':
		case '\025':
			if (indx == 0)
				break;
			for (; indx>0; indx--)
				str_scr(BACKSPACE);
			quote_open = windex = indx = 0;
			*line = '\0';
			*argc = 0;
			break;
 		default:
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (c == '"')
				quote_open = !quote_open;
			/* 
			 * Ignore unrecognized control characters
			 */
			if (isprint(c)) {
				char_scr(c);
				line[indx] = c;
				line[indx+1] = '\0';
				indx++;
			}
			break;
		}
	}
	(void) signal(SIGCONT, sighandler SIG_DFL);
	(void) signal(SIGTSTP, sighandler SIG_DFL);
	if (quote_open) {
		char_scr('"');
		line[indx++] = '"';
		line[indx] = '\0';
	}
	if (indx > windex)
		(*argc)++;
	(void) cut(line);
	tmpargv = mkargv(line, *argc);
	for (i=0; tmpargv[i]; i++)
		savestr((char **)&argv[i], (char *)tmpargv[i]);
	argv[i] = NIL;
	str_scr("\r\n");
	nocbreak();
	return;
}

trimhist()

{
	struct hist *h;

	if (History.l_count <= MAXHIST) return;
	h = (struct hist *) listpop(&History);
	FREEMEM(h->h_line);
	FREEMEM((char *)h);
	return;
}

GetFilenames(prompt, nargs, argc, argv)
char *prompt;
int nargs, *argc;
addr *argv;

{
	addr *tmpargv;
	char buf[LONG_BUF], *p;
	int c, iscomplete, indx, windex, i, d, quote_open;
	static struct list c_list;

	exprv[0] = buf;
	MustEraseTag = 0;
	*argc = 0;
	*line = '\0';
	quote_open = windex = indx = 0;
	zerolist(&c_list);
	tmplistadd(&c_list);
	cbreak();
	(void) signal(SIGTSTP, tstp_cleanup);
	(void) signal(SIGCONT, input_continue);
	(void) setjmp(in_continue);
	str_scr(prompt);
	while ((c = GET()) != '\n' && indx < BUFSIZ) {
		if (setjmp(in_continue) == SIGCONT) {
			redraw(prompt, line);
			continue;
		}
		if (c == '\r') 
			break;
		switch ( c ) {
		case ' ':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (windex != indx) {
				char_scr(c);
				line[indx] = c;
				line[indx+1] = '\0';
				indx++;
				if (!quote_open) {
					windex = indx;
					(*argc)++;
				}
			}
			break;
		case '\t':
		case '\033':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			p = pathcomplete(&line[windex], &c_list, &iscomplete);
			str_scr(p);
			if (iscomplete) {
				windex = strlen(line);
				(*argc)++;
			}
			else if (*p == '\0' && indx != windex)
			  showtag("[Ambiguous]");
			indx = strlen(line);
			break;
		/*
		 * Ctrl-T transposes the two characters before the cursor
		 */
		case '\024':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (indx < 2)
				break;
			if (indx == 2 && line[1] == ' ')
				break;
			swap(line[indx-1], line[indx-2]);
			/*
			 * If one of the transposed characters was a space,
			 * we have either broken a word in two or merged
			 * the current word with the preceding one.
			 */
			if (line[indx-2] == ' ')
			    if (!quote_open && line[indx-1] == '"')
				(*argc)++, windex--;
			else if (line[indx-1] == ' ')
			    if (!quote_open && line[indx-2] == '"')
				(*argc)--, windex++;
			str_scr("\b \b\b \b");
			str_scr(&line[indx-2]);
			break;
		/* 
		 * Show user possiblities if he wonders why
		 * a word isn't completing.
		 */
		case '?':
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			str_scr("\r\n");
			freelist(&c_list);
			dirscan(dirof(&line[windex]), &c_list);
			(void) strcpy(buf, "^");
			(void) strcat(buf, fileof(&line[windex]));
			(void) strcat(buf, ".*");
			nocbreak();
			/*
			 * We don't want completion info going to stdout
			 * so we temporarily connect stdout to /dev/tty
			 */
			d = dup(1);
			(void) dup2(DevTty, 1);
			(void) showlist(&c_list, (addr *)exprv);
			/*
			 * Now re-connect stdout to whatever is was before
			 */
			(void) dup2(d, 1);
			(void) close(d);

			cbreak();
			redraw(prompt, line);
			break;
		/* 
		 * Ctrl-R simply reprints the command line.
		 */
		case '\022':
			redraw(prompt, line);
			break;
		/*
		 * Ctrl-W is accepted as the word-kill character.
		 */
		case '\027':
			if (indx == windex && windex != 0) {
				for (windex -= 2; windex >= 0; windex--) {
					if (line[windex] == '"') {
						quote_open = !quote_open;
						continue;
					}
					if (!quote_open&& line[windex] == ' ')
						break;
				}
				windex++;
				if (windex < 0)
					windex = 0;
				decr(*argc);
			}
			for (; indx > windex; indx--)
				str_scr(BACKSPACE);
			line[indx] = '\0';
			break;
		/* 
		 * Backspace (^H) and del are accepted as erase
		 * characters, with the screen erasure being
		 * accomplished via backspace-space-backpsace
		 * sequences
		 */
		case '\177':
		case '\b':
			if ( --indx < windex ) {
				for (windex-=2; windex >= 0; windex--) {
					if (line[windex] == '"') {
						quote_open = !quote_open;
						continue;
					}
					if (!quote_open&& line[windex] == ' ')
						break;
				}
				windex++;
				if (windex < 0)
					windex = 0;
				decr(*argc);
			}
			if (indx >= 0) {
				if (line[indx] == '"')
					quote_open = !quote_open;
				line[indx] = '\0';
				str_scr(BACKSPACE);
			}
			else
				indx = 0;
			break;
		/* 
		 * Ctrl-X and ctrl-U are accepted as line kill
		 * characters.
		 */
		case '\030':
		case '\025':
			if (indx == 0)
				break;
			for (; indx>0; indx--)
				str_scr(BACKSPACE);
			quote_open = windex = indx = 0;
			*line = '\0';
			*argc = 0;
			break;
 		default:
			if (nargs && *argc == nargs) {
			    showtag("[Press RETURN]");
			    break;
			}
			if (c == '"')
				quote_open = !quote_open;
			/* 
			 * Ignore unrecognized control characters
			 */
			if (isprint(c)) {
				char_scr(c);
				line[indx] = c;
				line[indx+1] = '\0';
				indx++;
			}
			break;
		}
	}
	(void) signal(SIGCONT, sighandler SIG_DFL);
	(void) signal(SIGTSTP, sighandler SIG_DFL);
	if (quote_open) {
		char_scr('"');
		line[indx++] = '"';
		line[indx] = '\0';
	}
	if (indx > windex)
		(*argc)++;
	(void) cut(line);
	tmpargv = mkargv(line, *argc);
	for (i=0; tmpargv[i]; i++)
		savestr((char **)&argv[i], (char *)tmpargv[i]);
	argv[i] = NIL;
	str_scr("\r\n");
	nocbreak();
	return;
}

char *
pathcomplete(path, c_list, iscomplete)
char *path;
struct list *c_list;
int *iscomplete;

{
	static char delta[LONG_BUF];
	char buf[LONG_BUF], *dir, *file;
	int n_matched, d_len, i;

	delta[0] = '\0';
	(void) strcpy(buf, path);

	/*
	 * If we can't chdir to the directory prefix of the path, then
	 * we hack away parts of the path until we can or it's all
	 * gone.  Note that this depends on the fact within UNIX "\0"
	 * is synonymous with "."
	 */
	dir = dirof(buf);
	if (chdir(dir) == -1) {
		diraxe(buf);
		while (*buf && chdir(buf) == -1)
			diraxe(buf);
		(void) getwd(buf);
		/*
		 * If this isn't the root directory, add a slash
		 */
		if (!eq(buf, "/"))
			(void) strcat(buf, "/");
		/* note changes and store necessary cursor motions */
		n_matched = nmatch(path, buf);
		d_len = strlen(path);
		for (i=d_len; i > n_matched; i--)
			(void) strcat(delta, BACKSPACE);
		(void) strcat(delta, buf+n_matched);
		(void) strcpy(path, buf);
		(void) chdir(Working_Directory);
		*iscomplete = 0;
		return delta;
	}
	/*
	 * Chdir'ed successfully so now we make the completion list.
	 */
	freelist(c_list);
	dirscan(".", c_list);
	/* do completion of the file suffix of the path */
	file = fileof(path);
	(void) complete(file, c_list, iscomplete);
	/*
	 * Shed . , .. and symlinks
	 */
	(void) getwd(buf);
	/*
	 * If this isn't the root directory, add a slash
	 */
	if (!eq(buf, "/"))
		(void) strcat(buf, "/");
	/*
	 * Add the result of file name completion
	 */
	(void) strcat(buf, file);
	/*
	 * Now note the difference between the what was passed in and
	 * what we have now and put the necessary cursor movements into
	 * delta[]
	 */
	n_matched = nmatch(path, buf);
	d_len = strlen(path);
	for (i=d_len; i > n_matched; i--)
		(void) strcat(delta, BACKSPACE);
	(void) strcat(delta, buf+n_matched);
	/* get rid of side effects */
	(void) chdir(Working_Directory);
	/* save results */
	(void) strcpy(path, buf);
	/*
	 * If the filename completed was in fact a directory then we append
	 * a slash to the file name and note that the completion didn't
	 * result in a regular file name (*iscomplete = 0).
	 */
	if (*iscomplete) {
		d_len = strlen(path) - 1;
		path[d_len] = '/';
		if (isdir(path)) {
			delta[strlen(delta)-1] = '/';
			*iscomplete = 0;
			return delta;
		}
		path[d_len] = ' ';
	}
	return delta;
}

char *
dirof(path)
char *path;

{
	register char *cp;
	static char buf[LONG_BUF];

	(void) strcpy(buf, path);
	cp = rindex(buf, '/');
	if (cp)
		*++cp = '\0';
	else
		*buf = '\0';
	return buf;
}

char *
fileof(path)
char *path;

{
	register char *cp;
	static char buf[LONG_BUF];

	(void) strcpy(buf, path);
	cp = rindex(buf, '/');
	return cp ? cp+1 : buf;
}

/*
 * Hack off the last directory in a path.  The path should end with '/'
 * for this to work properly.
 */
diraxe(path)
char *path;

{
	register char *cp;

	cp = rindex(path, '/');
	if (cp)
		*cp = '\0';
	else {
		*path = '\0';
		return;
	}
	cp = rindex(path, '/');
	if (cp)
		*++cp = '\0';
	else
		*path = '\0';
	return;
}