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