1*4887Schin /***********************************************************************
2*4887Schin *                                                                      *
3*4887Schin *               This software is part of the ast package               *
4*4887Schin *           Copyright (c) 1982-2007 AT&T Knowledge Ventures            *
5*4887Schin *                      and is licensed under the                       *
6*4887Schin *                  Common Public License, Version 1.0                  *
7*4887Schin *                      by AT&T Knowledge Ventures                      *
8*4887Schin *                                                                      *
9*4887Schin *                A copy of the License is available at                 *
10*4887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
11*4887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*4887Schin *                                                                      *
13*4887Schin *              Information and Software Systems Research               *
14*4887Schin *                            AT&T Research                             *
15*4887Schin *                           Florham Park NJ                            *
16*4887Schin *                                                                      *
17*4887Schin *                  David Korn <dgk@research.att.com>                   *
18*4887Schin *                                                                      *
19*4887Schin ***********************************************************************/
20*4887Schin #pragma prototyped
21*4887Schin /*
22*4887Schin  *  completion.c - command and file completion for shell editors
23*4887Schin  *
24*4887Schin  */
25*4887Schin 
26*4887Schin #include	"defs.h"
27*4887Schin #include	<ctype.h>
28*4887Schin #include	<ast_wchar.h>
29*4887Schin #include	"lexstates.h"
30*4887Schin #include	"path.h"
31*4887Schin #include	"io.h"
32*4887Schin #include	"edit.h"
33*4887Schin #include	"history.h"
34*4887Schin 
35*4887Schin static int charcmp(int a, int b, int nocase)
36*4887Schin {
37*4887Schin 	if(nocase)
38*4887Schin 	{
39*4887Schin 		if(isupper(a))
40*4887Schin 			a = tolower(a);
41*4887Schin 		if(isupper(b))
42*4887Schin 			b = tolower(b);
43*4887Schin 	}
44*4887Schin 	return(a==b);
45*4887Schin }
46*4887Schin 
47*4887Schin /*
48*4887Schin  *  overwrites <str> to common prefix of <str> and <newstr>
49*4887Schin  *  if <str> is equal to <newstr> returns  <str>+strlen(<str>)+1
50*4887Schin  *  otherwise returns <str>+strlen(<str>)
51*4887Schin  */
52*4887Schin static char *overlaid(register char *str,register const char *newstr,int nocase)
53*4887Schin {
54*4887Schin 	register int c,d;
55*4887Schin 	while((c= *(unsigned char *)str) && ((d= *(unsigned char*)newstr++),charcmp(c,d,nocase)))
56*4887Schin 		str++;
57*4887Schin 	if(*str)
58*4887Schin 		*str = 0;
59*4887Schin 	else if(*newstr==0)
60*4887Schin 		str++;
61*4887Schin 	return(str);
62*4887Schin }
63*4887Schin 
64*4887Schin 
65*4887Schin /*
66*4887Schin  * returns pointer to beginning of expansion and sets type of expansion
67*4887Schin  */
68*4887Schin static char *find_begin(char outbuff[], char *last, int endchar, int *type)
69*4887Schin {
70*4887Schin 	register char	*cp=outbuff, *bp, *xp;
71*4887Schin 	register int 	c,inquote = 0;
72*4887Schin 	bp = outbuff;
73*4887Schin 	*type = 0;
74*4887Schin 	while(cp < last)
75*4887Schin 	{
76*4887Schin 		xp = cp;
77*4887Schin 		switch(c= mbchar(cp))
78*4887Schin 		{
79*4887Schin 		    case '\'': case '"':
80*4887Schin 			if(!inquote)
81*4887Schin 			{
82*4887Schin 				inquote = c;
83*4887Schin 				bp = xp;
84*4887Schin 				break;
85*4887Schin 			}
86*4887Schin 			if(inquote==c)
87*4887Schin 				inquote = 0;
88*4887Schin 			break;
89*4887Schin 		    case '\\':
90*4887Schin 			if(inquote != '\'')
91*4887Schin 				mbchar(cp);
92*4887Schin 			break;
93*4887Schin 		    case '$':
94*4887Schin 			if(inquote == '\'')
95*4887Schin 				break;
96*4887Schin 			c = *(unsigned char*)cp;
97*4887Schin 			if(isaletter(c) || c=='{')
98*4887Schin 			{
99*4887Schin 				int dot = '.';
100*4887Schin 				if(c=='{')
101*4887Schin 				{
102*4887Schin 					xp = cp;
103*4887Schin 					mbchar(cp);
104*4887Schin 					c = *(unsigned char*)cp;
105*4887Schin 					if(c!='.' && !isaletter(c))
106*4887Schin 						break;
107*4887Schin 				}
108*4887Schin 				else
109*4887Schin 					dot = 'a';
110*4887Schin 				while(cp < last)
111*4887Schin 				{
112*4887Schin 					if((c= mbchar(cp)) , c!=dot && !isaname(c))
113*4887Schin 						break;
114*4887Schin 				}
115*4887Schin 				if(cp>=last)
116*4887Schin 				{
117*4887Schin 					*type='$';
118*4887Schin 					return(++xp);
119*4887Schin 				}
120*4887Schin 			}
121*4887Schin 			else if(c=='(')
122*4887Schin 			{
123*4887Schin 				xp = find_begin(cp,last,')',type);
124*4887Schin 				if(*(cp=xp)!=')')
125*4887Schin 					bp = xp;
126*4887Schin 				else
127*4887Schin 					cp++;
128*4887Schin 			}
129*4887Schin 			break;
130*4887Schin 		    case '=':
131*4887Schin 			if(!inquote)
132*4887Schin 				bp = cp;
133*4887Schin 			break;
134*4887Schin 		    case '~':
135*4887Schin 			if(*cp=='(')
136*4887Schin 				break;
137*4887Schin 			/* fall through */
138*4887Schin 		    default:
139*4887Schin 			if(c && c==endchar)
140*4887Schin 				return(xp);
141*4887Schin 			if(!inquote && ismeta(c))
142*4887Schin 				bp = cp;
143*4887Schin 			break;
144*4887Schin 		}
145*4887Schin 	}
146*4887Schin 	if(inquote && *bp==inquote)
147*4887Schin 		*type = *bp++;
148*4887Schin 	return(bp);
149*4887Schin }
150*4887Schin 
151*4887Schin /*
152*4887Schin  * file name generation for edit modes
153*4887Schin  * non-zero exit for error, <0 ring bell
154*4887Schin  * don't search back past beginning of the buffer
155*4887Schin  * mode is '*' for inline expansion,
156*4887Schin  * mode is '\' for filename completion
157*4887Schin  * mode is '=' cause files to be listed in select format
158*4887Schin  */
159*4887Schin 
160*4887Schin int ed_expand(Edit_t *ep, char outbuff[],int *cur,int *eol,int mode, int count)
161*4887Schin {
162*4887Schin 	struct comnod	*comptr;
163*4887Schin 	struct argnod	*ap;
164*4887Schin 	register char	*out;
165*4887Schin 	char 		*av[2], *begin , *dir=0;
166*4887Schin 	int		addstar=0, rval=0, var=0, strip=1;
167*4887Schin 	int 		nomarkdirs = !sh_isoption(SH_MARKDIRS);
168*4887Schin 	sh_onstate(SH_FCOMPLETE);
169*4887Schin 	if(ep->e_nlist)
170*4887Schin 	{
171*4887Schin 		if(mode=='=' && count>0)
172*4887Schin 		{
173*4887Schin 			if(count> ep->e_nlist)
174*4887Schin 				return(-1);
175*4887Schin 			mode = '*';
176*4887Schin 			av[0] = ep->e_clist[count-1];
177*4887Schin 			av[1] = 0;
178*4887Schin 		}
179*4887Schin 		else
180*4887Schin 		{
181*4887Schin 			stakset(ep->e_stkptr,ep->e_stkoff);
182*4887Schin 			ep->e_nlist = 0;
183*4887Schin 		}
184*4887Schin 	}
185*4887Schin 	comptr = (struct comnod*)stakalloc(sizeof(struct comnod));
186*4887Schin 	ap = (struct argnod*)stakseek(ARGVAL);
187*4887Schin #if SHOPT_MULTIBYTE
188*4887Schin 	{
189*4887Schin 		register int c = *cur;
190*4887Schin 		register genchar *cp;
191*4887Schin 		/* adjust cur */
192*4887Schin 		cp = (genchar *)outbuff + *cur;
193*4887Schin 		c = *cp;
194*4887Schin 		*cp = 0;
195*4887Schin 		*cur = ed_external((genchar*)outbuff,(char*)stakptr(0));
196*4887Schin 		*cp = c;
197*4887Schin 		*eol = ed_external((genchar*)outbuff,outbuff);
198*4887Schin 	}
199*4887Schin #endif /* SHOPT_MULTIBYTE */
200*4887Schin 	out = outbuff + *cur + (sh_isoption(SH_VI)!=0);
201*4887Schin 	comptr->comtyp = COMSCAN;
202*4887Schin 	comptr->comarg = ap;
203*4887Schin 	ap->argflag = (ARG_MAC|ARG_EXP);
204*4887Schin 	ap->argnxt.ap = 0;
205*4887Schin 	ap->argchn.cp = 0;
206*4887Schin 	{
207*4887Schin 		register int c;
208*4887Schin 		char *last = out;
209*4887Schin 		c =  *(unsigned char*)out;
210*4887Schin 		begin = out = find_begin(outbuff,last,0,&var);
211*4887Schin 		/* addstar set to zero if * should not be added */
212*4887Schin 		if(var=='$')
213*4887Schin 		{
214*4887Schin 			stakputs("${!");
215*4887Schin 			stakwrite(out,last-out);
216*4887Schin 			stakputs("@}");
217*4887Schin 			out = last;
218*4887Schin 		}
219*4887Schin 		else
220*4887Schin 		{
221*4887Schin 			addstar = '*';
222*4887Schin 			while(out < last)
223*4887Schin 			{
224*4887Schin 				c = *(unsigned char*)out;
225*4887Schin 				if(isexp(c))
226*4887Schin 					addstar = 0;
227*4887Schin 				if (c == '/')
228*4887Schin 				{
229*4887Schin 					if(addstar == 0)
230*4887Schin 						strip = 0;
231*4887Schin 					dir = out+1;
232*4887Schin 				}
233*4887Schin 				stakputc(c);
234*4887Schin 				out++;
235*4887Schin 			}
236*4887Schin 		}
237*4887Schin 		if(var!='$' && mode=='\\' && out[-1]!='*')
238*4887Schin 			addstar = '*';
239*4887Schin 		if(*begin=='~' && !strchr(begin,'/'))
240*4887Schin 			addstar = 0;
241*4887Schin 		stakputc(addstar);
242*4887Schin 		ap = (struct argnod*)stakfreeze(1);
243*4887Schin 	}
244*4887Schin 	if(mode!='*')
245*4887Schin 		sh_onoption(SH_MARKDIRS);
246*4887Schin 	{
247*4887Schin 		register char	**com;
248*4887Schin 		char		*cp=begin, *left=0, *saveout=".";
249*4887Schin 		int	 	nocase=0,narg,cmd_completion=0;
250*4887Schin 		register 	int size='x';
251*4887Schin 		while(cp>outbuff && ((size=cp[-1])==' ' || size=='\t'))
252*4887Schin 			cp--;
253*4887Schin 		if(!var && !strchr(ap->argval,'/') && (((cp==outbuff&&sh.nextprompt==1) || (strchr(";&|(",size)) && (cp==outbuff+1||size=='('||cp[-2]!='>') && *begin!='~' )))
254*4887Schin 		{
255*4887Schin 			cmd_completion=1;
256*4887Schin 			sh_onstate(SH_COMPLETE);
257*4887Schin 		}
258*4887Schin 		if(ep->e_nlist)
259*4887Schin 		{
260*4887Schin 			narg = 1;
261*4887Schin 			com = av;
262*4887Schin 			if(dir)
263*4887Schin 				begin += (dir-begin);
264*4887Schin 		}
265*4887Schin 		else
266*4887Schin 		{
267*4887Schin 			com = sh_argbuild(&narg,comptr,0);
268*4887Schin 			/* special handling for leading quotes */
269*4887Schin 			if(begin>outbuff && (begin[-1]=='"' || begin[-1]=='\''))
270*4887Schin 			begin--;
271*4887Schin 		}
272*4887Schin 		sh_offstate(SH_COMPLETE);
273*4887Schin                 /* allow a search to be aborted */
274*4887Schin 		if(sh.trapnote&SH_SIGSET)
275*4887Schin 		{
276*4887Schin 			rval = -1;
277*4887Schin 			goto done;
278*4887Schin 		}
279*4887Schin 		/*  match? */
280*4887Schin 		if (*com==0 || (narg <= 1 && (strcmp(ap->argval,*com)==0) || (addstar && com[0][strlen(*com)-1]=='*')))
281*4887Schin 		{
282*4887Schin 			rval = -1;
283*4887Schin 			goto done;
284*4887Schin 		}
285*4887Schin 		if(mode=='=')
286*4887Schin 		{
287*4887Schin 			if (strip && !cmd_completion)
288*4887Schin 			{
289*4887Schin 				register char **ptrcom;
290*4887Schin 				for(ptrcom=com;*ptrcom;ptrcom++)
291*4887Schin 					/* trim directory prefix */
292*4887Schin 					*ptrcom = path_basename(*ptrcom);
293*4887Schin 			}
294*4887Schin 			sfputc(sfstderr,'\n');
295*4887Schin 			sh_menu(sfstderr,narg,com);
296*4887Schin 			sfsync(sfstderr);
297*4887Schin 			ep->e_nlist = narg;
298*4887Schin 			ep->e_clist = com;
299*4887Schin 			goto done;
300*4887Schin 		}
301*4887Schin 		/* see if there is enough room */
302*4887Schin 		size = *eol - (out-begin);
303*4887Schin 		if(mode=='\\')
304*4887Schin 		{
305*4887Schin 			int c;
306*4887Schin 			if(dir)
307*4887Schin 			{
308*4887Schin 				c = *dir;
309*4887Schin 				*dir = 0;
310*4887Schin 				saveout = begin;
311*4887Schin 			}
312*4887Schin 			if(saveout=astconf("PATH_ATTRIBUTES",saveout,(char*)0))
313*4887Schin 				nocase = (strchr(saveout,'c')!=0);
314*4887Schin 			if(dir)
315*4887Schin 				*dir = c;
316*4887Schin 			/* just expand until name is unique */
317*4887Schin 			size += strlen(*com);
318*4887Schin 		}
319*4887Schin 		else
320*4887Schin 		{
321*4887Schin 			size += narg;
322*4887Schin 			{
323*4887Schin 				char **savcom = com;
324*4887Schin 				while (*com)
325*4887Schin 					size += strlen(cp=sh_fmtq(*com++));
326*4887Schin 				com = savcom;
327*4887Schin 			}
328*4887Schin 		}
329*4887Schin 		/* see if room for expansion */
330*4887Schin 		if(outbuff+size >= &outbuff[MAXLINE])
331*4887Schin 		{
332*4887Schin 			com[0] = ap->argval;
333*4887Schin 			com[1] = 0;
334*4887Schin 		}
335*4887Schin 		/* save remainder of the buffer */
336*4887Schin 		if(*out)
337*4887Schin 			left=stakcopy(out);
338*4887Schin 		if(cmd_completion && mode=='\\')
339*4887Schin 			out = strcopy(begin,path_basename(cp= *com++));
340*4887Schin 		else if(mode=='*')
341*4887Schin 		{
342*4887Schin 			if(ep->e_nlist && dir && var)
343*4887Schin 			{
344*4887Schin 				if(*cp==var)
345*4887Schin 					cp++;
346*4887Schin 				else
347*4887Schin 					*begin++ = var;
348*4887Schin 				out = strcopy(begin,cp);
349*4887Schin 				var = 0;
350*4887Schin 			}
351*4887Schin 			else
352*4887Schin 				out = strcopy(begin,sh_fmtq(*com));
353*4887Schin 			com++;
354*4887Schin 		}
355*4887Schin 		else
356*4887Schin 			out = strcopy(begin,*com++);
357*4887Schin 		if(mode=='\\')
358*4887Schin 		{
359*4887Schin 			saveout= ++out;
360*4887Schin 			while (*com && *begin)
361*4887Schin 			{
362*4887Schin 				if(cmd_completion)
363*4887Schin 					out = overlaid(begin,path_basename(*com++),nocase);
364*4887Schin 				else
365*4887Schin 					out = overlaid(begin,*com++,nocase);
366*4887Schin 			}
367*4887Schin 			mode = (out==saveout);
368*4887Schin 			if(out[-1]==0)
369*4887Schin 				out--;
370*4887Schin 			if(mode && out[-1]!='/')
371*4887Schin 			{
372*4887Schin 				if(cmd_completion)
373*4887Schin 				{
374*4887Schin 					Namval_t *np;
375*4887Schin 					/* add as tracked alias */
376*4887Schin #ifdef PATH_BFPATH
377*4887Schin 					Pathcomp_t *pp;
378*4887Schin 					if(*cp=='/' && (pp=path_dirfind(sh.pathlist,cp,'/')) && (np=nv_search(begin,sh.track_tree,NV_ADD)))
379*4887Schin 						path_alias(np,pp);
380*4887Schin #else
381*4887Schin 					if(*cp=='/' && (np=nv_search(begin,sh.track_tree,NV_ADD)))
382*4887Schin 						path_alias(np,cp);
383*4887Schin #endif
384*4887Schin 					out = strcopy(begin,cp);
385*4887Schin 				}
386*4887Schin 				/* add quotes if necessary */
387*4887Schin 				if((cp=sh_fmtq(begin))!=begin)
388*4887Schin 					out = strcopy(begin,cp);
389*4887Schin 				if(var=='$' && begin[-1]=='{')
390*4887Schin 					*out = '}';
391*4887Schin 				else
392*4887Schin 					*out = ' ';
393*4887Schin 				*++out = 0;
394*4887Schin 			}
395*4887Schin 			else if(out[-1]=='/' && (cp=sh_fmtq(begin))!=begin)
396*4887Schin 			{
397*4887Schin 				out = strcopy(begin,cp);
398*4887Schin 				if(out[-1] =='"' || out[-1]=='\'')
399*4887Schin 					  *--out = 0;;
400*4887Schin 			}
401*4887Schin 			if(*begin==0)
402*4887Schin 				ed_ringbell();
403*4887Schin 		}
404*4887Schin 		else
405*4887Schin 		{
406*4887Schin 			while (*com)
407*4887Schin 			{
408*4887Schin 				*out++  = ' ';
409*4887Schin 				out = strcopy(out,sh_fmtq(*com++));
410*4887Schin 			}
411*4887Schin 		}
412*4887Schin 		if(ep->e_nlist)
413*4887Schin 		{
414*4887Schin 			cp = com[-1];
415*4887Schin 			if(cp[strlen(cp)-1]!='/')
416*4887Schin 			{
417*4887Schin 				if(var=='$' && begin[-1]=='{')
418*4887Schin 					*out = '}';
419*4887Schin 				else
420*4887Schin 					*out = ' ';
421*4887Schin 				out++;
422*4887Schin 			}
423*4887Schin 			else if(out[-1] =='"' || out[-1]=='\'')
424*4887Schin 				out--;
425*4887Schin 			*out = 0;
426*4887Schin 		}
427*4887Schin 		*cur = (out-outbuff);
428*4887Schin 		/* restore rest of buffer */
429*4887Schin 		if(left)
430*4887Schin 			out = strcopy(out,left);
431*4887Schin 		*eol = (out-outbuff);
432*4887Schin 	}
433*4887Schin  done:
434*4887Schin 	sh_offstate(SH_FCOMPLETE);
435*4887Schin 	if(!ep->e_nlist)
436*4887Schin 		stakset(ep->e_stkptr,ep->e_stkoff);
437*4887Schin 	if(nomarkdirs)
438*4887Schin 		sh_offoption(SH_MARKDIRS);
439*4887Schin #if SHOPT_MULTIBYTE
440*4887Schin 	{
441*4887Schin 		register int c,n=0;
442*4887Schin 		/* first re-adjust cur */
443*4887Schin 		c = outbuff[*cur];
444*4887Schin 		outbuff[*cur] = 0;
445*4887Schin 		for(out=outbuff; *out;n++)
446*4887Schin 			mbchar(out);
447*4887Schin 		outbuff[*cur] = c;
448*4887Schin 		*cur = n;
449*4887Schin 		outbuff[*eol+1] = 0;
450*4887Schin 		*eol = ed_internal(outbuff,(genchar*)outbuff);
451*4887Schin 	}
452*4887Schin #endif /* SHOPT_MULTIBYTE */
453*4887Schin 	return(rval);
454*4887Schin }
455*4887Schin 
456*4887Schin /*
457*4887Schin  * look for edit macro named _i
458*4887Schin  * if found, puts the macro definition into lookahead buffer and returns 1
459*4887Schin  */
460*4887Schin int ed_macro(Edit_t *ep, register int i)
461*4887Schin {
462*4887Schin 	register char *out;
463*4887Schin 	Namval_t *np;
464*4887Schin 	genchar buff[LOOKAHEAD+1];
465*4887Schin 	if(i != '@')
466*4887Schin 		ep->e_macro[1] = i;
467*4887Schin 	/* undocumented feature, macros of the form <ESC>[c evoke alias __c */
468*4887Schin 	if(i=='_')
469*4887Schin 		ep->e_macro[2] = ed_getchar(ep,1);
470*4887Schin 	else
471*4887Schin 		ep->e_macro[2] = 0;
472*4887Schin 	if (isalnum(i)&&(np=nv_search(ep->e_macro,sh.alias_tree,HASH_SCOPE))&&(out=nv_getval(np)))
473*4887Schin 	{
474*4887Schin #if SHOPT_MULTIBYTE
475*4887Schin 		/* copy to buff in internal representation */
476*4887Schin 		int c = 0;
477*4887Schin 		if( strlen(out) > LOOKAHEAD )
478*4887Schin 		{
479*4887Schin 			c = out[LOOKAHEAD];
480*4887Schin 			out[LOOKAHEAD] = 0;
481*4887Schin 		}
482*4887Schin 		i = ed_internal(out,buff);
483*4887Schin 		if(c)
484*4887Schin 			out[LOOKAHEAD] = c;
485*4887Schin #else
486*4887Schin 		strncpy((char*)buff,out,LOOKAHEAD);
487*4887Schin 		buff[LOOKAHEAD] = 0;
488*4887Schin 		i = strlen((char*)buff);
489*4887Schin #endif /* SHOPT_MULTIBYTE */
490*4887Schin 		while(i-- > 0)
491*4887Schin 			ed_ungetchar(ep,buff[i]);
492*4887Schin 		return(1);
493*4887Schin 	}
494*4887Schin 	return(0);
495*4887Schin }
496*4887Schin 
497*4887Schin /*
498*4887Schin  * Enter the fc command on the current history line
499*4887Schin  */
500*4887Schin int ed_fulledit(Edit_t *ep)
501*4887Schin {
502*4887Schin 	register char *cp;
503*4887Schin 	if(!sh.hist_ptr)
504*4887Schin 		return(-1);
505*4887Schin 	/* use EDITOR on current command */
506*4887Schin 	if(ep->e_hline == ep->e_hismax)
507*4887Schin 	{
508*4887Schin 		if(ep->e_eol<0)
509*4887Schin 			return(-1);
510*4887Schin #if SHOPT_MULTIBYTE
511*4887Schin 		ep->e_inbuf[ep->e_eol+1] = 0;
512*4887Schin 		ed_external(ep->e_inbuf, (char *)ep->e_inbuf);
513*4887Schin #endif /* SHOPT_MULTIBYTE */
514*4887Schin 		sfwrite(sh.hist_ptr->histfp,(char*)ep->e_inbuf,ep->e_eol+1);
515*4887Schin 		sh_onstate(SH_HISTORY);
516*4887Schin 		hist_flush(sh.hist_ptr);
517*4887Schin 	}
518*4887Schin 	cp = strcopy((char*)ep->e_inbuf,e_runvi);
519*4887Schin 	cp = strcopy(cp, fmtbase((long)ep->e_hline,10,0));
520*4887Schin 	ep->e_eol = ((unsigned char*)cp - (unsigned char*)ep->e_inbuf)-(sh_isoption(SH_VI)!=0);
521*4887Schin 	return(0);
522*4887Schin }
523