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