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