/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/
/*
 * Modified: Nov-88, Apr-89 by Tom Hageman
 * - add arrow keys in real_ask
 * - make real_ask re-entrant.
 * Jun-90, update to v4.14
 */

#define NO_PROCDECL		/* kludge for teensy pdp11 compiler */

#include "jove.h"

RCS("$Id: ask.c,v 14.32.0.12 1994/06/23 02:46:23 tom Exp tom $")

#include "ctype.h"
#include "io.h"
#include "screen.h"

#define PROMPTSIZE	128	/* size of prompt buffer */

int		Asking ZERO;
char		Minibuf[LBSIZE];

private Line	*CurAskPtr ZERO;	/* points at some line in mini-buffer */
private Buffer	*AskBuffer ZERO;	/* Askbuffer points to actual structure */

/* The way the mini-buffer works is this:  The first line of the mini-buffer
   is where the user does his stuff.  The rest of the buffer contains
   strings that the user often wants to use, for instance, file names, or
   common search strings, etc.  If he types C-N or C-P while in ask(), we
   bump the point up or down a line and extract the contents (we make sure
   is somewhere in the mini-buffer). */

private Buffer *get_minibuf __(( void ));
private Buffer *
get_minibuf()
{
	if (!bufno(AskBuffer))	/* make sure it still exists */
		(AskBuffer = do_select((Window *) 0,
				       "*minibuf*"))->b_type = B_SCRATCH;
	return AskBuffer;
}

/* Add a string to the mini-buffer. */
void
minib_add(str, movedown)
const char	*str;
{
	register Buffer	*saveb = curbuf;

	SetBuf(get_minibuf());
	ToLast();
	LineInsert(1);
	ins_str(str, NO);
	if (movedown)
		CurAskPtr = curline;
	SetBuf(saveb);
}

void
AskNextLine()
{
	register Line	*lp;

	lp = next_line(CurAskPtr, exp);
	if (lp->l_prev == NULL && lp->l_next != NULL)
		lp = lp->l_next;
	CurAskPtr = lp;
	ltobuf(lp, linebuf);
	makedirty(curline);
	Eol();
}

/* Kludge: truncate prompt if it's too long.
   Also try to do something sensible with defaults...
   Assumes prompt contains printable characters only.
   Returns length of (possibly truncated) prompt. */

private int fix_prompt __(( char *_(prompt), int _(limit) ));
private int
fix_prompt(prompt, limit)
register char	*prompt;
register int	limit;
{
	register int		i,
				len;

	if ((len = strlen(prompt)) <= limit)
		return len;

	if ((i = sindex("(default ", prompt)) == 0 || i > limit) {
		i = limit >> 1;
		goto dots;
	}
	else if (i + 3 >= limit)	  /* no room for "...",     */
		limit = --i;		  /* so truncate at `('.    */
	else if ((len - 8) <= limit)	  /* just enough room if    */
		limit = (len - 8);	  /* we remove "default ".  */
	else {				  /* remove "default ",     */
dots:		strcpy(&prompt[i], "..."); /* insert "...", and      */
		i += 3;			  /* setup to truncate tail */
	}
	prompt += i;
	strcpy(prompt, &prompt[len - limit]);

	return limit;
}

private char *real_ask __(( const char *_(delim), int (*_(d_proc))(int _(c)),
			    const char *_(def), char *_(prompt) ));
private char *
real_ask(delim, d_proc, def, prompt)
const char	*delim,
		*def;
