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