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