char		*prompt;
int		(*d_proc)__(( int _(c) ));
{
	extern int	DOLsave;
	static int	InAsk ZERO;
	Savejmp		savejmp;
	register Buffer	*ask_bp;	/* Invariant: ask_bp == curbuf */
	register int	c,
			abort = NO,
			prompt_len,
			start_col,
			start_off;
	Buffer		*saveb = curbuf,
			*saveb_next_window = NULL;
	const data_obj	*push_cmd = LastCmd;
	int		o_exp = exp,
			o_exp_p = exp_p,
			o_Asking = Asking,
			o_Interactive = Interactive;
	char		*save_line;
	const char	*insert;

	if (!one_windp(curwind))
		saveb_next_window = curwind->w_next->w_bufp;

	SetBuf(ask_bp = get_minibuf());
	if (!inlist(ask_bp->b_first, CurAskPtr))
		CurAskPtr = ask_bp->b_dot;	/* i.e. curline */

	ToFirst();	/* Beginning of buffer. */

	if (InAsk)
		save_line = copystr(linebuf),	/* to make ask re-entrant */
		RecDepth++, updmodline();	/* to show recursive ask */
	InAsk++;

	linebuf[0] = '\0';
	modify();
	makedirty(ask_bp->b_dot);

	if (!InJoverc) {
		prompt_len = fix_prompt(prompt, (3 * CO >> 2));
		UpdMesg = start_col = start_off = 0;
	}
	if (push_env(savejmp)) {
		if (InJoverc || (in_macro() && !Interactive))
			abort++;	/* this is a kludge */
	}
	this_cmd = 0;
	insert = def;
	while (!abort) {
		if (!InJoverc) {
			extern int	alarmed;

			/* show error message, or message from a command,
			   or message modified by d_proc for a short time.
			   In the latter case, keep current cursor position.
			   But don't show alarmed keystrokes! */

			if (UpdMesg && mesgbuf[0] &&
			    (!in_macro() || Interactive) &&
			    (!alarmed || strcmp(mesgbuf, key_strokes()) != 0)) {
				if (c >= 0)
					Asking = strlen(mesgbuf);
				SitFor(3 * PDelay >> 1);
			}

			/* calculate start_column of horizontal window.
			   These are PRINT columns, so we have to convert
			   to character offset in linebuf.  Fortunately we
			   can get away with re-calculating it whenever
			   start_col changes (which isn't too often). */

			c = HorWindow(calc_pos(linebuf, ask_bp->b_char),
				      start_col, prompt_len);
			if (c != start_col) {
				start_col = c;
				start_off = how_far(linebuf, c);
			}
			Asking = ask_bp->b_char - start_off + prompt_len;
			s_mess("%s%s", prompt, &linebuf[start_off]);
		}

		if (this_cmd != ARG_CMD) {
			exp = 1;
			exp_p = NO;
			last_cmd = this_cmd;
			init_strokes();
		}

		if ((c = getch()) < 0 /*EOF*/ || (c && index(delim, c))) {
			if (!d_proc || !(*d_proc)(c))
				break;
			c = EOF;	/* for delayed display */
		}
		else {
			UpdMesg = NO;	/* Kludge: this suppresses delay after
					   Ungetc'd character. */
			if (c == AbortChar) {
				abort++;
				message("[Aborted]");
				continue;
			}
			switch (c) {

			case CTL('S'):		/* Spew out current file name */
			case CTL('\\'):
			    {
				register Buffer *b = curwind->w_bufp;

				if (exp_p) {	/* ...of buffer #<exp> */
					c = exp;
					b = world;
					while (--c > 0 && (b = b->b_next))
						continue;
				}
				insert = (b && b->b_fname) ? filename(b) : NULL;
			    }
#if/*ndef TINY*/ 1
				goto ins;

			case CTL('C'):
				SetBuf(curwind->w_bufp);
				WordAtDot((char *)(insert = Minibuf), sizeof Minibuf);
				SetBuf(ask_bp);
			     /*	goto ins; */

			ins:
#endif
			case CTL('R'):		/* Recall default */
				if (insert && *insert)
					ins_str(insert, NO);
				else
					rbell();
				this_cmd = 0;
				insert = def;
				continue;

#if NO			/* this is now handled in NextLine, so
			   that key rebindings will work as they should. */

			case UPARROW:		/* move up in minibuf	*/
			case CTL('P'):
				exp = -exp;
				/* fall into... */

			case DOWNARROW:		/* move down in minibuf	*/
			case CTL('N'):
				AskNextLine();
				continue;
#endif
			default:
				dispatch(c);
				break;
			}
		}
		if (curbuf != ask_bp)
			SetBuf(ask_bp = get_minibuf());
		if (ask_bp->b_dot != ask_bp->b_first) {
			CurAskPtr = ask_bp->b_dot;
			ask_bp->b_dot = ask_bp->b_first;
			/* with whatever is in linebuf */
		}
	}
	pop_env(savejmp);

	DOLsave = 0;			/* [TRH] prevent lsave */
	LastCmd = push_cmd;
	exp_p = o_exp_p;
	exp = o_exp;
	Asking = o_Asking;
	Interactive = o_Interactive;
	strcpy(Minibuf, linebuf);
	if (--InAsk) {
		strcpy(linebuf, save_line);
		free(save_line);
		Eol();
		UpdMesg = NO;
		RecDepth--, updmodline();
	}

	SetBuf(saveb);

	if (/*- True(UseBuffers) && -*/	/* remove completion/help windows */
	    !one_windp(curwind) &&
	    curwind->w_next->w_bufp != saveb_next_window)
		if (saveb_next_window)
			tiewind(curwind->w_next, saveb_next_window);
		else
			del_wind(curwind->w_next);

	if (abort)
		complain((char *)0);		/* propagate... */

	if (!charp() && !in_macro()) {
		curstoLL();
		flusho();
	}
	if (Minibuf[0] == '\0')
		return NULL;

	return Minibuf;
}

