14887Schin /***********************************************************************
24887Schin *                                                                      *
34887Schin *               This software is part of the ast package               *
4*8462SApril.Chin@Sun.COM *          Copyright (c) 1982-2008 AT&T Intellectual Property          *
54887Schin *                      and is licensed under the                       *
64887Schin *                  Common Public License, Version 1.0                  *
7*8462SApril.Chin@Sun.COM *                    by AT&T Intellectual Property                     *
84887Schin *                                                                      *
94887Schin *                A copy of the License is available at                 *
104887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
114887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
124887Schin *                                                                      *
134887Schin *              Information and Software Systems Research               *
144887Schin *                            AT&T Research                             *
154887Schin *                           Florham Park NJ                            *
164887Schin *                                                                      *
174887Schin *                  David Korn <dgk@research.att.com>                   *
184887Schin *                                                                      *
194887Schin ***********************************************************************/
204887Schin #pragma prototyped
214887Schin /*
224887Schin  *  completion.c - command and file completion for shell editors
234887Schin  *
244887Schin  */
254887Schin 
264887Schin #include	"defs.h"
274887Schin #include	<ctype.h>
284887Schin #include	<ast_wchar.h>
294887Schin #include	"lexstates.h"
304887Schin #include	"path.h"
314887Schin #include	"io.h"
324887Schin #include	"edit.h"
334887Schin #include	"history.h"
344887Schin 
354887Schin static int charcmp(int a, int b, int nocase)
364887Schin {
374887Schin 	if(nocase)
384887Schin 	{
394887Schin 		if(isupper(a))
404887Schin 			a = tolower(a);
414887Schin 		if(isupper(b))
424887Schin 			b = tolower(b);
434887Schin 	}
444887Schin 	return(a==b);
454887Schin }
464887Schin 
474887Schin /*
484887Schin  *  overwrites <str> to common prefix of <str> and <newstr>
494887Schin  *  if <str> is equal to <newstr> returns  <str>+strlen(<str>)+1
504887Schin  *  otherwise returns <str>+strlen(<str>)
514887Schin  */
524887Schin static char *overlaid(register char *str,register const char *newstr,int nocase)
534887Schin {
544887Schin 	register int c,d;
554887Schin 	while((c= *(unsigned char *)str) && ((d= *(unsigned char*)newstr++),charcmp(c,d,nocase)))
564887Schin 		str++;
574887Schin 	if(*str)
584887Schin 		*str = 0;
594887Schin 	else if(*newstr==0)
604887Schin 		str++;
614887Schin 	return(str);
624887Schin }
634887Schin 
644887Schin 
654887Schin /*
664887Schin  * returns pointer to beginning of expansion and sets type of expansion
674887Schin  */
684887Schin static char *find_begin(char outbuff[], char *last, int endchar, int *type)
694887Schin {
704887Schin 	register char	*cp=outbuff, *bp, *xp;
71*8462SApril.Chin@Sun.COM 	register int 	c,inquote = 0, inassign=0;
72*8462SApril.Chin@Sun.COM 	int		mode=*type;
734887Schin 	bp = outbuff;
744887Schin 	*type = 0;
754887Schin 	while(cp < last)
764887Schin 	{
774887Schin 		xp = cp;
784887Schin 		switch(c= mbchar(cp))
794887Schin 		{
804887Schin 		    case '\'': case '"':
814887Schin 			if(!inquote)
824887Schin 			{
834887Schin 				inquote = c;
844887Schin 				bp = xp;
854887Schin 				break;
864887Schin 			}
874887Schin 			if(inquote==c)
884887Schin 				inquote = 0;
894887Schin 			break;
904887Schin 		    case '\\':
914887Schin 			if(inquote != '\'')
924887Schin 				mbchar(cp);
934887Schin 			break;
944887Schin 		    case '$':
954887Schin 			if(inquote == '\'')
964887Schin 				break;
974887Schin 			c = *(unsigned char*)cp;
98*8462SApril.Chin@Sun.COM 			if(mode!='*' && (isaletter(c) || c=='{'))
994887Schin 			{
1004887Schin 				int dot = '.';
1014887Schin 				if(c=='{')
1024887Schin 				{
1034887Schin 					xp = cp;
1044887Schin 					mbchar(cp);
1054887Schin 					c = *(unsigned char*)cp;
1064887Schin 					if(c!='.' && !isaletter(c))
1074887Schin 						break;
1084887Schin 				}
1094887Schin 				else
1104887Schin 					dot = 'a';
1114887Schin 				while(cp < last)
1124887Schin 				{
1134887Schin 					if((c= mbchar(cp)) , c!=dot && !isaname(c))
1144887Schin 						break;
1154887Schin 				}
116*8462SApril.Chin@Sun.COM 				if(cp>=last && c!= '}')
1174887Schin 				{
1184887Schin 					*type='$';
1194887Schin 					return(++xp);
1204887Schin 				}
1214887Schin 			}
1224887Schin 			else if(c=='(')
1234887Schin 			{
124*8462SApril.Chin@Sun.COM 				*type = mode;
1254887Schin 				xp = find_begin(cp,last,')',type);
1264887Schin 				if(*(cp=xp)!=')')
1274887Schin 					bp = xp;
1284887Schin 				else
1294887Schin 					cp++;
1304887Schin 			}
1314887Schin 			break;
1324887Schin 		    case '=':
1334887Schin 			if(!inquote)
134*8462SApril.Chin@Sun.COM 			{
135*8462SApril.Chin@Sun.COM 				bp = cp;
136*8462SApril.Chin@Sun.COM 				inassign = 1;
137*8462SApril.Chin@Sun.COM 			}
138*8462SApril.Chin@Sun.COM 			break;
139*8462SApril.Chin@Sun.COM 		    case ':':
140*8462SApril.Chin@Sun.COM 			if(!inquote && inassign)
1414887Schin 				bp = cp;
1424887Schin 			break;
1434887Schin 		    case '~':
1444887Schin 			if(*cp=='(')
1454887Schin 				break;
1464887Schin 			/* fall through */
1474887Schin 		    default:
1484887Schin 			if(c && c==endchar)
1494887Schin 				return(xp);
1504887Schin 			if(!inquote && ismeta(c))
151*8462SApril.Chin@Sun.COM 			{
1524887Schin 				bp = cp;
153*8462SApril.Chin@Sun.COM 				inassign = 0;
154*8462SApril.Chin@Sun.COM 			}
1554887Schin 			break;
1564887Schin 		}
1574887Schin 	}
1584887Schin 	if(inquote && *bp==inquote)
1594887Schin 		*type = *bp++;
1604887Schin 	return(bp);
1614887Schin }
1624887Schin 
1634887Schin /*
1644887Schin  * file name generation for edit modes
1654887Schin  * non-zero exit for error, <0 ring bell
1664887Schin  * don't search back past beginning of the buffer
1674887Schin  * mode is '*' for inline expansion,
1684887Schin  * mode is '\' for filename completion
1694887Schin  * mode is '=' cause files to be listed in select format
1704887Schin  */
1714887Schin 
1724887Schin int ed_expand(Edit_t *ep, char outbuff[],int *cur,int *eol,int mode, int count)
1734887Schin {
1744887Schin 	struct comnod	*comptr;
1754887Schin 	struct argnod	*ap;
1764887Schin 	register char	*out;
1774887Schin 	char 		*av[2], *begin , *dir=0;
1784887Schin 	int		addstar=0, rval=0, var=0, strip=1;
1794887Schin 	int 		nomarkdirs = !sh_isoption(SH_MARKDIRS);
1804887Schin 	sh_onstate(SH_FCOMPLETE);
1814887Schin 	if(ep->e_nlist)
1824887Schin 	{
1834887Schin 		if(mode=='=' && count>0)
1844887Schin 		{
1854887Schin 			if(count> ep->e_nlist)
1864887Schin 				return(-1);
187*8462SApril.Chin@Sun.COM 			mode = '?';
1884887Schin 			av[0] = ep->e_clist[count-1];
1894887Schin 			av[1] = 0;
1904887Schin 		}
1914887Schin 		else
1924887Schin 		{
1934887Schin 			stakset(ep->e_stkptr,ep->e_stkoff);
1944887Schin 			ep->e_nlist = 0;
1954887Schin 		}
1964887Schin 	}
1974887Schin 	comptr = (struct comnod*)stakalloc(sizeof(struct comnod));
1984887Schin 	ap = (struct argnod*)stakseek(ARGVAL);
1994887Schin #if SHOPT_MULTIBYTE
2004887Schin 	{
2014887Schin 		register int c = *cur;
2024887Schin 		register genchar *cp;
2034887Schin 		/* adjust cur */
2044887Schin 		cp = (genchar *)outbuff + *cur;
2054887Schin 		c = *cp;
2064887Schin 		*cp = 0;
2074887Schin 		*cur = ed_external((genchar*)outbuff,(char*)stakptr(0));
2084887Schin 		*cp = c;
2094887Schin 		*eol = ed_external((genchar*)outbuff,outbuff);
2104887Schin 	}
2114887Schin #endif /* SHOPT_MULTIBYTE */
2124887Schin 	out = outbuff + *cur + (sh_isoption(SH_VI)!=0);
2134887Schin 	comptr->comtyp = COMSCAN;
2144887Schin 	comptr->comarg = ap;
2154887Schin 	ap->argflag = (ARG_MAC|ARG_EXP);
2164887Schin 	ap->argnxt.ap = 0;
2174887Schin 	ap->argchn.cp = 0;
2184887Schin 	{
2194887Schin 		register int c;
2204887Schin 		char *last = out;
2214887Schin 		c =  *(unsigned char*)out;
222*8462SApril.Chin@Sun.COM 		var = mode;
2234887Schin 		begin = out = find_begin(outbuff,last,0,&var);
2244887Schin 		/* addstar set to zero if * should not be added */
2254887Schin 		if(var=='$')
2264887Schin 		{
2274887Schin 			stakputs("${!");
2284887Schin 			stakwrite(out,last-out);
2294887Schin 			stakputs("@}");
2304887Schin 			out = last;
2314887Schin 		}
2324887Schin 		else
2334887Schin 		{
2344887Schin 			addstar = '*';
2354887Schin 			while(out < last)
2364887Schin 			{
2374887Schin 				c = *(unsigned char*)out;
2384887Schin 				if(isexp(c))
2394887Schin 					addstar = 0;
2404887Schin 				if (c == '/')
2414887Schin 				{
2424887Schin 					if(addstar == 0)
2434887Schin 						strip = 0;
2444887Schin 					dir = out+1;
2454887Schin 				}
2464887Schin 				stakputc(c);
2474887Schin 				out++;
2484887Schin 			}
2494887Schin 		}
250*8462SApril.Chin@Sun.COM 		if(mode=='?')
251*8462SApril.Chin@Sun.COM 			mode = '*';
2524887Schin 		if(var!='$' && mode=='\\' && out[-1]!='*')
2534887Schin 			addstar = '*';
2544887Schin 		if(*begin=='~' && !strchr(begin,'/'))
2554887Schin 			addstar = 0;
2564887Schin 		stakputc(addstar);
2574887Schin 		ap = (struct argnod*)stakfreeze(1);
2584887Schin 	}
2594887Schin 	if(mode!='*')
2604887Schin 		sh_onoption(SH_MARKDIRS);
2614887Schin 	{
2624887Schin 		register char	**com;
2634887Schin 		char		*cp=begin, *left=0, *saveout=".";
2644887Schin 		int	 	nocase=0,narg,cmd_completion=0;
2654887Schin 		register 	int size='x';
2664887Schin 		while(cp>outbuff && ((size=cp[-1])==' ' || size=='\t'))
2674887Schin 			cp--;
2684887Schin 		if(!var && !strchr(ap->argval,'/') && (((cp==outbuff&&sh.nextprompt==1) || (strchr(";&|(",size)) && (cp==outbuff+1||size=='('||cp[-2]!='>') && *begin!='~' )))
2694887Schin 		{
2704887Schin 			cmd_completion=1;
2714887Schin 			sh_onstate(SH_COMPLETE);
2724887Schin 		}
2734887Schin 		if(ep->e_nlist)
2744887Schin 		{
2754887Schin 			narg = 1;
2764887Schin 			com = av;
2774887Schin 			if(dir)
2784887Schin 				begin += (dir-begin);
2794887Schin 		}
2804887Schin 		else
2814887Schin 		{
282*8462SApril.Chin@Sun.COM 			com = sh_argbuild(ep->sh,&narg,comptr,0);
2834887Schin 			/* special handling for leading quotes */
2844887Schin 			if(begin>outbuff && (begin[-1]=='"' || begin[-1]=='\''))
2854887Schin 			begin--;
2864887Schin 		}
2874887Schin 		sh_offstate(SH_COMPLETE);
2884887Schin                 /* allow a search to be aborted */
2894887Schin 		if(sh.trapnote&SH_SIGSET)
2904887Schin 		{
2914887Schin 			rval = -1;
2924887Schin 			goto done;
2934887Schin 		}
2944887Schin 		/*  match? */
2954887Schin 		if (*com==0 || (narg <= 1 && (strcmp(ap->argval,*com)==0) || (addstar && com[0][strlen(*com)-1]=='*')))
2964887Schin 		{
2974887Schin 			rval = -1;
2984887Schin 			goto done;
2994887Schin 		}
3004887Schin 		if(mode=='=')
3014887Schin 		{
3024887Schin 			if (strip && !cmd_completion)
3034887Schin 			{
3044887Schin 				register char **ptrcom;
3054887Schin 				for(ptrcom=com;*ptrcom;ptrcom++)
3064887Schin 					/* trim directory prefix */
3074887Schin 					*ptrcom = path_basename(*ptrcom);
3084887Schin 			}
3094887Schin 			sfputc(sfstderr,'\n');
3104887Schin 			sh_menu(sfstderr,narg,com);
3114887Schin 			sfsync(sfstderr);
3124887Schin 			ep->e_nlist = narg;
3134887Schin 			ep->e_clist = com;
3144887Schin 			goto done;
3154887Schin 		}
3164887Schin 		/* see if there is enough room */
3174887Schin 		size = *eol - (out-begin);
3184887Schin 		if(mode=='\\')
3194887Schin 		{
3204887Schin 			int c;
3214887Schin 			if(dir)
3224887Schin 			{
3234887Schin 				c = *dir;
3244887Schin 				*dir = 0;
3254887Schin 				saveout = begin;
3264887Schin 			}
3274887Schin 			if(saveout=astconf("PATH_ATTRIBUTES",saveout,(char*)0))
3284887Schin 				nocase = (strchr(saveout,'c')!=0);
3294887Schin 			if(dir)
3304887Schin 				*dir = c;
3314887Schin 			/* just expand until name is unique */
3324887Schin 			size += strlen(*com);
3334887Schin 		}
3344887Schin 		else
3354887Schin 		{
3364887Schin 			size += narg;
3374887Schin 			{
3384887Schin 				char **savcom = com;
3394887Schin 				while (*com)
3404887Schin 					size += strlen(cp=sh_fmtq(*com++));
3414887Schin 				com = savcom;
3424887Schin 			}
3434887Schin 		}
3444887Schin 		/* see if room for expansion */
3454887Schin 		if(outbuff+size >= &outbuff[MAXLINE])
3464887Schin 		{
3474887Schin 			com[0] = ap->argval;
3484887Schin 			com[1] = 0;
3494887Schin 		}
3504887Schin 		/* save remainder of the buffer */
3514887Schin 		if(*out)
3524887Schin 			left=stakcopy(out);
3534887Schin 		if(cmd_completion && mode=='\\')
3544887Schin 			out = strcopy(begin,path_basename(cp= *com++));
3554887Schin 		else if(mode=='*')
3564887Schin 		{
3574887Schin 			if(ep->e_nlist && dir && var)
3584887Schin 			{
3594887Schin 				if(*cp==var)
3604887Schin 					cp++;
3614887Schin 				else
3624887Schin 					*begin++ = var;
3634887Schin 				out = strcopy(begin,cp);
3644887Schin 				var = 0;
3654887Schin 			}
3664887Schin 			else
3674887Schin 				out = strcopy(begin,sh_fmtq(*com));
3684887Schin 			com++;
3694887Schin 		}
3704887Schin 		else
3714887Schin 			out = strcopy(begin,*com++);
3724887Schin 		if(mode=='\\')
3734887Schin 		{
3744887Schin 			saveout= ++out;
3754887Schin 			while (*com && *begin)
3764887Schin 			{
3774887Schin 				if(cmd_completion)
3784887Schin 					out = overlaid(begin,path_basename(*com++),nocase);
3794887Schin 				else
3804887Schin 					out = overlaid(begin,*com++,nocase);
3814887Schin 			}
3824887Schin 			mode = (out==saveout);
3834887Schin 			if(out[-1]==0)
3844887Schin 				out--;
3854887Schin 			if(mode && out[-1]!='/')
3864887Schin 			{
3874887Schin 				if(cmd_completion)
3884887Schin 				{
3894887Schin 					Namval_t *np;
3904887Schin 					/* add as tracked alias */
3914887Schin 					Pathcomp_t *pp;
3924887Schin 					if(*cp=='/' && (pp=path_dirfind(sh.pathlist,cp,'/')) && (np=nv_search(begin,sh.track_tree,NV_ADD)))
3934887Schin 						path_alias(np,pp);
3944887Schin 					out = strcopy(begin,cp);
3954887Schin 				}
3964887Schin 				/* add quotes if necessary */
3974887Schin 				if((cp=sh_fmtq(begin))!=begin)
3984887Schin 					out = strcopy(begin,cp);
3994887Schin 				if(var=='$' && begin[-1]=='{')
4004887Schin 					*out = '}';
4014887Schin 				else
4024887Schin 					*out = ' ';
4034887Schin 				*++out = 0;
4044887Schin 			}
4054887Schin 			else if(out[-1]=='/' && (cp=sh_fmtq(begin))!=begin)
4064887Schin 			{
4074887Schin 				out = strcopy(begin,cp);
4084887Schin 				if(out[-1] =='"' || out[-1]=='\'')
4094887Schin 					  *--out = 0;;
4104887Schin 			}
4114887Schin 			if(*begin==0)
4124887Schin 				ed_ringbell();
4134887Schin 		}
4144887Schin 		else
4154887Schin 		{
4164887Schin 			while (*com)
4174887Schin 			{
4184887Schin 				*out++  = ' ';
4194887Schin 				out = strcopy(out,sh_fmtq(*com++));
4204887Schin 			}
4214887Schin 		}
4224887Schin 		if(ep->e_nlist)
4234887Schin 		{
4244887Schin 			cp = com[-1];
4254887Schin 			if(cp[strlen(cp)-1]!='/')
4264887Schin 			{
4274887Schin 				if(var=='$' && begin[-1]=='{')
4284887Schin 					*out = '}';
4294887Schin 				else
4304887Schin 					*out = ' ';
4314887Schin 				out++;
4324887Schin 			}
4334887Schin 			else if(out[-1] =='"' || out[-1]=='\'')
4344887Schin 				out--;
4354887Schin 			*out = 0;
4364887Schin 		}
4374887Schin 		*cur = (out-outbuff);
4384887Schin 		/* restore rest of buffer */
4394887Schin 		if(left)
4404887Schin 			out = strcopy(out,left);
4414887Schin 		*eol = (out-outbuff);
4424887Schin 	}
4434887Schin  done:
4444887Schin 	sh_offstate(SH_FCOMPLETE);
4454887Schin 	if(!ep->e_nlist)
4464887Schin 		stakset(ep->e_stkptr,ep->e_stkoff);
4474887Schin 	if(nomarkdirs)
4484887Schin 		sh_offoption(SH_MARKDIRS);
4494887Schin #if SHOPT_MULTIBYTE
4504887Schin 	{
4514887Schin 		register int c,n=0;
4524887Schin 		/* first re-adjust cur */
4534887Schin 		c = outbuff[*cur];
4544887Schin 		outbuff[*cur] = 0;
4554887Schin 		for(out=outbuff; *out;n++)
4564887Schin 			mbchar(out);
4574887Schin 		outbuff[*cur] = c;
4584887Schin 		*cur = n;
4594887Schin 		outbuff[*eol+1] = 0;
4604887Schin 		*eol = ed_internal(outbuff,(genchar*)outbuff);
4614887Schin 	}
4624887Schin #endif /* SHOPT_MULTIBYTE */
4634887Schin 	return(rval);
4644887Schin }
4654887Schin 
4664887Schin /*
4674887Schin  * look for edit macro named _i
4684887Schin  * if found, puts the macro definition into lookahead buffer and returns 1
4694887Schin  */
4704887Schin int ed_macro(Edit_t *ep, register int i)
4714887Schin {
4724887Schin 	register char *out;
4734887Schin 	Namval_t *np;
4744887Schin 	genchar buff[LOOKAHEAD+1];
4754887Schin 	if(i != '@')
4764887Schin 		ep->e_macro[1] = i;
4774887Schin 	/* undocumented feature, macros of the form <ESC>[c evoke alias __c */
4784887Schin 	if(i=='_')
4794887Schin 		ep->e_macro[2] = ed_getchar(ep,1);
4804887Schin 	else
4814887Schin 		ep->e_macro[2] = 0;
4824887Schin 	if (isalnum(i)&&(np=nv_search(ep->e_macro,sh.alias_tree,HASH_SCOPE))&&(out=nv_getval(np)))
4834887Schin 	{
4844887Schin #if SHOPT_MULTIBYTE
4854887Schin 		/* copy to buff in internal representation */
4864887Schin 		int c = 0;
4874887Schin 		if( strlen(out) > LOOKAHEAD )
4884887Schin 		{
4894887Schin 			c = out[LOOKAHEAD];
4904887Schin 			out[LOOKAHEAD] = 0;
4914887Schin 		}
4924887Schin 		i = ed_internal(out,buff);
4934887Schin 		if(c)
4944887Schin 			out[LOOKAHEAD] = c;
4954887Schin #else
4964887Schin 		strncpy((char*)buff,out,LOOKAHEAD);
4974887Schin 		buff[LOOKAHEAD] = 0;
4984887Schin 		i = strlen((char*)buff);
4994887Schin #endif /* SHOPT_MULTIBYTE */
5004887Schin 		while(i-- > 0)
5014887Schin 			ed_ungetchar(ep,buff[i]);
5024887Schin 		return(1);
5034887Schin 	}
5044887Schin 	return(0);
5054887Schin }
5064887Schin 
5074887Schin /*
5084887Schin  * Enter the fc command on the current history line
5094887Schin  */
5104887Schin int ed_fulledit(Edit_t *ep)
5114887Schin {
5124887Schin 	register char *cp;
5134887Schin 	if(!sh.hist_ptr)
5144887Schin 		return(-1);
5154887Schin 	/* use EDITOR on current command */
5164887Schin 	if(ep->e_hline == ep->e_hismax)
5174887Schin 	{
5184887Schin 		if(ep->e_eol<0)
5194887Schin 			return(-1);
5204887Schin #if SHOPT_MULTIBYTE
5214887Schin 		ep->e_inbuf[ep->e_eol+1] = 0;
5224887Schin 		ed_external(ep->e_inbuf, (char *)ep->e_inbuf);
5234887Schin #endif /* SHOPT_MULTIBYTE */
5244887Schin 		sfwrite(sh.hist_ptr->histfp,(char*)ep->e_inbuf,ep->e_eol+1);
5254887Schin 		sh_onstate(SH_HISTORY);
5264887Schin 		hist_flush(sh.hist_ptr);
5274887Schin 	}
5284887Schin 	cp = strcopy((char*)ep->e_inbuf,e_runvi);
5294887Schin 	cp = strcopy(cp, fmtbase((long)ep->e_hline,10,0));
5304887Schin 	ep->e_eol = ((unsigned char*)cp - (unsigned char*)ep->e_inbuf)-(sh_isoption(SH_VI)!=0);
5314887Schin 	return(0);
5324887Schin }
533