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
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;
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
charcmp(int a,int b,int nocase)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 */
overlaid(register char * str,register const char * newstr,int nocase)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 */
find_begin(char outbuff[],char * last,int endchar,int * type)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
ed_expand(Edit_t * ep,char outbuff[],int * cur,int * eol,int mode,int count)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 */
ed_macro(Edit_t * ep,register int i)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 */
ed_fulledit(Edit_t * ep)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