/* VARARGS2 */

char *
DEFVARG(ask, (const char *def, const char *fmt, ...),
	     (def, fmt, va_alist)
	const char	*def;
	const char	*fmt;)
{
	char			promptbuf[PROMPTSIZE];
	register char		*prompt = promptbuf;
	register const char	*ans;
	va_register va_list	ap;

	if (!InJoverc) {
		va_begin(ap, fmt);
		format(prompt, PROMPTSIZE, fmt, ap);
		va_end(ap);
	}
	if ((ans = real_ask("\r\n", (int (*)()) 0, def, prompt)) == NULL) {
		/* Typed nothing. */
		if ((ans = def) == NULL)
			complain("[No default]");
	}
	return (char *) ans;
}

/* VARARGS4 */

char *
DEFVARG(do_ask, (const char *delim, int (*d_proc)(int c), const char *def,
		 const char *fmt, ...),
		(delim, d_proc, def, fmt, va_alist)
	const char	*delim;
	int		(*d_proc)();
	const char	*def;
	const char	*fmt;)
{
	char		promptbuf[PROMPTSIZE];
	register char	*prompt = promptbuf;
	va_register va_list ap;

	if (!InJoverc) {
		va_begin(ap, fmt);
		format(prompt, PROMPTSIZE, fmt, ap);
		va_end(ap);
	}
	return real_ask(delim, d_proc, def, prompt);
}

/*
 * Ask for confirmation.
 * def is default answer, which must be 'Y', 'N' or zero (no default).
 * If a default is present you can respond with <return> to get the default,
 * or any command key to get the default and execute that command.
 * If no default is present you must explicitly respond with 'y' or 'n'.
 */
/* VARARGS2 */

DEFVARG(yes_or_no_p, (int def, const char *fmt, ...),
		     (def, fmt, va_alist)
	int		def;
	const char	*fmt;)
{
	register int	c, d;
	va_register va_list ap;

	for (;;) {
		VA_INIT_PROPAGATE
		va_begin(ap, fmt);
		s_mess(VA_PROPAGATE(fmt, ap));
		va_end(ap);
		Asking = fix_prompt(mesgbuf, CO - 3);	/* so redisplay works */
		if (d = def)
			Asking++, add_mess("[%c]", d);

		if ((c = Peekc()) < 0) {
			redisplay();
			c = getchar();
			add_stroke(c);
		}
		if (c == AbortChar)
			complain("[Aborted]");

		switch (toupper(c)) {
		default:
			if (!d || (' ' < c && c < '\177')) {
				add_mess("[Type Y or N]");
				SitFor(PDelay<<1);
				continue;
			}
			if (c != CR && c != ' ')
				Ungetc(c);

			if (d == 'Y')
		case 'Y':	d = YES;
			else
		case 'N':	d = NO;
		}
		Asking = NO;
		message(NullStr);
		return d;
	}
	/* NOTREACHED */
}

