1 /*
2 *
3 * CLEX File Manager
4 *
5 * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6 *
7 * CLEX is free software without warranty of any kind; see the
8 * GNU General Public License as set out in the "COPYING" document
9 * which accompanies the CLEX File Manager package.
10 *
11 * CLEX can be downloaded from http://www.clex.sk
12 *
13 */
14
15 #include "clexheaders.h"
16
17 #include <sys/stat.h> /* stat() */
18 #include <ctype.h> /* tolower() */
19 #include <dirent.h> /* readdir() */
20 #include <errno.h> /* errno */
21 #include <stdarg.h> /* log.h */
22 #include <stdlib.h> /* qsort() */
23 #include <stdio.h> /* EOF */
24 #include <string.h> /* strerror() */
25 #include <time.h> /* time() */
26 #include <unistd.h> /* stat() */
27 #include <wctype.h> /* iswalnum() */
28
29 #include "completion.h"
30
31 #include "cfg.h" /* cfg_num() */
32 #include "control.h" /* control_loop() */
33 #include "edit.h" /* edit_update() */
34 #include "history.h" /* get_history_entry() */
35 #include "inout.h" /* win_waitmsg() */
36 #include "lex.h" /* usw_dequote() */
37 #include "list.h" /* stat2type() */
38 #include "log.h" /* msgout() */
39 #include "match.h" /* match_substr() */
40 #include "mbwstring.h" /* convert2w() */
41 #include "sort.h" /* num_wcscoll() */
42 #include "userdata.h" /* username_find() */
43 #include "util.h" /* emalloc() */
44
45 /* internal use only */
46 #define COMPL_TYPE_PATHCMD 100 /* search $PATH */
47 #define COMPL_TYPE_USERDIR 101 /* with trailing slash (~name/) */
48 #define COMPL_TYPE_ENV2 102 /* with trailing curly brace ${env} */
49
50 /* commands are stored in several linked lists depending on the first character */
51 #define LIST_NR(ch) ((ch) >= L'a' && (ch) <= L'z' ? (ch) - L'a' : 26)
52 #define LISTS 27
53 typedef struct cmd {
54 SDSTRING cmd; /* command name */
55 SDSTRINGW cmdw; /* command name */
56 struct cmd *next; /* linked list ptr */
57 } CMD;
58 /* PATHDIR holds info about commands in a PATH member directory */
59 typedef struct {
60 const char *dir; /* PATH directory name */
61 const wchar_t *dirw; /* PATH directory name */
62 time_t timestamp; /* time of last successfull directory scan, or 0 */
63 dev_t device; /* device/inode from stat() */
64 ino_t inode;
65 CMD *commands[LISTS]; /* lists of commands in this directory */
66 } PATHDIR;
67
68 static PATHDIR *pd_list; /* PATHDIRs for all members of the PATH search list */
69 static int pd_cnt = 0; /* number od PATHDIRs in pd_list */
70
71 #define QFL_NONE 0
72 #define QFL_INQ 1 /* inside quotes '...' or "..." */
73 #define QFL_MDQ 2 /* missing closing double quote */
74 #define QFL_MSQ 3 /* missing closing single quote */
75 /* input: completion request data */
76 static struct {
77 CODE type; /* type of completion - one of COMPL_TYPE_XXX */
78 const wchar_t *str; /* string to be completed */
79 int strlen; /* length of 'str' */
80 const wchar_t *dirw;/* directory to attempt the completion in */
81 const char *dir; /* multibyte version of 'dirw' if available or 0 */
82 int qlevel; /* quote level for names when inserting (one of QUOT_XXX in edit.h) */
83 CODE qflags; /* other quoting issues - one of QFL_XXX */
84 } rq;
85
86 /* output: completion results */
87 static struct {
88 FLAG filenames; /* names are filenames */
89 int cnt; /* number of completion candidates */
90 int err; /* errno value (if cnt is zero) */
91 size_t clen; /* how many characters following 'str'
92 are the same for all candidates */
93 } compl;
94 /* output: candidates */
95
96 static COMPL_ENTRY *cc_list = 0; /* list of all candidates */
97 static int cc_alloc = 0; /* max number of candidates in CC_LIST */
98
99 static FLAG unfinished; /* completion not finished (partial success) */
100 extern char **environ;
101
102 /* environment in wchar */
103 typedef struct {
104 const wchar_t *var;
105 const wchar_t *val;
106 } ENW;
107 static ENW *enw;
108
109 static void
path_init(void)110 path_init(void)
111 {
112 char *path, *p;
113 int i, list;
114
115 if ( (path = getenv("PATH")) == 0) {
116 msgout(MSG_NOTICE,"There is no PATH environment variable");
117 return;
118 }
119
120 /* split PATH to components */
121 path = estrdup(path);
122 for (pd_cnt = 1, p = path; *p; p++)
123 if (*p == ':') {
124 *p = '\0';
125 pd_cnt++;
126 }
127 pd_list = emalloc(pd_cnt * sizeof(PATHDIR));
128
129 for (i = 0, p = path; i < pd_cnt; i++) {
130 pd_list[i].dir = *p ? p : ".";
131 pd_list[i].dirw = ewcsdup(convert2w(pd_list[i].dir));
132 pd_list[i].timestamp = 0;
133 for (list = 0; list < LISTS; list++)
134 pd_list[i].commands[list] = 0;
135 while (*p++)
136 ;
137 }
138 }
139
140 /* transform the environment into wide char strings */
141 void
environ_init(void)142 environ_init(void)
143 {
144 int i;
145 char *var, *val;
146 static USTRING buff = UNULL;
147
148 for (i = 0; environ[i] != 0; i++)
149 ;
150 enw = emalloc(sizeof(ENW) * (i + 1));
151 for (i = 0; environ[i] != 0; i++) {
152 for (var = val = us_copy(&buff,environ[i]); *val != '\0'; val++)
153 if (*val == '=') {
154 *val++ = '\0';
155 break;
156 }
157 enw[i].var = ewcsdup(convert2w(var));
158 enw[i].val = ewcsdup(convert2w(val));
159 }
160 enw[i].var = enw[i].val = 0;
161 }
162
163 void
compl_initialize(void)164 compl_initialize(void)
165 {
166 path_init();
167 environ_init();
168 compl_reconfig();
169 }
170
171 void
compl_reconfig(void)172 compl_reconfig(void)
173 {
174 int i;
175
176 if (cc_alloc > 0) {
177 for (i = 0; i < cc_alloc; i++)
178 sdw_reset(&cc_list[i].str);
179 free(cc_list);
180 free(panel_compl.cand);
181 }
182
183 cc_alloc = cfg_num(CFG_C_SIZE);
184 cc_list = emalloc(cc_alloc * sizeof(COMPL_ENTRY));
185 panel_compl.cand = emalloc(cc_alloc * sizeof(COMPL_ENTRY *));
186 for (i = 0; i < cc_alloc; i++)
187 SD_INIT(cc_list[i].str);
188 }
189
190 static int
qcmp(const void * e1,const void * e2)191 qcmp(const void *e1, const void *e2)
192 {
193 return (panel_sort.order == SORT_NAME_NUM ? num_wcscoll : wcscoll)(
194 SDSTR(((COMPL_ENTRY *)e1)->str),
195 SDSTR(((COMPL_ENTRY *)e2)->str));
196 }
197
198 /* simplified version of sort_group() found in sort.c */
199 enum FILETYPE_TYPE {
200 FILETYPE_DIR, FILETYPE_BDEV, FILETYPE_CDEV, FILETYPE_OTHER, FILETYPE_PLAIN
201 };
202 static int
sort_group(int type)203 sort_group(int type)
204 {
205 if (IS_FT_PLAIN(type))
206 return FILETYPE_PLAIN;
207 if (IS_FT_DIR(type))
208 return FILETYPE_DIR;
209 if (panel_sort.group == GROUP_DBCOP) {
210 if (type == FT_DEV_CHAR)
211 return FILETYPE_CDEV;
212 if (type == FT_DEV_BLOCK)
213 return FILETYPE_BDEV;
214 }
215 return FILETYPE_OTHER;
216 }
217
218 static int
qcmp_group(const void * e1,const void * e2)219 qcmp_group(const void *e1, const void *e2)
220 {
221 int cmp, group1, group2;
222
223 group1 = sort_group(((COMPL_ENTRY *)e1)->file_type);
224 group2 = sort_group(((COMPL_ENTRY *)e2)->file_type);
225 cmp = group1 - group2;
226 if (cmp)
227 return cmp;
228 return qcmp(e1,e2);
229 }
230
231 void
compl_panel_data(void)232 compl_panel_data(void)
233 {
234 int i, j;
235 COMPL_ENTRY *pcc, *curs;
236
237 curs = VALID_CURSOR(panel_compl.pd) ? panel_compl.cand[panel_compl.pd->curs] : 0;
238 if (panel_compl.pd->filtering)
239 match_substr_set(panel_compl.pd->filter->line);
240
241 for (i = j = 0; i < compl.cnt; i++) {
242 pcc = &cc_list[i];
243 if (pcc == curs)
244 panel_compl.pd->curs = j;
245 if (panel_compl.pd->filtering && !match_substr(SDSTR(pcc->str)))
246 continue;
247 panel_compl.cand[j++] = pcc;
248 }
249 panel_compl.pd->cnt = j;
250 }
251
252 int
compl_prepare(void)253 compl_prepare(void)
254 {
255 const wchar_t *title, *aux;
256 static wchar_t msg[80]; /* win_sethelp() stores this ptr */
257
258 if (compl.cnt > cc_alloc) {
259 swprintf(msg,ARRAY_SIZE(msg),L"%d additional entries not shown (table full)",compl.cnt - cc_alloc);
260 win_sethelp(HELPMSG_TMP,msg);
261 compl.cnt = cc_alloc;
262 }
263
264 if (rq.type != COMPL_TYPE_HIST)
265 qsort(cc_list,compl.cnt,sizeof(COMPL_ENTRY),
266 (compl.filenames && panel_sort.group) ? qcmp_group : qcmp);
267
268 aux = 0;
269 switch (rq.type) {
270 case COMPL_TYPE_FILE:
271 title = L"FILENAME COMPLETION";
272 break;
273 case COMPL_TYPE_DIR:
274 title = L"DIRECTORY NAME COMPLETION";
275 break;
276 case COMPL_TYPE_PATHCMD:
277 aux = L"found in: ";
278 /* no break */
279 case COMPL_TYPE_CMD:
280 title = L"COMMAND NAME COMPLETION";
281 break;
282 case COMPL_TYPE_HIST:
283 title = L"COMMAND COMPLETION FROM HISTORY";
284 win_sethelp(HELPMSG_BASE,L"commands are listed in order of their execution");
285 break;
286 case COMPL_TYPE_GROUP:
287 title = L"GROUP NAME COMPLETION";
288 break;
289 case COMPL_TYPE_USER:
290 case COMPL_TYPE_USERDIR:
291 aux = L"name/comment: ";
292 title = L"USER NAME COMPLETION";
293 break;
294 case COMPL_TYPE_ENV:
295 case COMPL_TYPE_ENV2:
296 title = L"ENVIRONMENT VARIABLE COMPLETION";
297 aux = L"value: ";
298 break;
299 default:
300 title = L"NAME COMPLETION";
301 }
302 panel_compl.title = title;
303 panel_compl.aux = aux;
304 panel_compl.filenames = compl.filenames;
305
306 panel_compl.pd->filtering = 0;
307 panel_compl.pd->curs = -1;
308 compl_panel_data();
309 panel_compl.pd->top = panel_compl.pd->min;
310 panel_compl.pd->curs = rq.type == COMPL_TYPE_HIST ? 0 : panel_compl.pd->min;
311
312 panel = panel_compl.pd;
313 /* textline inherited from previous mode */
314 return 0;
315 }
316
317 static void
register_candidate(const wchar_t * cand,int is_link,int file_type,const wchar_t * aux)318 register_candidate(const wchar_t *cand, int is_link, int file_type, const wchar_t *aux)
319 {
320 int i;
321 static const wchar_t *cand0;
322
323 if (rq.type == COMPL_TYPE_PATHCMD)
324 /* check for duplicates like awk in both /bin and /usr/bin */
325 for (i = 0; i < compl.cnt && i < cc_alloc; i++)
326 if (wcscmp(SDSTR(cc_list[i].str),cand) == 0)
327 return;
328
329 if (compl.cnt < cc_alloc) {
330 sdw_copy(&cc_list[compl.cnt].str,cand);
331 cc_list[compl.cnt].is_link = is_link;
332 cc_list[compl.cnt].file_type = file_type;
333 cc_list[compl.cnt].aux = aux;
334 }
335
336 if (compl.cnt == 0) {
337 cand0 = SDSTR(cc_list[0].str); /* cand0 = cand; would be an error */
338 compl.clen = wcslen(cand0) - rq.strlen;
339 }
340 else
341 for (i = 0; i < compl.clen ; i++)
342 if (cand[rq.strlen + i] != cand0[rq.strlen + i]) {
343 compl.clen = i;
344 break;
345 }
346 compl.cnt++;
347 }
348
349 static void
complete_environ(void)350 complete_environ(void)
351 {
352 int i;
353
354 for (i = 0; enw[i].var != 0 ; i++)
355 if (rq.strlen == 0 || wcsncmp(enw[i].var,rq.str,rq.strlen) == 0)
356 register_candidate(enw[i].var,0,0,enw[i].val);
357 }
358
359 static void
complete_history()360 complete_history()
361 {
362 int i;
363 const HIST_ENTRY *ph;
364
365 for (i = 0; (ph = get_history_entry(i)); i++)
366 if (wcsncmp(USTR(ph->cmd),rq.str,rq.strlen) == 0)
367 register_candidate(USTR(ph->cmd),0,0,
368 ph->failed ? L"this command failed last time" : 0);
369 }
370
371 static void
complete_username(void)372 complete_username(void)
373 {
374 const wchar_t *login, *fullname;
375
376 username_find_init(rq.str,rq.strlen);
377 while ( (login = username_find(&fullname)) )
378 register_candidate(login,0,0,fullname);
379 }
380
381 static void
complete_groupname(void)382 complete_groupname(void)
383 {
384 const wchar_t *group;
385
386 groupname_find_init(rq.str,rq.strlen);
387 while ( (group = groupname_find()) )
388 register_candidate(group,0,0,0);
389 }
390
391 static void
complete_file(void)392 complete_file(void)
393 {
394 FLAG is_link;
395 CODE type;
396 const char *path, *dir, *file;
397 const wchar_t *filew;
398 struct stat st;
399 struct dirent *direntry;
400 DIR *dd;
401 static USTRING mbdir = UNULL;
402
403 if (rq.dirw == 0) {
404 /* special case: bare tilde */
405 if (wcscmp(rq.str,L"~") == 0) {
406 register_candidate(L"~",0,FT_DIRECTORY,0);
407 return;
408 }
409 rq.dir = ".";
410 rq.dirw = L".";
411 }
412 dir = rq.dir ? rq.dir : us_convert2mb(rq.dirw,&mbdir);
413 if ( (dd = opendir(dir)) == 0) {
414 compl.err = errno;
415 return;
416 }
417
418 win_waitmsg();
419 pathname_set_directory(dir);
420 while ( ( direntry = readdir(dd)) ) {
421 filew = convert2w(file = direntry->d_name);
422 if (rq.strlen == 0) {
423 if (file[0] == '.' && (file[1] == '\0' || (file[1] == '.' && file[2] == '\0')))
424 continue;
425 }
426 else if (wcsncmp(filew,rq.str,rq.strlen))
427 continue;
428
429 if (lstat(path = pathname_join(file),&st) < 0)
430 continue; /* file just deleted ? */
431 if ( (is_link = S_ISLNK(st.st_mode)) && stat(path,&st) < 0)
432 type = FT_NA;
433 else
434 type = stat2type(st.st_mode,st.st_uid);
435
436 if (rq.type == COMPL_TYPE_DIR && !IS_FT_DIR(type))
437 continue; /* must be a directory */
438 if (rq.type == COMPL_TYPE_CMD && !IS_FT_DIR(type) && !IS_FT_EXEC(type))
439 continue; /* must be a directory or executable */
440 if (rq.type == COMPL_TYPE_PATHCMD && !IS_FT_EXEC(type))
441 continue; /* must be an executable */
442
443 register_candidate(filew,is_link,type,rq.type == COMPL_TYPE_PATHCMD ? rq.dirw : 0);
444 }
445 closedir(dd);
446 }
447
448 static void
pathcmd_refresh(PATHDIR * ppd)449 pathcmd_refresh(PATHDIR *ppd)
450 {
451 FLAG stat_ok;
452 int list;
453 const wchar_t *filew;
454 struct dirent *direntry;
455 struct stat st;
456 DIR *dd;
457 CMD *pc;
458
459 /*
460 * fstat(dirfd()) instead of stat() followed by opendir() would be
461 * better, but dirfd() is not available on some systems
462 */
463
464 stat_ok = stat(ppd->dir,&st) == 0;
465 if (stat_ok && st.st_mtime < ppd->timestamp
466 && st.st_dev == ppd->device && st.st_ino == ppd->inode)
467 return;
468
469 /* clear all command lists */
470 for (list = 0; list < LISTS; list++) {
471 while ( (pc = ppd->commands[list]) ) {
472 ppd->commands[list] = pc->next;
473 sd_reset(&pc->cmd);
474 sdw_reset(&pc->cmdw);
475 free(pc);
476 }
477 }
478
479 ppd->timestamp = time(0);
480 if (!stat_ok || (dd = opendir(ppd->dir)) == 0) {
481 ppd->timestamp = 0;
482 msgout(MSG_NOTICE,"Command name completion routine cannot list "
483 "directory \"%s\" (member of $PATH): %s",ppd->dir,strerror(errno));
484 return;
485 }
486 ppd->device = st.st_dev;
487 ppd->inode = st.st_ino;
488
489 win_waitmsg();
490 while ( (direntry = readdir(dd)) ) {
491 filew = convert2w(direntry->d_name);
492 list = LIST_NR(*filew);
493 pc = emalloc(sizeof(CMD));
494 SD_INIT(pc->cmd);
495 SD_INIT(pc->cmdw);
496 sd_copy(&pc->cmd,direntry->d_name);
497 sdw_copy(&pc->cmdw,filew);
498 pc->next = ppd->commands[list];
499 ppd->commands[list] = pc;
500 }
501 closedir(dd);
502 }
503
504 static void
complete_pathcmd(void)505 complete_pathcmd(void)
506 {
507 FLAG is_link;
508 CODE file_type;
509 int i, list;
510 const char *path;
511 const wchar_t *filew;
512 CMD *pc;
513 PATHDIR *ppd;
514 struct stat st;
515
516 /* include subdirectories of the current directory */
517 rq.type = COMPL_TYPE_DIR;
518 complete_file();
519 rq.type = COMPL_TYPE_PATHCMD;
520
521 list = LIST_NR(*rq.str);
522 for (i = 0; i < pd_cnt; i++) {
523 ppd = &pd_list[i];
524 if (*ppd->dir == '/') {
525 /* absolute PATH directories are cached */
526 pathcmd_refresh(ppd);
527 pathname_set_directory(ppd->dir);
528 for (pc = ppd->commands[list]; pc; pc = pc->next) {
529 filew = SDSTR(pc->cmdw);
530 if (wcsncmp(filew,rq.str,rq.strlen) != 0)
531 continue;
532 if (lstat(path = pathname_join(SDSTR(pc->cmd)),&st) < 0)
533 continue;
534 if ( (is_link = S_ISLNK(st.st_mode))
535 && stat(path,&st) < 0)
536 continue;
537 file_type = stat2type(st.st_mode,st.st_uid);
538 if (!IS_FT_EXEC(file_type))
539 continue;
540 register_candidate(filew,is_link,file_type,ppd->dirw);
541 }
542 }
543 else {
544 /* relative PATH directories are impossible to cache */
545 rq.dir = ppd->dir;
546 rq.dirw = ppd->dirw;
547 complete_file();
548 }
549 }
550 }
551
552 static void
reset_results(void)553 reset_results(void)
554 {
555 compl.cnt = 0;
556 compl.err = 0;
557 compl.filenames = 0;
558 }
559
560 static void
complete_it(void)561 complete_it(void)
562 {
563 if (rq.type == COMPL_TYPE_ENV || rq.type == COMPL_TYPE_ENV2)
564 complete_environ();
565 else if (rq.type == COMPL_TYPE_USER || rq.type == COMPL_TYPE_USERDIR)
566 complete_username();
567 else if (rq.type == COMPL_TYPE_GROUP)
568 complete_groupname();
569 else if (rq.type == COMPL_TYPE_HIST)
570 complete_history();
571 else {
572 compl.filenames = 1;
573 if (rq.type == COMPL_TYPE_PATHCMD)
574 complete_pathcmd();
575 else
576 /* FILE, DIR, CMD completion */
577 complete_file();
578 }
579 }
580
581 /* insert char 'ch' if it is not already there */
582 static void
condinsert(wchar_t ch)583 condinsert(wchar_t ch)
584 {
585 if (USTR(textline->line)[textline->curs] == ch)
586 textline->curs++;
587 else
588 edit_nu_insertchar(ch);
589 }
590
591
592 static void
insert_candidate(COMPL_ENTRY * pcc)593 insert_candidate(COMPL_ENTRY *pcc)
594 {
595 edit_nu_insertstr(SDSTR(pcc->str) + rq.strlen,rq.qlevel);
596
597 if ((compl.filenames && IS_FT_DIR(pcc->file_type))
598 || rq.type == COMPL_TYPE_USERDIR /* ~user is a directory */ ) {
599 unfinished = 1; /* a directory may have subdirectories */
600 condinsert(L'/');
601 }
602 else {
603 if (rq.type == COMPL_TYPE_ENV2)
604 condinsert(L'}');
605
606 if (rq.qflags == QFL_INQ)
607 /* move over the closing quote */
608 textline->curs++;
609 else if (rq.qflags == QFL_MSQ)
610 edit_nu_insertchar(L'\'');
611 else if (rq.qflags == QFL_MDQ)
612 edit_nu_insertchar(L'\"');
613 else if (compl.filenames)
614 condinsert(L' ');
615 }
616
617 edit_update();
618 }
619
620 static const char *
code2string(int type)621 code2string(int type)
622 {
623 switch (type) {
624 case COMPL_TYPE_FILE:
625 return "filename";
626 case COMPL_TYPE_DIR:
627 return "directory name";
628 case COMPL_TYPE_PATHCMD:
629 case COMPL_TYPE_CMD:
630 return "command name";
631 case COMPL_TYPE_HIST:
632 return "command";
633 case COMPL_TYPE_GROUP:
634 return "group name";
635 case COMPL_TYPE_USER:
636 case COMPL_TYPE_USERDIR:
637 return "user name";
638 case COMPL_TYPE_ENV:
639 case COMPL_TYPE_ENV2:
640 return "environment variable";
641 default:
642 return "string";
643 }
644 }
645
646 static void
show_results(void)647 show_results(void)
648 {
649 static SDSTRINGW common = SDNULL(L"");
650
651 if (compl.cnt == 0) {
652 msgout(MSG_i,"cannot complete this %s (%s)",code2string(rq.type),
653 compl.err == 0 ? "no match" : strerror(compl.err));
654 return;
655 }
656
657 if (compl.cnt == 1) {
658 insert_candidate(&cc_list[0]);
659 return;
660 }
661
662 if (compl.clen) {
663 /* insert the common part of all candidates */
664 sdw_copyn(&common,SDSTR(cc_list[0].str) + rq.strlen,compl.clen);
665 edit_insertstr(SDSTR(common),rq.qlevel);
666 /*
667 * pretend that the string to be completed already contains
668 * the chars just inserted
669 */
670 rq.strlen += compl.clen;
671 }
672
673 control_loop(MODE_COMPL);
674 }
675
676 #define ISAZ09(CH) ((CH) == L'_' || iswalnum(CH))
677
678 /*
679 * compl_name(type) is a completion routine for alphanumerical strings
680 * type is one of COMPL_TYPE_AUTO, ENV, GROUP, USER
681 *
682 * return value:
683 * 0 = completion process completed (successfully or not)
684 * -1 = nothing to complete
685 */
686 #define ISUGCHAR(CH) ((CH) == L'.' || (CH) == L',' || (CH) == L'-')
687 #define ISUGTYPE(T) ((T) == COMPL_TYPE_USER || (T) == COMPL_TYPE_GROUP)
688 #define TESTAZ09(POS) (lex[POS] == LEX_PLAINTEXT \
689 && (ISAZ09(pline[POS]) || (ISUGTYPE(type) && ISUGCHAR(pline[POS])) ) )
690 static int
compl_name(int type)691 compl_name(int type)
692 {
693 static USTRINGW str_buff = UNULL;
694 const char *lex;
695 const wchar_t *pline;
696 int start, end;
697
698 /* find start and end */
699 pline = USTR(textline->line);
700 lex = cmd2lex(pline);
701 start = end = textline->curs;
702
703 if (TESTAZ09(start))
704 /* complete the name at the cursor */
705 while (end++, TESTAZ09(end))
706 ;
707 else if (panel_paste.wordstart || !TESTAZ09(start - 1))
708 return -1; /* nothing to complete */
709 /* else complete the name immediately before the cursor */
710
711 if (!panel_paste.wordstart)
712 while (TESTAZ09(start - 1))
713 start--;
714
715 /* set the proper COMPL_TYPE */
716 if (type == COMPL_TYPE_AUTO) {
717 if (lex[start - 1] == LEX_PLAINTEXT && pline[start - 1] == L'~' && !IS_LEX_WORD(lex[start - 2]))
718 type = COMPL_TYPE_USERDIR;
719 else if (lex[start - 1] == LEX_VAR)
720 type = pline[start - 1] == L'{' ? COMPL_TYPE_ENV2 : COMPL_TYPE_ENV;
721 else
722 return -1; /* try compl_file() */
723 }
724 else if (type == COMPL_TYPE_ENV && lex[start - 1] == LEX_VAR && pline[start - 1] == L'{')
725 type = COMPL_TYPE_ENV2;
726
727 /* fill in the 'rq' struct */
728 rq.qlevel = QUOT_NONE;
729 rq.qflags = QFL_NONE;
730 rq.type = type;
731 rq.dir = 0;
732 rq.dirw = 0;
733 rq.strlen = end - start;
734 rq.str = usw_copyn(&str_buff,pline + start,rq.strlen);
735
736 /* move cursor to the end of the current word */
737 textline->curs = end;
738 edit_update_cursor();
739
740 reset_results();
741 complete_it();
742 show_results();
743 return 0;
744 }
745
746 /*
747 * compl_file() attempts to complete the partial text in the command line
748 * type is on of the COMPL_TYPE_AUTO, CMD, DIR, DIRPANEL, HISTORY, FILE, or DRYRUN
749 *
750 * return value:
751 * 0 = completion process completed (successfully or not)
752 * -1, -2 = nothing to complete (current word is an empty string):
753 * -1 = first word (usually the command)
754 * -2 = not the first word (usually one of the arguments)
755 * -3 = could complete, but not allowed to (COMPL_TYPE_DRYRUN)
756 */
757 static int
compl_file(int type)758 compl_file(int type)
759 {
760 static USTRINGW dequote_str = UNULL, dequote_dir = UNULL;
761 const char *lex;
762 const wchar_t *p, *pslash, *pstart, *pend, *pline;
763 wchar_t ch;
764 int i, start, end, dirlen;
765 FLAG tilde, wholeline, userdir;
766
767 /*
768 * pline -> the input line
769 * pstart -> start of the string to be completed
770 * pend -> position immediately after the last character of that string
771 * pslash -> last slash '/' in the string (if any)
772 */
773 pline = USTR(textline->line);
774
775 wholeline = type == COMPL_TYPE_DIRPANEL || type == COMPL_TYPE_HIST;
776 if (wholeline) {
777 /* complete the whole line */
778 rq.qlevel = QUOT_NONE;
779 rq.qflags = QFL_NONE;
780 start = 0;
781 end = textline->size;
782 }
783 else {
784 /* find the start and end */
785 rq.qlevel = QUOT_NORMAL;
786 rq.qflags = QFL_NONE;
787 lex = cmd2lex(pline);
788 start = end = textline->curs;
789 if (IS_LEX_WORD(lex[start])) {
790 /* complete the name at the cursor */
791 while (end++, IS_LEX_WORD(lex[end]))
792 ;
793 }
794 else if (IS_LEX_WORD(lex[start - 1]) && !panel_paste.wordstart)
795 ; /* complete the name immediately before the cursor */
796 else if ((IS_LEX_CMDSEP(lex[start - 1]) || IS_LEX_SPACE(lex[start - 1])
797 || panel_paste.wordstart) && IS_LEX_EMPTY(lex[start])) {
798 /* there is no text to complete */
799 for (i = start - 1; IS_LEX_SPACE(lex[i]); i--)
800 ;
801 return IS_LEX_CMDSEP(lex[i]) ? -1 : -2;
802 } else {
803 /* the text at the cursor is not a name */
804 msgout(MSG_i,"cannot complete a special symbol");
805 return 0;
806 }
807
808 if (type == COMPL_TYPE_DRYRUN)
809 return -3;
810
811 if (!panel_paste.wordstart)
812 while (IS_LEX_WORD(lex[start - 1]))
813 start--;
814
815 for (i = start; i < end; i++)
816 if (lex[i] == LEX_VAR) {
817 msgout(MSG_i,"cannot complete a name containing a $variable");
818 return 0;
819 }
820
821 if (type == COMPL_TYPE_AUTO) {
822 if (lex[start - 1] == LEX_OTHER) {
823 msgout(MSG_i,"cannot complete after a special symbol");
824 return 0;
825 }
826
827 /* find out what precedes the name to be completed */
828 for (i = start - 1; IS_LEX_SPACE(lex[i]); i--)
829 ;
830 type = IS_LEX_CMDSEP(lex[i]) ? COMPL_TYPE_CMD : COMPL_TYPE_FILE;
831
832 /* special case - complete file in expressions like
833 name:file, --opt=file or user@some.host:file */
834 if (!panel_paste.wordstart && type == COMPL_TYPE_FILE && lex[i] != LEX_IO) {
835 for (i = start; i < end; i++) {
836 if (lex[i] != LEX_PLAINTEXT)
837 break;
838 ch = pline[i];
839 if (i > start && i < textline->curs && (ch == L':' || ch == L'=')) {
840 start = i + 1;
841 if (start == end)
842 return -2;
843 break;
844 }
845 if (ch != L'.' && ch != L'-' && ch != L'@' && !ISAZ09(ch))
846 break;
847 }
848 }
849 }
850
851 if (lex[end] == LEX_END_ERR_SQ) {
852 rq.qlevel = QUOT_NONE;
853 rq.qflags = QFL_MSQ;
854 }
855 else if (lex[end] == LEX_END_ERR_DQ) {
856 rq.qlevel = QUOT_IN_QUOTES;
857 rq.qflags = QFL_MDQ;
858 }
859 else if (lex[end - 1] == LEX_QMARK) {
860 if ((ch = pline[end - 1]) == L'\'')
861 rq.qlevel = QUOT_NONE;
862 else if (ch == L'\"')
863 rq.qlevel = QUOT_IN_QUOTES;
864 rq.qflags = QFL_INQ;
865 }
866 }
867 pstart = pline + start;
868 pend = pline + end;
869
870 pslash = 0;
871 if (type != COMPL_TYPE_HIST)
872 /* separate the name into the directory part and the file part */
873 for (p = pend; p > pstart;)
874 if (*--p == L'/') {
875 pslash = p;
876 break;
877 }
878 /* set rq.dirw */
879 if (pslash == 0) {
880 rq.str = pstart;
881 rq.dirw = 0;
882 }
883 else {
884 rq.str = pslash + 1;
885 rq.dirw = pstart;
886
887 dirlen = pslash - pstart;
888 /* dequote 'dir' (if appropriate) + add terminating null byte */
889 if (type != COMPL_TYPE_DIRPANEL && isquoted(rq.dirw)) {
890 tilde = is_dir_tilde(rq.dirw);
891 dirlen = usw_dequote(&dequote_dir,rq.dirw,dirlen);
892 }
893 else {
894 tilde = *rq.dirw == L'~';
895 usw_copyn(&dequote_dir,rq.dirw,dirlen);
896 }
897 rq.dirw = USTR(dequote_dir);
898 if (dirlen == 0)
899 /* first slash == last slash */
900 rq.dirw = L"/";
901 else if (tilde)
902 rq.dirw = dir_tilde(rq.dirw);
903 }
904 rq.dir = 0; /* will be converted from rq.dirw on demand */
905
906 /* set the proper completion type */
907 if (type == COMPL_TYPE_DIRPANEL) {
908 if ( (userdir = *pstart == L'~') ) {
909 for (i = 1; (ch = pstart[i]) != L'\0' && ch != L'/'; i++)
910 if (!ISAZ09(ch)) {
911 userdir = 0;
912 break;
913 }
914 if (textline->curs > i)
915 userdir = 0;
916 }
917 if (userdir) {
918 rq.str = pstart + 1;
919 pend = pstart + i;
920 type = COMPL_TYPE_USERDIR;
921 }
922 else
923 type = COMPL_TYPE_DIR;
924 }
925 else if (type == COMPL_TYPE_CMD && pslash == 0)
926 type = COMPL_TYPE_PATHCMD;
927
928 rq.strlen = pend - rq.str;
929 /* dequote 'str' (if appropriate) + add terminating null byte */
930 if (!wholeline && isquoted(rq.str)) {
931 rq.strlen = usw_dequote(&dequote_str,rq.str,rq.strlen);
932 rq.str = USTR(dequote_str);
933 }
934 else if (*pend != L'\0')
935 rq.str = usw_copyn(&dequote_str,rq.str,rq.strlen);
936
937 /* move cursor to the end of the current word */
938 textline->curs = (pend - pline) - (rq.qflags == QFL_INQ /* 1 or 0 */);
939 edit_update_cursor();
940
941 rq.type = type;
942 reset_results();
943 complete_it();
944 show_results();
945 return 0;
946 }
947
948 int
compl_text(int type)949 compl_text(int type)
950 {
951 if (textline->size == 0)
952 return -1;
953
954 if (get_current_mode() != MODE_PASTE)
955 panel_paste.wordstart = 0; /* valid in the completion/insertion panel only */
956
957 if (type == COMPL_TYPE_AUTO)
958 return compl_name(COMPL_TYPE_AUTO) == 0 ? 0 : compl_file(COMPL_TYPE_AUTO);
959
960 if (type == COMPL_TYPE_ENV || type == COMPL_TYPE_GROUP || type == COMPL_TYPE_USER)
961 return compl_name(type);
962
963 return compl_file(type);
964 }
965
966 static void
complete_type(int type)967 complete_type(int type)
968 {
969 int mode, curs, offset;
970
971 curs = textline->curs;
972 offset = textline->offset;
973 mode = get_current_mode();
974
975 unfinished = 0;
976 if (compl_text(type) != 0)
977 msgout(MSG_i,"there is nothing to complete");
978 if (unfinished) {
979 if (mode == MODE_PASTE && panel_paste.wordstart) {
980 textline->curs = curs;
981 if (textline->offset != offset)
982 edit_update_cursor();
983 }
984 }
985 else if (mode != MODE_FILE)
986 next_mode = MODE_SPECIAL_RETURN;
987 }
988
cx_complete_auto(void)989 void cx_complete_auto(void) { complete_type(COMPL_TYPE_AUTO); }
cx_complete_file(void)990 void cx_complete_file(void) { complete_type(COMPL_TYPE_FILE); }
cx_complete_dir(void)991 void cx_complete_dir(void) { complete_type(COMPL_TYPE_DIR); }
cx_complete_cmd(void)992 void cx_complete_cmd(void) { complete_type(COMPL_TYPE_CMD); }
cx_complete_user(void)993 void cx_complete_user(void) { complete_type(COMPL_TYPE_USER); }
cx_complete_group(void)994 void cx_complete_group(void){ complete_type(COMPL_TYPE_GROUP); }
cx_complete_env(void)995 void cx_complete_env(void) { complete_type(COMPL_TYPE_ENV); }
cx_complete_hist(void)996 void cx_complete_hist(void) { complete_type(COMPL_TYPE_HIST); }
997
998 void
cx_compl_wordstart(void)999 cx_compl_wordstart(void)
1000 {
1001 TOGGLE(panel_paste.wordstart);
1002 win_panel_opt();
1003 }
1004
1005 void
cx_compl_enter(void)1006 cx_compl_enter(void)
1007 {
1008 insert_candidate(panel_compl.cand[panel_compl.pd->curs]);
1009 next_mode = MODE_SPECIAL_RETURN;
1010 }
1011