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