/*
 * Expand environment variables in line.
 * this is called in `f_complete' before any file searching is done.
 * (the original JOVE 4.14 expanded environment variables in real_ask
 *  conditional to a JOVE variable; I [TRH] don't think that that is a
 *  good idea since it will probably hit you when you're least expecting it,
 *  e.g., in a search string. In practice, you need environment variable
 *  expansion only in file names)
 */
int
env_expand(buf)
char	*buf;
{
	char		work[LBSIZE];	/* we build string in here */
	register char	*s = buf,
			*d = work,
			*v;
	register int	nsubst = 0;

#	define DOLLAR	'$'

#if vms	/* `$' is a valid filename character; use single quote instead. */
#	undef DOLLAR
#	define DOLLAR	'\''
#endif
	while (*d = *s++) {
		if (*d++ != DOLLAR)
			continue;
		if ((*d = *s++) == '\0')
			break;
		if (*d == DOLLAR)			/* '$$' -> '$' */
			continue;
		v = d;
		if (*d == '{') {
			while (*d = *s++)
				if (*d++ == '}') {
					--d;
					++s;		/*  skip '}' */
					break;
				}
		}
		else {
			WITH_TABLE(CMODE);
			do {
				if (*d == DOLLAR) {
#if vms
					++s;
#endif
					break;
				}
				if (!isword(*d++)) {
					--d;
					break;
				}
			} while (*d = *s++);
			END_TABLE();
		}
		*d = '\0';
		--s;	/* one too far */

		if (d == v)			/* no word following '$' */
			continue;

		if (d = getenv(v))
			d = appcpy(v - 1, d);	/*  overwrite '$' */
		else
			d = v - 1;
		nsubst++;

	}
	if (nsubst)
		strcpy(buf, work);
	return nsubst;
}

#ifdef CHDIR
#   ifndef TINY
public int	ask_dir_only ZERO;
#	define ASK_DIR
#   endif
#endif

#ifdef F_COMPLETION

#include "readdir.h"

private char	*fc_filebase;

DEF_STR( "bad-filename-extensions", BadExtensions, 128, V_STRING ) _IF(def F_COMPLETION)_IF(def PRIVATE)
	= ".o";
DEF_INT( "display-bad-filenames", DispBadFs, V_BOOL ) _IF(def F_COMPLETION)_IF(def PRIVATE)
	= YES;
#ifndef TINY
DEF_INT( "display-filetype", DispFileType, V_BOOL ) _IF(def F_COMPLETION)_IF(ndef TINY)_IF(def PRIVATE)
	= YES;

/* filetype(): {{mostly stolen from tcsh}}
 *	Return a character that signifies the filetype of `filename'
 *	symbology from 4.3 ls command.
 */
private int	filetype __(( const char *_(filename) ));
private int
filetype(filename)
const char	*filename;
{
	struct stat	stbuf;
	register int	mode;

	if (lstat(rel_name(filename), &stbuf) == 0) {
		mode = stbuf.st_mode;
#ifdef S_ISLNK
		if (S_ISLNK(mode)) {	/* Symbolic link */
#   ifdef S_ISVTX
			/* Some file systems (like autonfsmount) set the
			   `sticky' bit on symbolic links that point to
			   a(n unmounted) directory, for performance reasons. */
			if (stbuf.st_mode & S_ISVTX)
				return ('>');
#   endif
			if (zstat(filename, &stbuf) != 0)
				return ('&');
			if (S_ISDIR(stbuf.st_mode))
				return ('>');
			return ('@');
		}
#endif
#ifdef S_ISSOCK
		if (S_ISSOCK(mode))	/* Socket */
			return ('=');
#endif
#ifdef S_ISFIFO
		if (S_ISFIFO(mode))	/* Named Pipe */
			return ('|');
#endif
#ifdef S_ISHIDDEN
		if (S_ISHIDDEN(mode))	/* Hidden Directory [aix] */
			return ('+');
#endif
#ifdef S_ISCDF
		if (S_ISCDF(mode))	/* Context Dependent Files [hpux] */
			return ('+');
#endif
#ifdef S_ISNWK
		if (S_ISNWK(mode))	/* Network Special [hpux] */
			return (':');
#endif
		if (S_ISCHR(mode))	/* char device */
			return ('%');
		if (S_ISBLK(mode))	/* block device */
			return ('#');
		if (S_ISDIR(mode))	/* normal Directory */
			return ('/');
		if (mode & (S_IXUSR|S_IXGRP|S_IXOTH))
			return ('*');
    }
    return ('\0');
}
#endif /* TINY */

int
isdir(name)
const char	*name;
{
	struct stat	stbuf;
	char		filebuf[FILESIZE];

	zstat(PathParse(name, filebuf), &stbuf);
	return S_ISDIR(stbuf.st_mode);
}


/*
 * [TRH] this routine is now non-destructive.
 */

int
bad_extension(name)
const char	*name;
{
	register const char
			*ext,
			*p;
	register int	ext_len,
			name_len = strlen(p = name);

#if vms /* ignore version numbers */
	for (ext = p + name_len; ext > p; )
		if (*--ext == ';') {
			name_len = ext - p;
			break;
		}
#endif
	/* also ignore "." and ".." */

	if (*p++ == '.' && (*p == '\0' || (*p++ == '.' && *p == '\0')))
		return YES;

	p = BadExtensions;
	do {
		for (ext = p;  *p != ' '; ) {
			if (*p++ == '\0') {
				--p;
				break;
			}
		}
		if ((ext_len = (p - ext)) == 0)
			continue;
		if (ext_len >= name_len)
			continue;
		if (numcomp(&name[name_len - ext_len], ext) == ext_len)
			return YES;
	} while (*p++);

	return NO;
}

private int f_match __(( const char *_(file) ));
private int
f_match(file)
const char	*file;
{
	register char	*base = fc_filebase;
	register int	matching = NO,
			n;

	if ((base[n = numcomp(file, base)] == '\0') &&
	    (file[n] == '\0' /* exact match */ ||
	     True(DispBadFs) || !bad_extension(file))) {
		++matching;
#ifdef ASK_DIR
#   ifndef DOS		/* We arranged for DOS to select directories in readdir
			   (see readdir.h), so we won't need this. */
#	ifndef MAC
		if (ask_dir_only >= 0) {
					/* `base' is a prefix of `file'... */
			strcpy(base, file);
			matching = isdir(linebuf);
			base[n] = '\0';	/* ...so this restores it. */
		}
#	endif
#   endif
#endif
	}
	return matching;
}

private const char NoMatch[] = " [No match]";

/* Complete a single filename.  Also, returns whether to auto-list. */

private int fill_in __(( char **_(dir_vec) ));
private int
fill_in(dir_vec)
register char	**dir_vec;
{
	register int	i,
			minmatch = strlen(fc_filebase),
    			numfound = 0,
			fc_filelen = minmatch,
			exactmatch = NO;
	const char	*lastmatch = *dir_vec;

	for (; *dir_vec; *dir_vec++) {
		i = numcomp(lastmatch, *dir_vec);
		/*
		 * Check for bad filenames only if we didn't already have
		 * them filtered out in f_match(), that is, if it is an
		 * exact match OR if "display-bad-filenames" is ON.
		 * (Exact match is ALWAYS checked.)
		 */
		if (((i == fc_filelen && (*dir_vec)[i] == '\0' &&
		      (exactmatch++, TRUE)) || True(DispBadFs)) &&
		    bad_extension(*dir_vec))
			continue;
		if (!numfound)
			minmatch = strlen(*dir_vec);
		else if (i < minmatch)
			minmatch = i;
		numfound++;
		lastmatch = *dir_vec;
	}

	if (minmatch > fc_filelen) {
		null_ncpy(fc_filebase, lastmatch, minmatch);
		minmatch = -1;		/* to indicate that something changed */
		makedirty(curline);
	}
	Eol();

	/* did we find a new directory? */
	if ((--numfound == 0) && ((i = curchar - 1) >= 0) &&
	    (!ISDIRSEP(linebuf[i])) && (isdir(linebuf))) {
#if vms		/* Kludge for VMS: remove trailing ".dir" extension.
		   (assumes version number is already removed by readdir) */
		if (i - 3 >= 0 && casencmp(&linebuf[i - 3], ".dir", 4) == 0)
			exp_p = NO, exp = -4, DelNChar(), exp = 1;
#endif
		Insert(SLASH);
	}
	else if (minmatch >= 0) {
		/* nothing changed... (remember we decremented numfound) */
		add_mess((numfound == 0) ? " [Unique]" :
			 (exactmatch) ? " [Exact]" :
			 (numfound > 0) ? " [Ambiguous]" : NoMatch);
		if ((True(ComplAutoHelp) || exp_p) && numfound > 0) {
			redisplay();	/* kludge...to show the above. */
			updmesg();
			return YES;
		}
	}
	return NO;
}

private int max_len __(( char **_(dir_vec) ));
private int
max_len(dir_vec)
register char	**dir_vec;
{
	register int	len,
			maxlen = 0;

	while (*dir_vec)
		if ((len = strlen(*dir_vec++)) > maxlen)
			maxlen = len;
	return maxlen;
}

/* called from do_ask() when one of "\r\n \t?" is typed.  Does the right
   thing, depending on which. */

private int f_complete __(( int _(c) ));
private int
f_complete(c)
{
	char		dirbuf[FILESIZE],
			**dir_vec;
	const char	*dir;
    {
	register char	*lp = linebuf,
			*bp;

	if (env_expand(lp))
		Eol();
	if (c == CR || c == LF)
		return 0;	/* tells ask to return now */

	dir = pwd();

	FIX_FILENAME(lp);
	if ((bp = basename(lp)) != lp) {
		register char savec = *bp;
		*bp = '\0';
		dir = PathParse(lp, dirbuf);
		*bp = savec;
	}
	fc_filebase = bp;
    }
    {
	register int	nentries;

	if ((nentries = scandir(dir, &dir_vec, f_match, alphacomp)) < 0) {
		add_mess(" [Unknown directory: %s]", dir);
		return 1;
	}
	if (nentries == 0)
		add_mess(NoMatch);
	else if (c == '?' || True(fill_in(dir_vec))) {
		/* we're a '?' */
		register int	i,
				which,
				lines,
				colwidth;

		TOstart("*Completion*", FALSE);	/* false means newline only on request */
		if (True(DispBadFs)) {
			Typeout("(! means file will not be chosen unless typed explicitly)");
			Typeout((char *) 0);
		}
		Typeout("Possible completions (in %s):", dir);
		Typeout((char *) 0);

		/* each column padded with at least 3 blanks */
		colwidth = max_len(dir_vec) + 3;
		lines = 1 + (nentries / (CO / colwidth));

		{ i = 0; } do {
			{ which = i; } do {
				int		isbad = NO;
				const char	*f = dir_vec[which];

				if (True(DispBadFs))
					isbad = bad_extension(f);
#ifndef TINY
			    {				/* a la "ls -F" */
				char		tmp[FILESIZE];
				int		tc;

				if (True(DispFileType) &&
				    (tc = filetype(make_filename(tmp, dir, f))))
					f = sprint("%s%c", f, tc);
			    }
#endif /* !TINY */
				Typeout("%s%-*s", isbad ? "!" : NullStr,
					colwidth - isbad, f);
			} while ((which += lines) < nentries);
			Typeout((char *) 0);
#ifndef TINY
			if (TOabort)
				break;
#endif
		} while (++i < lines);
		TOstop();
	}
	freedir(&dir_vec, nentries);
    }
	return 1;
}
#endif /* F_COMPLETION */

#ifdef FILESELECTOR
DEF_INT( "file-selector", UseFSel, V_BOOL ) _IF(def FILESELECTOR)_IF(def PRIVATE) ZERO;

int	FSelMode ZERO;
#endif

char *
ask_file(prompt, def, buf)
const char	*prompt;
const char	*def;
char		*buf;
{
	register const char	*_prompt;
	register char	*ans,
			*pretty = pr_name(def);

#ifdef ASK_DIR
	/* `ask_dir_only' flag is set to +1 by "cd" and "pushd" prior
	   to asking, to request directories only.  Decrement it now, so
	   that after an abort in `do_ask' or `FileSelector', the next
	   call to this function will consider all files again. */
	ask_dir_only--;
#endif
#ifdef FILESELECTOR
    if (True(UseFSel) && !Inputp && (!in_macro() || Interactive)) {
	if (!(_prompt = prompt))
		_prompt = ProcFmt;
	if (!(ans = FileSelector(sprint(_prompt), pretty, Minibuf)) &&
	    !(ans = pretty))
		complain("[No default file name]");
    } else {
	FSelMode = READ;	/* reset for next time */
#endif
	if (!(_prompt = prompt))
		if (pretty && *pretty)
			_prompt = ProcDefFmt;
		else
			_prompt = ProcFmt;
#ifdef F_COMPLETION
	if (!(ans = do_ask("\r\n \t?", f_complete, pretty, _prompt, pretty)) &&
	    !(ans = pretty))
		complain("[No default file name]");
#else
	ans = ask(pretty, _prompt, pretty);
#endif
#ifdef FILESELECTOR
    }
#endif
#ifdef ASK_DIR
	ask_dir_only = 0;
#endif
	return PathParse(ans, buf);
}


#ifdef ARG_EXPAND

#   ifndef F_COMPLETION
	#error "ARG_EXPAND requires F_COMPLETION"
#   endif

#include "re.h"

/* This is for systems without a decent (unix-like) shell.
   The usual /bin/sh file metacharacters (plus some expansions)
   are supported. */

DEF_INT( "expand-command-line-arguments", ExpCmdArgs, V_BOOL ) _IF(def ARG_EXPAND)_IF(def PRIVATE)
	= YES;

/* Check if `filepattern' contains any wildcard characters,
   remove escape characters from it and build the equivalent RE pattern. */

private int	wildcarded __(( const char *_(pattern), char *_(REsult) ));
private int
wildcarded(filepattern, REsult)
const char	*filepattern;
char		*REsult;	/* equivalent RE pattern ends up here */
{
	register char	c;
	register char	*d = REsult;
	register const char	*s = filepattern;
	register int	wildcarded = NO;

#	define BRA	'['
#	define KET	']'
#	define QUOTE	'\\'

#   if vms	/* VMS already uses `[' and `]' as directory delimiters,
		   so we have to use something else for character sets. */
#	undef BRA
#	define BRA	'('
#	undef KET
#	define KET	')'
#   endif

#   ifdef DOS	/* we cannot use backslash as escape character since it
		   is already in use as directory separator. Sigh... */
#	undef QUOTE
#	define QUOTE	'`'
#   endif

	while (c = *s++) {
		switch (*d++ = c) {
		default:
			continue;
		case QUOTE:
#   if (QUOTE != '\\')
			d[-1] = '\\';
#   endif
			c = *s;
			if (index("[*?+,", c))
				*d++ = c;
			else
				d[-1] = c;

			/* remove escape in literal string */
			strcpy(s - 1, s);
			continue;
		case '+':
			wildcarded++;
			continue;
		case '?':
		case '*':
			wildcarded++;
			d[-1] = '.', *d++ = c;
			continue;
		case '|':
		case '{':
		case '}':
			wildcarded++;
		case '.':
			d[-1] = '\\', *d++ = c;
			continue;
		case BRA:
#   if (BRA != '[')
			d[-1] = '[';
#   endif
			if (*s == '^' || *s == '!' || *s == '~')
				*d++ = '^', s++;
			wildcarded++;
			do {
				if (*s == '\0') {
					/* bail out on malformed pattern */
					wildcarded = NO;
					break;
				}
				*d++ = *s++;
			} while (*s != KET);
#   if (KET != ']')
			*d++ = ']';
			s++;
#   endif
			continue;
		}
	}
	*d++ = '$';
	*d = '\0';

	return wildcarded;
}

/* filter for scandir. Minibuf should contain RE pattern to match. */

private int	arg_match __(( const char *_(name) ));
private int
arg_match(name)
const char	*name;
{
	return LookingAt(Minibuf, name, 0);
}

/* first call, with arg != NULL, initializes the name list and returns
   the first match.  Subsequent calls, with arg == NULL, hand out the
   rest of the list, one by one, until the list is exhausted. */

char *
arg_expand(arg)
const char	*arg;
{
	static struct {
		char	**base;
		char	**curr;
		int	length;
		char	*directory;
	} list ZERO;

	char		tmp[FILESIZE];
	register char	*s,
			*d;

	if (False(ExpCmdArgs))
		return (char *) arg;

	if (arg) {				/* build a new list */
		if (list.base) {		/* free any leftover list */
			freedir(&list.base, list.length);
			free(list.directory);
		}

		env_expand(d = strcpy(genbuf, arg));

		/* If the argument does not contain wildcard characters,
		   we have an easy job. (we don't support wildcards in the
		   directory part of the pathname yet.) */

		if (!wildcarded(s = basename(d), Minibuf))
			return d;

		if (s != d) {
			char save = *s;
			*s = '\0';
			list.directory = copystr(d);
			d = PathParse(d, tmp);
			*s = save;
		} else {
			list.directory = copystr(NullStr);
			d = pwd();
		}
#ifdef ASK_DIR
		ask_dir_only = -1;
#endif
		list.length = scandir(d, &list.base, arg_match, alphacomp);
		list.curr = list.base;
		if (list.length <= 0)
			return genbuf;	/* no match; return original pattern */
	}

	if (list.base == NULL)
		return NULL;

	if ((s = *list.curr++) == NULL) {
		freedir(&list.base, list.length);
		free(list.directory);
		return NULL;
	}

	return make_filename(genbuf, list.directory, s);
}

#endif /* ARG_EXPAND */

/*======================================================================
 * $Log: ask.c,v $
 * Revision 14.32.0.12  1994/06/23  02:46:23  tom
 * (f_complete): fix name of scratch buffer.
 *
 * Revision 14.32.0.10  1994/04/22  18:24:21  tom
 * (ask,do_ask,yes_or_no_p): use `va_register va_list'.
 *
 * Revision 14.32.0.8  1993/11/01  16:42:20  tom
 * (real_ask): reset this_cmd ater ^C ^S ^R insert to force num.arg reset;
 * (fill_in): show completion list with C-U <space, tab>.
 *
 * Revision 14.32.0.8  1993/11/01  16:42:20  tom
 * *** empty log message ***
 *
 * Revision 14.32.0.7  1993/10/29  02:43:03  tom
 * wildcarded(): const-ify.
 *
 * Revision 14.32.0.6  1993/10/28  00:54:50  tom
 * f_complete: add FIX_FILENAME to allow completion on case-insensitive names.
 * wildcarded: const fix.
 *
 * Revision 14.32  1993/06/09  01:02:21  tom
 * optimize for autonfsmount symbolic directory links.
 *
 * Revision 14.31  1993/02/15  02:27:35  tom
 * remove (void) casts; real_ask() now always restores next window to orig.
 * buffer; lotsa random optimizations.
 *
 * Revision 14.30  1993/02/05  18:11:00  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 14.28  1992/10/22  01:19:58  tom
 * make filetype() private and add prototype.
 *
 * Revision 14.27  1992/09/10  05:17:36  tom
 * real_ask(): don't abort macro execution when Interactive;
 * fill_in(): force display of [Ambiguous] etc. when "completion-auto-help" set.
 *
 * Revision 14.26  1992/08/26  23:56:50  tom
 * add support for "completion-auto-help" in f_complete() and fill_in();
 * PRIVATE-ized some Variable defs; add RCS directives.
 *
 */
