1 /* readline.c -- functions to control the behaviour of readline,
2  * specially completions. It also introduces the suggestions system
3  * via my_rl_getc function */
4 
5 /*
6  * This file is part of CliFM
7  *
8  * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
9  * All rights reserved.
10 
11  * CliFM is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * CliFM is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24  * MA 02110-1301, USA.
25 */
26 
27 #include "helpers.h"
28 
29 //#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
30 #include <sys/stat.h>
31 //#endif
32 #include <dirent.h>
33 #include <fcntl.h>
34 #include <stdio.h>
35 #include <string.h>
36 #ifdef __OpenBSD__
37 #include <strings.h>
38 #endif
39 #include <unistd.h>
40 #include <errno.h>
41 
42 #ifdef __OpenBSD__
43 typedef char *rl_cpvfunc_t;
44 #include <ereadline/readline/readline.h>
45 #include <ereadline/readline/history.h>
46 #else
47 #include <readline/readline.h>
48 #include <readline/history.h>
49 #endif
50 
51 #include "misc.h"
52 #include "aux.h"
53 #include "checks.h"
54 #include "keybinds.h"
55 #include "navigation.h"
56 #include "readline.h"
57 #include "tabcomp.h"
58 
59 #include "mime.h"
60 
61 #ifndef _NO_SUGGESTIONS
62 #include "suggestions.h"
63 #endif
64 
65 #ifndef _NO_HIGHLIGHT
66 #include "highlight.h"
67 #endif
68 
69 #if !defined(S_IFREG) || !defined(S_IFDIR)
70 #include <sys/stat.h>
71 #endif
72 
73 #if !defined(_NO_SUGGESTIONS) && defined(__FreeBSD__)
74 int freebsd_sc_console = 0;
75 #endif /* __FreeBSD__ */
76 
77 /* Wide chars */
78 /*char wc[12];
79 size_t wcn = 0; */
80 
81 /* Delete key implementation */
82 static void
xdelete()83 xdelete()
84 {
85 #ifndef _NO_SUGGESTIONS
86 	if (suggestion.printed && suggestion_buf)
87 		remove_suggestion_not_end();
88 #endif // !_NO_SUGGESTIONS
89 
90 	if (rl_point == rl_end)
91 		return;
92 
93 	int bk = rl_point;
94 	int mlen = mblen(rl_line_buffer + rl_point, MB_LEN_MAX);
95 
96 	rl_point = bk;
97 	char *s = rl_copy_text(rl_point + mlen, rl_end);
98 	rl_end = rl_point;
99 	rl_insert_text(s);
100 	free(s);
101 	rl_point = bk;
102 }
103 
104 /* Backspace implementation */
105 static void
xbackspace()106 xbackspace()
107 {
108 	if (rl_point != rl_end) {
109 		if (rl_point) {
110 			int bk = rl_point, cc = 0;
111 			char *s = rl_copy_text(rl_point, rl_end);
112 			while ((rl_line_buffer[rl_point - 1] & 0xc0) == 0x80) {
113 				rl_point--;
114 				cc++;
115 			}
116 			rl_point--;
117 			rl_end = rl_point;
118 			rl_insert_text(s);
119 			free(s);
120 			rl_point = bk - 1 - cc;
121 		}
122 #ifndef _NO_SUGGESTIONS
123 		if (suggestion.printed && suggestion_buf)
124 			remove_suggestion_not_end();
125 #endif /* !_NO_SUGGESTIONS */
126 	} else {
127 #ifndef _NO_SUGGESTIONS
128 		if (suggestion_buf)
129 			clear_suggestion(CS_FREEBUF);
130 #endif /* !_NO_SUGGESTIONS */
131 		if (rl_end) {
132 			while ((rl_line_buffer[rl_end - 1] & 0xc0) == 0x80) {
133 				rl_line_buffer[rl_end - 1] = '\0';
134 				rl_point--;
135 				rl_end--;
136 			}
137 			rl_line_buffer[rl_end - 1] = '\0';
138 			rl_point--;
139 			rl_end--;
140 		}
141 	}
142 }
143 
144 
145 static void
leftmost_bell(void)146 leftmost_bell(void)
147 {
148 	if (bell == BELL_VISIBLE) {
149 		rl_extend_line_buffer(2);
150 		*rl_line_buffer = ' ';
151 		*(rl_line_buffer + 1) = '\0';
152 		rl_end = rl_point = 1;
153 	}
154 
155 	rl_ring_bell();
156 
157 	if (bell == BELL_VISIBLE) {
158 		rl_end = rl_point = 0;
159 	}
160 }
161 
162 static int
rl_exclude_input(unsigned char c)163 rl_exclude_input(unsigned char c)
164 {
165 	/* If del or backspace, highlight, but do not suggest */
166 	int _del = 0;
167 
168 	/* Disable suggestions while in vi command mode and reenable them
169 	 * when changing back to insert mode */
170 	if (rl_editing_mode == 0) {
171 		if (rl_readline_state & RL_STATE_VICMDONCE) {
172 			if (c == 'i') {
173 				rl_readline_state &= (unsigned long)~RL_STATE_VICMDONCE;
174 #ifndef _NO_SUGGESTIONS
175 			} else if (suggestion.printed) {
176 				clear_suggestion(CS_FREEBUF);
177 				return 1;
178 #endif /* !_NO_SUGGESTIONS */
179 			} else {
180 				return 1;
181 			}
182 		}
183 	}
184 
185 	/* Skip escape sequences, mostly arrow keys */
186 	if (rl_readline_state & RL_STATE_MOREINPUT) {
187 		if (c == '~') {
188 #ifndef _NO_SUGGESTIONS
189 			if (rl_point != rl_end && suggestion.printed)
190 				/* This should be the delete key */
191 				remove_suggestion_not_end();
192 			else if (suggestion.printed)
193 				clear_suggestion(CS_FREEBUF);
194 			return 1;
195 #endif /* !_NO_SUGGESTIONS */
196 		}
197 
198 		else if (c == '3' && rl_point != rl_end) {
199 			xdelete();
200 			_del = 1;
201 			goto END;
202 		}
203 
204 		/* Handle history events. If a suggestion has been printed and
205 		 * a history event is triggered (usually via the Up and Down arrow
206 		 * keys), the suggestion buffer won't be freed. Let's do it
207 		 * here */
208 #ifndef _NO_SUGGESTIONS
209 		else if ((c == 'A' || c == 'B') && suggestion_buf)
210 			clear_suggestion(CS_FREEBUF);
211 #endif /* !_NO_SUGGESTIONS */
212 
213 		else if (c == 'C' || c == 'D')
214 			cmdhist_flag = 0;
215 
216 		return 1;
217 	}
218 
219 	/* Skip control characters (0 - 31) except backspace (8), tab(9),
220 	 * enter (13), and escape (27) */
221 	if (c < 32 && c != BS && c != _TAB && c != ENTER && c != _ESC)
222 		return 1;
223 
224 	/* Multi-byte char. Send it directly to the input buffer. We can't
225 	 * process it here, since we process only single bytes */
226 	if (c > 127 || (c & 0xc0) == 0x80)
227 		return 1;
228 
229 	if (c != _ESC)
230 		cmdhist_flag = 0;
231 
232 	/* Skip backspace, Enter, and TAB keys */
233 	switch(c) {
234 		case DELETE: /* fallthrough */
235 /*			if (rl_point != rl_end && suggestion.printed)
236 				clear_suggestion(CS_FREEBUF);
237 			goto FAIL; */
238 
239 		case BS:
240 			xbackspace();
241 			if (rl_end == 0 && cur_color != tx_c) {
242 				cur_color = tx_c;
243 				fputs(tx_c, stdout);
244 			}
245 			_del = 1;
246 			goto END;
247 
248 		case ENTER:
249 #ifndef _NO_SUGGESTIONS
250 			if (suggestion.printed && suggestion_buf)
251 				clear_suggestion(CS_FREEBUF);
252 #endif /* !_NO_SUGGESTIONS */
253 			cur_color = tx_c;
254 			fputs(tx_c, stdout);
255 			return 1;
256 
257 		case _ESC:
258 			return 1;
259 
260 		case _TAB:
261 #ifndef _NO_SUGGESTIONS
262 			if (suggestion.printed) {
263 				if (suggestion.nlines >= 2 || suggestion.type == ELN_SUG
264 				|| suggestion.type == BOOKMARK_SUG
265 				|| suggestion.type == ALIAS_SUG
266 				|| suggestion.type == JCMD_SUG) {
267 					clear_suggestion(CS_FREEBUF);
268 					return 1;
269 				}
270 			}
271 #endif /* !_NO_SUGGESTIONS */
272 			return 1;
273 
274 		default: break;
275 	}
276 
277 /*	if (c <= 127) {
278 		char t[2];
279 		t[0] = (char)c;
280 		t[1] = '\0';
281 		rl_insert_text(t);
282 	} else if (wcn >= sizeof(wc) || (c & 0xc0) != 0x80) {
283 		wc[wcn] = '\0';
284 		rl_insert_text(wc);
285 		memset(wc, '\0', sizeof(wc));
286 		wcn = 0;
287 		wc[wcn++] = (char)c;
288 	} else {
289 		wc[wcn++] = (char)c;
290 		return -2;
291 	} */
292 
293 	char t[2];
294 	t[0] = (char)c;
295 	t[1] = '\0';
296 	rl_insert_text(t);
297 
298 	int s = 0;
299 
300 END:
301 #ifndef _NO_SUGGESTIONS
302 	s = strcntchrlst(rl_line_buffer, ' ');
303 	/* Do not take into account final spaces */
304 	if (s >= 0 && !rl_line_buffer[s + 1])
305 		s = -1;
306 	if (rl_point != rl_end && c != _ESC) {
307 		if (rl_point < s) {
308 			if (suggestion.printed)
309 				remove_suggestion_not_end();
310 		}
311 	}
312 #else
313 	UNUSED(s);
314 #endif /* !_NO_SUGGESTIONS */
315 
316 #ifndef _NO_HIGHLIGHT
317 	if (!highlight) {
318 		if (_del) {
319 #ifndef _NO_SUGGESTIONS
320 			/* Since we have removed a char, let's check if there is
321 			 * a suggestion available using the modified input line */
322 			if (wrong_cmd && s == -1 && rl_end) {
323 				/* If a suggestion is found, the normal prompt will be
324 				 * restored and wrong_cmd will be set to zero */
325 				rl_suggestions((unsigned char)rl_line_buffer[rl_end - 1]);
326 				return 2;
327 			}
328 			if (rl_point == 0 && rl_end == 0) {
329 				if (wrong_cmd)
330 					recover_from_wrong_cmd();
331 				leftmost_bell();
332 			}
333 #endif /* !_NO_SUGGESTIONS */
334 			return 2;
335 		}
336 		return 0;
337 	}
338 
339 	if (!wrong_cmd)
340 		recolorize_line();
341 #endif /* !_NO_HIGHLIGHT */
342 
343 	if (_del) {
344 #ifndef _NO_SUGGESTIONS
345 		if (wrong_cmd && s == -1 && rl_end) {
346 			rl_suggestions((unsigned char)rl_line_buffer[rl_end - 1]);
347 			return 2;
348 		}
349 		if (rl_point == 0 && rl_end == 0) {
350 			if (wrong_cmd)
351 				recover_from_wrong_cmd();
352 			leftmost_bell();
353 		}
354 #endif /* !_NO_SUGGESTIONS */
355 		return 2;
356 	}
357 
358 	return 0;
359 }
360 
361 /* Print the corresponding file name in the xrename prompt */
362 static int
prompt_xrename(void)363 prompt_xrename(void)
364 {
365 	char *p = (char *)NULL;
366 	if (*(rl_line_buffer + 1) == ' ')
367 		p = rl_line_buffer + 2;
368 	else /* We have a fused parameter */
369 		p = rl_line_buffer + 1;
370 
371 	size_t plen = strlen(p);
372 	char pp[NAME_MAX];
373 	strcpy(pp, p);
374 
375 	if (plen) {
376 		while (pp[--plen] == ' ')
377 			pp[plen] = '\0';
378 	}
379 
380 	if (is_number(pp)) {
381 		int ipp = atoi(pp);
382 		if (ipp > 0 && ipp <= (int)files) {
383 			rl_replace_line(file_info[ipp - 1].name, 1);
384 			rl_point = rl_end = (int)strlen(file_info[ipp - 1].name);
385 		} else {
386 			xrename = 0;
387 			return EXIT_FAILURE;
388 		}
389 	} else {
390 		char *dstr = dequote_str(pp, 0);
391 		if (!dstr) {
392 			fprintf(stderr, _("%s: %s: Error dequoting file name\n"),
393 					PROGRAM_NAME, pp);
394 			xrename = 0;
395 			return EXIT_FAILURE;
396 		}
397 		rl_replace_line(dstr, 1);
398 		rl_point = rl_end = (int)strlen(dstr);
399 		free(dstr);
400 	}
401 
402 	rl_redisplay();
403 	xrename = 0;
404 
405 	return EXIT_SUCCESS;
406 }
407 
408 /* This function is automatically called by readline() to handle input.
409  * Taken from Bash 1.14.7 and modified to fit our needs. Used
410  * to introduce the suggestions system */
411 static int
my_rl_getc(FILE * stream)412 my_rl_getc(FILE *stream)
413 {
414 	int result;
415 	unsigned char c;
416 
417 #if defined(__GO32__)
418 	if (isatty(0))
419 		return (getkey() & 0x7F);
420 #endif /* __GO32__ */
421 
422 #ifndef _NO_FZF
423 	if (xargs.fzftab == 1 || warning_prompt == 1) {
424 #else
425 	if (warning_prompt == 1) {
426 #endif /* !_NO_FZF */
427 		if (prompt_offset == UNSET) {
428 			get_cursor_position(STDIN_FILENO, STDOUT_FILENO);
429 			prompt_offset = curcol;
430 		}
431 	}
432 
433 	if (xrename) {
434 		/* We are using a secondary prompt for the xrename function */
435 		if (prompt_xrename() == EXIT_FAILURE)
436 			return (EOF);
437 	}
438 
439 	while(1) {
440 		result = (int)read(fileno(stream), &c, sizeof(unsigned char));
441 		if (result == sizeof(unsigned char)) {
442 			if (control_d_exits && c == 4) /* Ctrl-d */
443 				rl_quit(0, 0);
444 
445 			/* Syntax highlighting is made from here */
446 			int ret = rl_exclude_input(c);
447 			if (ret == 1)
448 				return c;
449 
450 #ifndef _NO_SUGGESTIONS
451 			if (ret != 2 && ret != -2 && !_xrename && suggestions) {
452 				/* rl_suggestions returns -1 is C was inserted before
453 				 * the end of the current line, in which case we don't
454 				 * want to return it here (otherwise, it would be added
455 				 * to rl_line_buffer) */
456 #ifdef __FreeBSD__
457 			/* For the time being, suggestions do not work on the FreeBSD
458 			 * console (vt). The escape code to retrieve the current cursor
459 			 * position doesn't seem to work. Switching the console to 'sc'
460 			 * solves the issue */
461 				if (flags & GUI)
462 					rl_suggestions(c);
463 				else if (freebsd_sc_console)
464 					rl_suggestions(c);
465 #else
466 				rl_suggestions(c);
467 #endif /* __FreeBSD__ */
468 			}
469 #endif /* !_NO_SUGGESTIONS */
470 			if (ret != -2)
471 				rl_redisplay();
472 			continue;
473 		}
474 		/* If zero characters are returned, then the file that we are
475 		reading from is empty!  Return EOF in that case. */
476 		if (result == 0)
477 			return (EOF);
478 
479 #if defined(EWOULDBLOCK)
480 		if (errno == EWOULDBLOCK) {
481 			int xflags;
482 
483 			if ((xflags = fcntl(fileno(stream), F_GETFL, 0)) < 0)
484 				return (EOF);
485 			if (xflags & O_NDELAY) {
486 /*				xflags &= ~O_NDELAY; */
487 				fcntl(fileno(stream), F_SETFL, flags);
488 				continue;
489 			}
490 			continue;
491 		}
492 #endif /* EWOULDBLOCK */
493 
494 #if defined(_POSIX_VERSION) && defined(EAGAIN) && defined(O_NONBLOCK)
495 		if (errno == EAGAIN) {
496 			int xflags;
497 
498 			if ((xflags = fcntl(fileno(stream), F_GETFL, 0)) < 0)
499 				return (EOF);
500 			if (xflags & O_NONBLOCK) {
501 /*				xflags &= ~O_NONBLOCK; */
502 				fcntl(fileno(stream), F_SETFL, flags);
503 				continue;
504 			}
505 		}
506 #endif /* _POSIX_VERSION && EAGAIN && O_NONBLOCK */
507 
508 #if !defined(__GO32__)
509       /* If the error that we received was SIGINT, then try again,
510 	 this is simply an interrupted system call to read ().
511 	 Otherwise, some error ocurred, also signifying EOF. */
512 		if (errno != EINTR)
513 			return (EOF);
514 #endif /* !__GO32__ */
515 	}
516 }
517 
518 /* Simply check a single chartacter (c) against the quoting characters
519  * list defined in the qc global array (which takes its values from
520  * rl_filename_quote_characters */
521 int
522 is_quote_char(const char c)
523 {
524 	if (c == '\0' || !qc)
525 		return -1;
526 
527 	char *p = qc;
528 
529 	while (*p) {
530 		if (c == *(p++))
531 			return 1;
532 	}
533 
534 	return 0;
535 }
536 
537 char *
538 rl_no_hist(const char *prompt)
539 {
540 	int bk = suggestions;
541 	suggestions = 0;
542 	rl_nohist = rl_notab = 1;
543 //	stifle_history(0); /* Prevent readline from using the history
544 //	setting */
545 	char *input = readline(prompt);
546 	rl_notab = rl_nohist = 0;
547 //	unstifle_history();	 /* Reenable history */
548 //	read_history(hist_file); /* Reload history lines from file */
549 	suggestions = bk;
550 
551 	if (input) {
552 		/* Make sure input isn't empty string */
553 		if (!*input) {
554 			free(input);
555 			return (char *)NULL;
556 		}
557 
558 		/* Check we have some non-blank char */
559 		int no_blank = 0;
560 		char *p = input;
561 
562 		while (*p) {
563 			if (*p != ' ' && *p != '\n' && *p != '\t') {
564 				no_blank = 1;
565 				break;
566 			}
567 			p++;
568 		}
569 
570 		if (!no_blank) {
571 			free(input);
572 			return (char *)NULL;
573 		}
574 
575 		return input;
576 	}
577 
578 	return (char *)NULL;
579 }
580 
581 /* Used by readline to check if a char in the string being completed is
582  * quoted or not */
583 static int
584 quote_detector(char *line, int index)
585 {
586 	if (index > 0 && line[index - 1] == '\\' && !quote_detector(line, index - 1))
587 		return 1;
588 
589 	return 0;
590 }
591 
592 /* Performs bash-style filename quoting for readline (put a backslash
593  * before any char listed in rl_filename_quote_characters.
594  * Modified version of:
595  * https://utcc.utoronto.ca/~cks/space/blog/programming/ReadlineQuotingExample*/
596 static char *
597 my_rl_quote(char *text, int mt, char *qp)
598 {
599 	/* NOTE: mt and qp arguments are not used here, but are required by
600 	 * rl_filename_quoting_function */
601 	UNUSED(mt); UNUSED(qp);
602 
603 	/*
604 	 * How it works: P and R are pointers to the same memory location
605 	 * initialized (calloced) twice as big as the line that needs to be
606 	 * quoted (in case all chars in the line need to be quoted); TP is a
607 	 * pointer to TEXT, which contains the string to be quoted. We move
608 	 * through TP to find all chars that need to be quoted ("a's" becomes
609 	 * "a\'s", for example). At this point we cannot return P, since this
610 	 * pointer is at the end of the string, so that we return R instead,
611 	 * which is at the beginning of the same string pointed to by P.
612 	 * */
613 	char *r = (char *)NULL, *p = (char *)NULL, *tp = (char *)NULL;
614 
615 	size_t text_len = strlen(text);
616 	/* Worst case: every character of text needs to be escaped. In this
617 	 * case we need 2x text's bytes plus the NULL byte. */
618 	p = (char *)xnmalloc((text_len * 2) + 1, sizeof(char));
619 	r = p;
620 
621 	if (r == NULL)
622 		return (char *)NULL;
623 
624 	/* Escape whatever char that needs to be escaped */
625 	for (tp = text; *tp; tp++) {
626 		if (is_quote_char(*tp))
627 			*p++ = '\\';
628 
629 		*p++ = *tp;
630 	}
631 
632 	/* Add a final null byte to the string */
633 	*p = '\0';
634 	return r;
635 }
636 
637 /* This is the filename_completion_function() function of an old Bash
638  * release (1.14.7) modified to fit CliFM needs */
639 static char *
640 my_rl_path_completion(const char *text, int state)
641 {
642 	if (!text || !*text)
643 		return (char *)NULL;
644 	/* state is zero before completion, and 1 ... n after getting
645 	 * possible completions. Example:
646 	 * cd Do[TAB] -> state 0
647 	 * cuments/ -> state 1
648 	 * wnloads/ -> state 2
649 	 * */
650 
651 	/* Dequote string to be completed (text), if necessary */
652 	static char *tmp_text = (char *)NULL;
653 
654 	if (strchr(text, '\\')) {
655 		char *p = savestring(text, strlen(text));
656 		tmp_text = dequote_str(p, 0);
657 		free(p);
658 		p = (char *)NULL;
659 		if (!tmp_text)
660 			return (char *)NULL;
661 	}
662 
663 	if (*text == '.' && text[1] == '.' && text[2] == '.') {
664 		char *p = savestring(text, strlen(text));
665 		tmp_text = fastback(p);
666 
667 		free(p);
668 		p = (char *)NULL;
669 
670 		if (!tmp_text)
671 			return (char *)NULL;
672 	}
673 
674 /*	int rl_complete_with_tilde_expansion = 0; */
675 	/* ~/Doc -> /home/user/Doc */
676 
677 	static DIR *directory;
678 	static char *filename = (char *)NULL;
679 	static char *dirname = (char *)NULL;
680 	static char *users_dirname = (char *)NULL;
681 	static size_t filename_len;
682 	static int match, ret;
683 	struct dirent *ent = (struct dirent *)NULL;
684 	static int exec = 0, exec_path = 0;
685 	static char *dir_tmp = (char *)NULL;
686 	static char tmp[PATH_MAX];
687 
688 	/* If we don't have any state, then do some initialization. */
689 	if (!state) {
690 		char *temp;
691 
692 		if (dirname)
693 			free(dirname);
694 		if (filename)
695 			free(filename);
696 		if (users_dirname)
697 			free(users_dirname);
698 
699 		/* tmp_text is true whenever text was dequoted */
700 		size_t text_len = strlen((tmp_text) ? tmp_text : text);
701 		if (text_len)
702 			filename = savestring((tmp_text) ? tmp_text : text, text_len);
703 		else
704 			filename = savestring("", 1);
705 
706 		if (!*text)
707 			text = ".";
708 
709 		if (text_len)
710 			dirname = savestring((tmp_text) ? tmp_text : text, text_len);
711 		else
712 			dirname = savestring("", 1);
713 
714 		if (dirname[0] == '.' && dirname[1] == '/')
715 			exec = 1;
716 		else
717 			exec = 0;
718 
719 		/* Get everything after last slash */
720 		temp = strrchr(dirname, '/');
721 
722 		if (temp) {
723 			strcpy(filename, ++temp);
724 			*temp = '\0';
725 		} else {
726 			strcpy(dirname, ".");
727 		}
728 
729 		/* We aren't done yet.  We also support the "~user" syntax. */
730 
731 		/* Save the version of the directory that the user typed. */
732 		size_t dirname_len = strlen(dirname);
733 
734 		users_dirname = savestring(dirname, dirname_len);
735 		/*      { */
736 		char *temp_dirname;
737 		int replace_dirname;
738 
739 		temp_dirname = tilde_expand(dirname);
740 		free(dirname);
741 		dirname = temp_dirname;
742 
743 		replace_dirname = 0;
744 
745 		if (rl_directory_completion_hook)
746 			replace_dirname = (*rl_directory_completion_hook)(&dirname);
747 
748 		if (replace_dirname) {
749 			free(users_dirname);
750 			users_dirname = savestring(dirname, dirname_len);
751 		}
752 		/*      } */
753 		directory = opendir(dirname);
754 		filename_len = strlen(filename);
755 
756 		rl_filename_completion_desired = 1;
757 	}
758 
759 	if (tmp_text) {
760 		free(tmp_text);
761 		tmp_text = (char *)NULL;
762 	}
763 
764 	/* Now that we have some state, we can read the directory. If we found
765 	 * a match among files in dir, break the loop and print the match */
766 
767 	match = 0;
768 
769 	size_t dirname_len = 0;
770 	if (dirname)
771 		dirname_len = strlen(dirname);
772 
773 	/* This block is used only in case of "/path/./" to remove the
774 	 * ending "./" from dirname and to be able to perform thus the
775 	 * executable check via access() */
776 	exec_path = 0;
777 
778 	if (dirname_len > 2) {
779 
780 		if (dirname[dirname_len - 3] == '/' && dirname[dirname_len - 2] == '.'
781 		&& dirname[dirname_len - 1] == '/') {
782 			dir_tmp = savestring(dirname, dirname_len);
783 
784 			if (dir_tmp) {
785 				dir_tmp[dirname_len - 2] = '\0';
786 				exec_path = 1;
787 			}
788 		}
789 	}
790 
791 	/* ############### COMPLETION FILTER ################## */
792 	/* #        This is the heart of the function         #
793 	 * #################################################### */
794 	mode_t type;
795 
796 	while (directory && (ent = readdir(directory))) {
797 #if !defined(_DIRENT_HAVE_D_TYPE)
798 		struct stat attr;
799 		if (!dirname || (*dirname == '.' && !*(dirname + 1)))
800 			xstrsncpy(tmp, ent->d_name, PATH_MAX);
801 		else
802 			snprintf(tmp, PATH_MAX, "%s%s", dirname, ent->d_name);
803 
804 		if (lstat(tmp, &attr) == -1) {
805 			continue;
806 		}
807 
808 		type = get_dt(attr.st_mode);
809 #else
810 		type = ent->d_type;
811 #endif /* !_DIRENT_HAVE_D_TYPE */
812 
813 		/* If the user entered nothing before TAB (ex: "cd [TAB]") */
814 		if (!filename_len) {
815 			/* Exclude "." and ".." as possible completions */
816 			if (SELFORPARENT(ent->d_name))
817 				continue;
818 
819 			/* If 'cd', match only dirs or symlinks to dir */
820 			if (*rl_line_buffer == 'c'
821 			&& strncmp(rl_line_buffer, "cd ", 3) == 0) {
822 				ret = -1;
823 
824 				switch (type) {
825 				case DT_LNK:
826 					if (dirname[0] == '.' && !dirname[1]) {
827 						ret = get_link_ref(ent->d_name);
828 					} else {
829 						snprintf(tmp, PATH_MAX, "%s%s", dirname, ent->d_name);
830 						ret = get_link_ref(tmp);
831 					}
832 
833 					if (ret == S_IFDIR)
834 						match = 1;
835 					break;
836 
837 				case DT_DIR: match = 1; break;
838 
839 				default: break;
840 				}
841 			}
842 
843 			/* If 'open', allow only reg files, dirs, and symlinks */
844 			else if (*rl_line_buffer == 'o'
845 			&& (strncmp(rl_line_buffer, "o ", 2) == 0
846 			|| strncmp(rl_line_buffer, "open ", 5) == 0)) {
847 				ret = -1;
848 
849 				switch (type) {
850 				case DT_LNK:
851 					if (dirname[0] == '.' && !dirname[1]) {
852 						ret = get_link_ref(ent->d_name);
853 					} else {
854 						snprintf(tmp, PATH_MAX, "%s%s", dirname, ent->d_name);
855 						ret = get_link_ref(tmp);
856 					}
857 
858 					if (ret == S_IFDIR || ret == S_IFREG)
859 						match = 1;
860 
861 					break;
862 
863 				case DT_REG: /* fallthrough */
864 				case DT_DIR: match = 1; break;
865 
866 				default: break;
867 				}
868 			}
869 
870 			/* If 'trash', allow only reg files, dirs, symlinks, pipes
871 			 * and sockets. You should not trash a block or a character
872 			 * device */
873 			else if (*rl_line_buffer == 't'
874 			&& (strncmp(rl_line_buffer, "t ", 2) == 0
875 			|| strncmp(rl_line_buffer, "tr ", 2) == 0
876 			|| strncmp(rl_line_buffer, "trash ", 6) == 0)) {
877 
878 				if (type != DT_BLK && type != DT_CHR)
879 					match = 1;
880 			}
881 
882 			/* If "./", list only executable regular files */
883 			else if (exec) {
884 				if (type == DT_REG && access(ent->d_name, X_OK) == 0)
885 					match = 1;
886 			}
887 
888 			/* If "/path/./", list only executable regular files */
889 			else if (exec_path) {
890 				if (type == DT_REG) {
891 					/* dir_tmp is dirname less "./", already
892 					 * allocated before the while loop */
893 					snprintf(tmp, PATH_MAX, "%s%s", dir_tmp, ent->d_name);
894 
895 					if (access(tmp, X_OK) == 0)
896 						match = 1;
897 				}
898 			}
899 
900 			/* No filter for everything else. Just print whatever is
901 			 * there */
902 			else
903 				match = 1;
904 		}
905 
906 		/* If there is at least one char to complete (ex: "cd .[TAB]") */
907 		else {
908 			/* Check if possible completion match up to the length of
909 			 * filename. */
910 			if (case_sens_path_comp) {
911 				if (*ent->d_name != *filename
912 				|| (strncmp(filename, ent->d_name, filename_len) != 0))
913 					continue;
914 			} else {
915 				if (TOUPPER(*ent->d_name) != TOUPPER(*filename)
916 				|| (strncasecmp(filename, ent->d_name, filename_len) != 0))
917 					continue;
918 			}
919 
920 			if (*rl_line_buffer == 'c'
921 			&& strncmp(rl_line_buffer, "cd ", 3) == 0) {
922 				ret = -1;
923 
924 				switch (type) {
925 				case DT_LNK:
926 					if (dirname[0] == '.' && !dirname[1]) {
927 						ret = get_link_ref(ent->d_name);
928 					} else {
929 						snprintf(tmp, PATH_MAX, "%s%s", dirname, ent->d_name);
930 						ret = get_link_ref(tmp);
931 					}
932 
933 					if (ret == S_IFDIR)
934 						match = 1;
935 					break;
936 
937 				case DT_DIR: match = 1; break;
938 
939 				default: break;
940 				}
941 			}
942 
943 			else if (*rl_line_buffer == 'o'
944 			&& (strncmp(rl_line_buffer, "o ", 2) == 0
945 			|| strncmp(rl_line_buffer, "open ", 5) == 0)) {
946 				ret = -1;
947 
948 				switch (type) {
949 				case DT_REG: /* fallthrough */
950 				case DT_DIR: match = 1; break;
951 
952 				case DT_LNK:
953 					if (dirname[0] == '.' && !dirname[1]) {
954 						ret = get_link_ref(ent->d_name);
955 					} else {
956 						snprintf(tmp, PATH_MAX, "%s%s", dirname, ent->d_name);
957 						ret = get_link_ref(tmp);
958 					}
959 
960 					if (ret == S_IFDIR || ret == S_IFREG)
961 						match = 1;
962 					break;
963 
964 				default: break;
965 				}
966 			}
967 
968 			else if (*rl_line_buffer == 't'
969 			&& (strncmp(rl_line_buffer, "t ", 2) == 0
970 			|| strncmp(rl_line_buffer, "tr ", 3) == 0
971 			|| strncmp(rl_line_buffer, "trash ", 6) == 0)) {
972 				if (type != DT_BLK && type != DT_CHR)
973 					match = 1;
974 			}
975 
976 			else if (exec) {
977 				if (type == DT_REG && access(ent->d_name, X_OK) == 0)
978 					match = 1;
979 			}
980 
981 			else if (exec_path) {
982 				if (type == DT_REG) {
983 					snprintf(tmp, PATH_MAX, "%s%s", dir_tmp, ent->d_name);
984 					if (access(tmp, X_OK) == 0)
985 						match = 1;
986 				}
987 			}
988 
989 			else
990 				match = 1;
991 		}
992 
993 		if (match)
994 			break;
995 	}
996 
997 	if (dir_tmp) { /* == exec_path */
998 		free(dir_tmp);
999 		dir_tmp = (char *)NULL;
1000 	}
1001 
1002 	/* readdir() returns NULL on reaching the end of directory stream.
1003 	 * So that if entry is NULL, we have no matches */
1004 
1005 	if (!ent) { /* == !match */
1006 		if (directory) {
1007 			closedir(directory);
1008 			directory = (DIR *)NULL;
1009 		}
1010 
1011 		if (dirname) {
1012 			free(dirname);
1013 			dirname = (char *)NULL;
1014 		}
1015 
1016 		if (filename) {
1017 			free(filename);
1018 			filename = (char *)NULL;
1019 		}
1020 
1021 		if (users_dirname) {
1022 			free(users_dirname);
1023 			users_dirname = (char *)NULL;
1024 		}
1025 
1026 		return (char *)NULL;
1027 	}
1028 
1029 	/* We have a match */
1030 	else {
1031 		cur_comp_type = TCMP_PATH;
1032 		char *temp = (char *)NULL;
1033 
1034 		/* dirname && (strcmp(dirname, ".") != 0) */
1035 		if (dirname && (dirname[0] != '.' || dirname[1])) {
1036 /*			if (rl_complete_with_tilde_expansion && *users_dirname == '~') {
1037 				size_t dirlen = strlen(dirname);
1038 				temp = (char *)xnmalloc(dirlen + strlen(ent->d_name) + 2,
1039 									sizeof(char));
1040 				strcpy(temp, dirname);
1041 				// Canonicalization cuts off any final slash present.
1042 				// We need to add it back.
1043 
1044 				if (dirname[dirlen - 1] != '/') {
1045 					temp[dirlen] = '/';
1046 					temp[dirlen + 1] = '\0';
1047 				}
1048 			} else { */
1049 			temp = (char *)xnmalloc(strlen(users_dirname) +
1050 					strlen(ent->d_name) + 1, sizeof(char));
1051 			strcpy(temp, users_dirname);
1052 /*			} */
1053 			strcat(temp, ent->d_name);
1054 		} else {
1055 			temp = savestring(ent->d_name, strlen(ent->d_name));
1056 		}
1057 
1058 		return (temp);
1059 	}
1060 }
1061 
1062 /* Used by bookmarks completion */
1063 static char *
1064 bookmarks_generator(const char *text, int state)
1065 {
1066 	if (!bookmark_names)
1067 		return (char *)NULL;
1068 
1069 	static int i;
1070 	static size_t len;
1071 	char *name;
1072 
1073 	if (!state) {
1074 		i = 0;
1075 		len = strlen(text);
1076 	}
1077 
1078 	/* Look for bookmarks in bookmark names for a match */
1079 	while ((name = bookmark_names[i++]) != NULL) {
1080 		if (strncmp(name, text, len) == 0)
1081 			return strdup(name);
1082 	}
1083 
1084 	return (char *)NULL;
1085 }
1086 
1087 /* Used by history completion */
1088 static char *
1089 hist_generator(const char *text, int state)
1090 {
1091 	if (!history)
1092 		return (char *)NULL;
1093 
1094 	static int i;
1095 	static size_t len;
1096 	char *name;
1097 
1098 	if (!state) {
1099 		i = 0;
1100 		len = strlen(text);
1101 	}
1102 
1103 	/* Look for cmd history entries for a match */
1104 	while ((name = history[i++]) != NULL) {
1105 		if (strncmp(name, text, len) == 0)
1106 			return strdup(name);
1107 	}
1108 
1109 	return (char *)NULL;
1110 }
1111 
1112 /* Expand string into matching path in the jump database. Used by
1113  * j, jc, and jp commands */
1114 static char *
1115 jump_generator(const char *text, int state)
1116 {
1117 	static int i;
1118 	char *name;
1119 
1120 	if (!state)
1121 		i = 0;
1122 
1123 	if (!jump_db)
1124 		return (char *)NULL;
1125 
1126 	/* Look for matches in the dirhist list */
1127 	while ((name = jump_db[i++].path) != NULL) {
1128 		/* Exclude CWD */
1129 		if (name[1] == ws[cur_ws].path[1] && strcmp(name, ws[cur_ws].path) == 0)
1130 			continue;
1131 		/* Filter by parent */
1132 		if (rl_line_buffer[1] == 'p') {
1133 			if (!strstr(ws[cur_ws].path, name))
1134 				continue;
1135 		}
1136 		/* Filter by child */
1137 		else if (rl_line_buffer[1] == 'c') {
1138 			if (!strstr(name, ws[cur_ws].path))
1139 				continue;
1140 		}
1141 
1142 		if (strstr(name, text))
1143 			return strdup(name);
1144 	}
1145 
1146 	return (char *)NULL;
1147 }
1148 
1149 /* Expand jump order number into the corresponding path. Used by the
1150  * jo command */
1151 static char *
1152 jump_entries_generator(const char *text, int state)
1153 {
1154 	static size_t i;
1155 	char *name;
1156 
1157 	if (!state)
1158 		i = 0;
1159 
1160 	int num_text = atoi(text);
1161 
1162 	/* Check list of jump entries for a match */
1163 	while (i <= jump_n && (name = jump_db[i++].path) != NULL)
1164 		if (*name == *jump_db[num_text - 1].path && strcmp(name,
1165 					jump_db[num_text - 1].path) == 0)
1166 			return strdup(name);
1167 
1168 	return (char *)NULL;
1169 }
1170 
1171 static char *
1172 cschemes_generator(const char *text, int state)
1173 {
1174 	if (!color_schemes)
1175 		return (char *)NULL;
1176 
1177 	static int i;
1178 	static size_t len;
1179 	char *name;
1180 
1181 	if (!state) {
1182 		i = 0;
1183 		len = strlen(text);
1184 	} /* The state variable is zero only the first time the function is
1185 	called, and a non-zero positive in later calls. This means that i
1186 	and len will be necessarilly initialized the first time */
1187 
1188 	/* Look for color schemes in color_schemes for a match */
1189 	while ((name = color_schemes[i++]) != NULL) {
1190 		if (strncmp(name, text, len) == 0)
1191 			return strdup(name);
1192 	}
1193 
1194 	return (char *)NULL;
1195 }
1196 
1197 /* Used by profiles completion */
1198 static char *
1199 profiles_generator(const char *text, int state)
1200 {
1201 	if (!profile_names)
1202 		return (char *)NULL;
1203 
1204 	static int i;
1205 	static size_t len;
1206 	char *name;
1207 
1208 	if (!state) {
1209 		i = 0;
1210 		len = strlen(text);
1211 	} /* The state variable is zero only the first time the function is
1212 	called, and a non-zero positive in later calls. This means that i
1213 	and len will be necessarilly initialized the first time */
1214 
1215 	/* Look for profiles in profile_names for a match */
1216 	while ((name = profile_names[i++]) != NULL) {
1217 		if (strncmp(name, text, len) == 0)
1218 			return strdup(name);
1219 	}
1220 
1221 	return (char *)NULL;
1222 }
1223 
1224 /* Used by ELN expansion */
1225 static char *
1226 filenames_gen_text(const char *text, int state)
1227 {
1228 	static size_t i, len = 0;
1229 	char *name;
1230 	rl_filename_completion_desired = 1;
1231 	/* According to the GNU readline documention: "If it is set to a
1232 	 * non-zero value, directory names have a slash appended and
1233 	 * Readline attempts to quote completed file names if they contain
1234 	 * any embedded word break characters." To make the quoting part
1235 	 * work I had to specify a custom quoting function (my_rl_quote) */
1236 	if (!state) { /* state is zero only the first time readline is
1237 	executed */
1238 		i = 0;
1239 		len = strlen(text);
1240 	}
1241 
1242 	/* Check list of currently displayed files for a match */
1243 	while (i < files && (name = file_info[i++].name) != NULL)
1244 		if (case_sens_path_comp ? strncmp(name, text, len) == 0
1245 		: strncasecmp(name, text, len) == 0)
1246 			return strdup(name);
1247 
1248 	return (char *)NULL;
1249 }
1250 
1251 /* Used by ELN expansion */
1252 static char *
1253 filenames_gen_eln(const char *text, int state)
1254 {
1255 	static size_t i;
1256 	char *name;
1257 	rl_filename_completion_desired = 1;
1258 
1259 	if (!state)
1260 		i = 0;
1261 
1262 	int num_text = atoi(text);
1263 
1264 	/* Check list of currently displayed files for a match */
1265 	while (i < files && (name = file_info[i++].name) != NULL) {
1266 		if (*name == *file_info[num_text - 1].name
1267 		&& strcmp(name, file_info[num_text - 1].name) == 0) {
1268 #ifndef _NO_SUGGESTIONS
1269 			if (suggestion_buf)
1270 				clear_suggestion(CS_FREEBUF);
1271 #endif
1272 			return strdup(name);
1273 		}
1274 	}
1275 
1276 	return (char *)NULL;
1277 }
1278 
1279 /* Used by ELN ranges expansion */
1280 static char *
1281 filenames_gen_ranges(const char *text, int state)
1282 {
1283 	static int i;
1284 	char *name;
1285 	rl_filename_completion_desired = 1;
1286 
1287 	if (!state)
1288 		i = 0;
1289 
1290 	char *r = strchr(text, '-');
1291 	if (!r)
1292 		return (char *)NULL;
1293 
1294 	*r = '\0';
1295 	int a = atoi(text);
1296 	int b = atoi(r + 1);
1297 	*r = '-';
1298 	if (a >= b)
1299 		return (char *)NULL;
1300 
1301 	/* Check list of currently displayed files for a match */
1302 	while (i < (int)files && (name = file_info[i++].name) != NULL) {
1303 		if (i >= a && i <= b) {
1304 #ifndef _NO_SUGGESTIONS
1305 			if (suggestion_buf)
1306 				clear_suggestion(CS_FREEBUF);
1307 #endif
1308 			return strdup(name);
1309 		}
1310 	}
1311 
1312 	return (char *)NULL;
1313 }
1314 
1315 /* Used by commands completion */
1316 static char *
1317 bin_cmd_generator(const char *text, int state)
1318 {
1319 	if (!bin_commands)
1320 		return (char *)NULL;
1321 
1322 	static int i;
1323 	static size_t len;
1324 	char *name;
1325 
1326 	if (!state) {
1327 		i = 0;
1328 		len = strlen(text);
1329 	}
1330 
1331 	while ((name = bin_commands[i++]) != NULL) {
1332 		if (!text || !*text)
1333 			return strdup(name);
1334 		if (*text == *name && strncmp(name, text, len) == 0)
1335 			return strdup(name);
1336 	}
1337 
1338 	return (char *)NULL;
1339 }
1340 
1341 static char *
1342 sort_num_generator(const char *text, int state)
1343 {
1344 	static size_t i;
1345 	char *name;
1346 	rl_filename_completion_desired = 1;
1347 
1348 	if (!state)
1349 		i = 0;
1350 
1351 	int num_text = atoi(text);
1352 	static char *sorts[] = {
1353 	    "none",
1354 	    "name",
1355 	    "size",
1356 	    "atime",
1357 	    "btime",
1358 	    "ctime",
1359 	    "mtime",
1360 	    "version",
1361 	    "extension",
1362 	    "inode",
1363 	    "owner",
1364 	    "group",
1365 	    NULL
1366 	};
1367 
1368 	/* Check list of currently displayed files for a match */
1369 	while (i <= SORT_TYPES && (name = sorts[i++]) != NULL) {
1370 		if (*name == *sorts[num_text]
1371 		&& strcmp(name, sorts[num_text]) == 0)
1372 			return strdup(name);
1373 	}
1374 
1375 	return (char *)NULL;
1376 }
1377 
1378 static char *
1379 nets_generator(const char *text, int state)
1380 {
1381 	if (!remotes)
1382 		return (char *)NULL;
1383 
1384 	static int i;
1385 	static size_t len;
1386 	char *name;
1387 
1388 	if (!state) {
1389 		i = 0;
1390 		len = strlen(text);
1391 	}
1392 
1393 	while ((name = remotes[i++].name) != NULL) {
1394 		if (case_sens_path_comp ? strncmp(name, text, len)
1395 		: strncasecmp(name, text, len) == 0)
1396 			return strdup(name);
1397 	}
1398 
1399 	return (char *)NULL;
1400 }
1401 /*
1402 static char *
1403 env_var_generator(const char *text, int state)
1404 {
1405 	static int i;
1406 	static size_t len;
1407 	char *name;
1408 
1409 	if (!state) {
1410 		i = 0;
1411 		len = strlen(text);
1412 	}
1413 
1414 	while ((name = environ[i++]) != NULL) {
1415 		char *p = strchr(name, '=');
1416 		if (!p)
1417 			return (char *)NULL;
1418 		else
1419 			*p = '\0';
1420 		if (len == 0) {
1421 			char *pp = strdup(name);
1422 			*p = '=';
1423 			return pp;
1424 		}
1425 		int ret = strncmp(name, text, len);
1426 		if (ret == 0) {
1427 			char *pp = strdup(name);
1428 			*p = '=';
1429 			return pp;
1430 		}
1431 		*p = '=';
1432 	}
1433 
1434 	return (char *)NULL;
1435 } */
1436 
1437 static char *
1438 sort_name_generator(const char *text, int state)
1439 {
1440 	static int i;
1441 	static size_t len;
1442 	char *name;
1443 
1444 	if (!state) {
1445 		i = 0;
1446 		len = strlen(text);
1447 	}
1448 
1449 	static char *sorts[] = {
1450 	    "none",
1451 	    "name",
1452 	    "size",
1453 	    "atime",
1454 	    "btime",
1455 	    "ctime",
1456 	    "mtime",
1457 	    "version",
1458 	    "extension",
1459 	    "inode",
1460 	    "owner",
1461 	    "group",
1462 	    NULL};
1463 
1464 	while ((name = sorts[i++]) != NULL) {
1465 		if (strncmp(name, text, len) == 0)
1466 			return strdup(name);
1467 	}
1468 
1469 	return (char *)NULL;
1470 }
1471 
1472 /* Generate entries from the jump database (not using the j function)*/
1473 /*char *
1474 jump_gen(const char *text, int state)
1475 {
1476 	static int i;
1477 	static size_t len;
1478 	char *name;
1479 
1480 	if (!state) {
1481 		i = 0;
1482 		len = strlen(text);
1483 	}
1484 
1485 	while ((name = jump_db[i++].path) != NULL) {
1486 		if (case_sens_path_comp ? strncmp(name, text, len) == 0
1487 		: strncasecmp(name, text, len) == 0)
1488 			return strdup(name);
1489 	}
1490 
1491 	return (char *)NULL;
1492 } */
1493 
1494 static char *
1495 sel_entries_generator(const char *text, int state)
1496 {
1497 	static int i;
1498 	static size_t len;
1499 	char *name;
1500 
1501 	if (!state) {
1502 		i = 0;
1503 		len = strlen(text);
1504 	}
1505 
1506 	while (i < (int)sel_n && (name = sel_elements[i++]) != NULL) {
1507 		if (strncmp(name, text, len) == 0)
1508 			return strdup(name);
1509 	}
1510 
1511 	return (char *)NULL;
1512 }
1513 
1514 /* Return the list of currently trashed files matching TEXT or NULL */
1515 static char **
1516 rl_trashed_files(const char *text)
1517 {
1518 #ifdef _NO_TRASH
1519 	UNUSED(text);
1520 	return (char **)NULL;
1521 #else
1522 	if (!trash_files_dir || !*trash_files_dir)
1523 		return (char **)NULL;
1524 
1525 	if (xchdir(trash_files_dir, NO_TITLE) == -1)
1526 		return (char **)NULL;
1527 
1528 	struct dirent **t = (struct dirent **)NULL;
1529 	int n = scandir(trash_files_dir, &t, NULL, alphasort);
1530 
1531 	xchdir(ws[cur_ws].path, NO_TITLE);
1532 
1533 	if (n == - 1)
1534 		return (char **)NULL;
1535 
1536 	if (n == 2) {
1537 		free(t[0]);
1538 		free(t[1]);
1539 		free(t);
1540 		return (char **)NULL;
1541 	}
1542 
1543 	char **tfiles = (char **)xnmalloc((size_t)n + 2, sizeof(char *));
1544 	if (text) {
1545 		tfiles[0] = savestring(text, strlen(text));
1546 	} else {
1547 		tfiles[0] = (char *)xnmalloc(1, sizeof(char));
1548 		*tfiles[0] = '\0';
1549 	}
1550 
1551 	int nn = 1, i = 0;
1552 	size_t tlen = text ? strlen(text) : 0;
1553 	for (; i < n; i++) {
1554 		char *name = t[i]->d_name;
1555 		if (SELFORPARENT(name) || !text || strncmp(text, name, tlen) != 0) {
1556 			free(t[i]);
1557 			continue;
1558 		}
1559 		tfiles[nn++] = savestring(name, strlen(name));
1560 		free(t[i]);
1561 	}
1562 	free(t);
1563 
1564 	tfiles[nn] = (char *)NULL;
1565 
1566 	/* If only one match */
1567 	if (nn == 2) {
1568 		char *d = escape_str(tfiles[1]);
1569 		free(tfiles[1]);
1570 		tfiles[1] = (char *)NULL;
1571 		if (d) {
1572 			tfiles[0] = (char *)xrealloc(tfiles[0], (strlen(d) + 1) * sizeof(char));
1573 			strcpy(tfiles[0], d);
1574 			free(d);
1575 		}
1576 	}
1577 
1578 	return tfiles;
1579 #endif /* _NO_TRASH */
1580 }
1581 
1582 char **
1583 my_rl_completion(const char *text, int start, int end)
1584 {
1585 	char **matches = (char **)NULL;
1586 	cur_comp_type = TCMP_NONE;
1587 	UNUSED(end);
1588 	if (start == 0) { /* Only for the first entered word */
1589 		/* Commands completion */
1590 /*		if (end == 0 && !autocd && !auto_open) {
1591 			// If text is empty, do nothing
1592 			// Prevent readline from attempting path completion if
1593 			// rl_completion matches returns NULL
1594 			rl_attempted_completion_over = 1;
1595 			return (char **)NULL;
1596 		} */
1597 
1598 		/* If the xrename function (for the m command) is running
1599 		 * only filenames completion is available */
1600 
1601 		/* History cmd completion */
1602 		if (!_xrename && *text == '!') {
1603 			matches = rl_completion_matches(text + 1, &hist_generator);
1604 			if (matches)
1605 				cur_comp_type = TCMP_HIST;
1606 		}
1607 
1608 		/* If autocd or auto-open, try to expand ELN's first */
1609 		if (!matches && (autocd || auto_open)) {
1610 			if (*text >= '1' && *text <= '9') {
1611 				int num_text = atoi(text);
1612 
1613 				if (is_number(text) && num_text > 0 && num_text <= (int)files) {
1614 					matches = rl_completion_matches(text, &filenames_gen_eln);
1615 					if (matches)
1616 						cur_comp_type = TCMP_ELN;
1617 				}
1618 			}
1619 
1620 			/* Compĺete with files in CWD */
1621 			if (!matches && (!text || *text != '/')) {
1622 				matches = rl_completion_matches(text, &filenames_gen_text);
1623 				if (matches)
1624 					cur_comp_type = TCMP_PATH;
1625 			}
1626 
1627 			/* Complete with entries in the jump database */
1628 /*			if (autocd && !matches)
1629 				matches = rl_completion_matches(text, &jump_gen); */
1630 		}
1631 
1632 		/* Bookmarks completion */
1633 		if (!_xrename && !matches && (autocd || auto_open) && expand_bookmarks)
1634 			matches = rl_completion_matches(text, &bookmarks_generator);
1635 
1636 		/* If neither autocd nor auto-open, try to complete with
1637 		 * command names */
1638 		if (!_xrename && !matches) {
1639 			matches = rl_completion_matches(text, &bin_cmd_generator);
1640 			if (matches)
1641 				cur_comp_type = TCMP_CMD;
1642 		}
1643 
1644 /*		if (!_xrename && !matches && *text == '$') {
1645 			matches = rl_completion_matches(text + 1, &env_var_generator);
1646 		} */
1647 	}
1648 
1649 	/* Second word or more */
1650 	else {
1651 		if (!_xrename && nwords == 1 && rl_line_buffer[rl_end - 1] != ' ') {
1652 			matches = rl_completion_matches(text, &bin_cmd_generator);
1653 			if (matches) {
1654 				cur_comp_type = TCMP_CMD;
1655 				return matches;
1656 			}
1657 		}
1658 
1659 #ifndef _NO_LIRA
1660 		/* #### OPEN WITH #### */
1661 		if (!_xrename && rl_end > 4 && *rl_line_buffer == 'o' && rl_line_buffer[1] == 'w'
1662 		&& rl_line_buffer[2] == ' ' && rl_line_buffer[3]
1663 		&& rl_line_buffer[3] != ' ') {
1664 			char *p = rl_line_buffer + 3;
1665 			char *s = strrchr(p, ' ');
1666 			if (s)
1667 				*s = '\0';
1668 			matches = mime_open_with_tab(p, text);
1669 			if (s)
1670 				*s = ' ';
1671 			if (matches) {
1672 				cur_comp_type = TCMP_OPENWITH;
1673 				return matches;
1674 			}
1675 		}
1676 #endif /* _NO_LIRA */
1677 
1678 		/* ### UNTRASH ### */
1679 		if (!_xrename && *rl_line_buffer == 'u' && (rl_line_buffer[1] == ' '
1680 		|| (rl_line_buffer[1] == 'n'
1681 		&& (strncmp(rl_line_buffer, "untrash ", 8) == 0
1682 		|| strncmp(rl_line_buffer, "undel ", 6) == 0)))) {
1683 			matches = rl_trashed_files(text);
1684 			if (matches) {
1685 				cur_comp_type = TCMP_UNTRASH;
1686 				return matches;
1687 			}
1688 		}
1689 
1690 		/* ### TRASH DEL ### */
1691 		if (!_xrename && *rl_line_buffer == 't' && (rl_line_buffer[1] == ' '
1692 		|| rl_line_buffer[1] == 'r')
1693 		&& (strncmp(rl_line_buffer, "t del ", 6) == 0
1694 		|| strncmp(rl_line_buffer, "tr del ", 7) == 0
1695 		|| strncmp(rl_line_buffer, "trash del ", 10) == 0)) {
1696 			matches = rl_trashed_files(text);
1697 			if (matches) {
1698 				cur_comp_type = TCMP_TRASHDEL;
1699 				return matches;
1700 			}
1701 		}
1702 
1703 		/* #### ELN AND JUMP ORDER EXPANSION ### */
1704 
1705 		/* Perform this check only if the first char of the string to be
1706 		 * completed is a number in order to prevent an unnecessary call
1707 		 * to atoi */
1708 		if (!_xrename && *text >= '0' && *text <= '9') {
1709 			/* Check ranges */
1710 			char *r = strchr(text, '-');
1711 			if (r && *(r + 1) >= '0' && *(r + 1) <= '9') {
1712 				*r = '\0';
1713 				if (is_number(text) && is_number(r + 1)) {
1714 					*r = '-';
1715 					matches = rl_completion_matches(text, &filenames_gen_ranges);
1716 					if (matches) {
1717 						cur_comp_type = TCMP_RANGES;
1718 						return matches;
1719 					}
1720 				} else {
1721 					*r = '-';
1722 				}
1723 			}
1724 
1725 			int num_text = atoi(text);
1726 
1727 			/* Dirjump: jo command */
1728 			if (*rl_line_buffer == 'j' && rl_line_buffer[1] == 'o'
1729 			&& rl_line_buffer[2] == ' ') {
1730 				if (is_number(text) && num_text > 0 && num_text <= (int)jump_n) {
1731 					matches = rl_completion_matches(text,
1732 					    &jump_entries_generator);
1733 					if (matches)
1734 						cur_comp_type = TCMP_JUMP;
1735 				}
1736 			}
1737 
1738 			/* Sort number expansion */
1739 			else if (*rl_line_buffer == 's'
1740 			&& (strncmp(rl_line_buffer, "st ", 3) == 0
1741 			|| strncmp(rl_line_buffer, "sort ", 5) == 0)
1742 			&& is_number(text) && num_text >= 0 && num_text <= SORT_TYPES) {
1743 				matches = rl_completion_matches(text, &sort_num_generator);
1744 				if (matches)
1745 					cur_comp_type = TCMP_SORT;
1746 			}
1747 
1748 			/* ELN expansion */
1749 			else if (is_number(text) && num_text > 0 && num_text <= (int)files) {
1750 				matches = rl_completion_matches(text, &filenames_gen_eln);
1751 				if (matches)
1752 					cur_comp_type = TCMP_ELN;
1753 			}
1754 		}
1755 
1756 		/* ### SEL KEYWORD EXPANSION ### */
1757 		else if (!_xrename && sel_n && *text == 's'
1758 		&& strncmp(text, "sel", 3) == 0) {
1759 			matches = rl_completion_matches("", &sel_entries_generator);
1760 			if (matches)
1761 				cur_comp_type = TCMP_SEL;
1762 		}
1763 
1764 		/* ### DESELECT COMPLETION ### */
1765 		else if (!_xrename && sel_n && *rl_line_buffer == 'd'
1766 		&& (strncmp(rl_line_buffer, "ds ", 3) == 0
1767 		|| strncmp(rl_line_buffer, "desel ", 6) == 0)) {
1768 			matches = rl_completion_matches(text, &sel_entries_generator);
1769 			if (matches)
1770 				cur_comp_type = TCMP_DESEL;
1771 		}
1772 
1773 		/* ### DIRJUMP COMPLETION ### */
1774 		/* j, jc, jp commands */
1775 		else if (!_xrename && *rl_line_buffer == 'j' && (rl_line_buffer[1] == ' '
1776 		|| ((rl_line_buffer[1] == 'c' || rl_line_buffer[1] == 'p')
1777 		&& rl_line_buffer[2] == ' ')
1778 		|| strncmp(rl_line_buffer, "jump ", 5) == 0)) {
1779 			matches = rl_completion_matches(text, &jump_generator);
1780 			if (matches)
1781 				cur_comp_type = TCMP_JUMP;
1782 		}
1783 
1784 		/* ### BOOKMARKS COMPLETION ### */
1785 
1786 		else if (!_xrename && *rl_line_buffer == 'b' && (rl_line_buffer[1] == 'm'
1787 		|| rl_line_buffer[1] == 'o')
1788 		&& (strncmp(rl_line_buffer, "bm ", 3) == 0
1789 		|| strncmp(rl_line_buffer, "bookmarks ", 10) == 0)) {
1790 #ifndef _NO_SUGGESTIONS
1791 			if (suggestion.type != FILE_SUG)
1792 				rl_attempted_completion_over = 1;
1793 #endif
1794 			matches = rl_completion_matches(text, &bookmarks_generator);
1795 			if (matches)
1796 				cur_comp_type = TCMP_BOOKMARK;
1797 		}
1798 
1799 		/* ### COLOR SCHEMES COMPLETION ### */
1800 		else if (!_xrename && *rl_line_buffer == 'c' && ((rl_line_buffer[1] == 's'
1801 		&& rl_line_buffer[2] == ' ')
1802 		|| strncmp(rl_line_buffer, "colorschemes ", 13) == 0)) {
1803 			matches = rl_completion_matches(text, &cschemes_generator);
1804 			if (matches)
1805 				cur_comp_type = TCMP_CSCHEME;
1806 		}
1807 
1808 		/* ### PROFILES COMPLETION ### */
1809 		else if (!_xrename && *rl_line_buffer == 'p' && (rl_line_buffer[1] == 'r'
1810 		|| rl_line_buffer[1] == 'f')
1811 		&& (strncmp(rl_line_buffer, "pf set ", 7) == 0
1812 		|| strncmp(rl_line_buffer, "profile set ", 12) == 0
1813 		|| strncmp(rl_line_buffer, "pf del ", 7) == 0
1814 		|| strncmp(rl_line_buffer, "profile del ", 12) == 0)) {
1815 #ifndef _NO_SUGGESTIONS
1816 			if (suggestion.type != FILE_SUG)
1817 				rl_attempted_completion_over = 1;
1818 #endif /* _NO_SUGGESTIONS */
1819 			matches = rl_completion_matches(text, &profiles_generator);
1820 			if (matches)
1821 				cur_comp_type = TCMP_PROF;
1822 		}
1823 
1824 		else if (!_xrename && expand_bookmarks) {
1825 			matches = rl_completion_matches(text, &bookmarks_generator);
1826 			if (matches)
1827 				cur_comp_type = TCMP_BOOKMARK;
1828 		}
1829 
1830 		else if (!_xrename && *rl_line_buffer == 's'
1831 		&& (strncmp(rl_line_buffer, "st ", 3) == 0
1832 		|| strncmp(rl_line_buffer, "sort ", 5) == 0)) {
1833 			matches = rl_completion_matches(text, &sort_name_generator);
1834 			if (matches)
1835 				cur_comp_type = TCMP_SORT;
1836 		}
1837 
1838 		else if (!_xrename && *rl_line_buffer == 'n'
1839 		&& strncmp(rl_line_buffer, "net ", 4) == 0) {
1840 			matches = rl_completion_matches(text, &nets_generator);
1841 			if (matches)
1842 				cur_comp_type = TCMP_NET;
1843 		}
1844 	}
1845 
1846 	/* ### PATH COMPLETION ### */
1847 
1848 	/* If none of the above, readline will attempt
1849 	 * path completion instead via my custom my_rl_path_completion() */
1850 	return matches;
1851 }
1852 
1853 int
1854 initialize_readline(void)
1855 {
1856 	/* #### INITIALIZE READLINE (what a hard beast to tackle!!) #### */
1857 
1858 	/* Set the name of the program using readline. Mostly used for
1859 	 * conditional constructs in the inputrc file */
1860 	rl_readline_name = argv_bk[0];
1861 
1862 /*	add_func_to_rl(); */
1863 
1864 	/* Load readline initialization file. Check order:
1865 	 * INPUTRC env var
1866 	 * ~/.config/clifm/readline.cfm
1867 	 * ~/.inputrc
1868 	 * /etc/inputrc */
1869 	char *p = getenv("INPUTRC");
1870 	if (p) {
1871 		rl_read_init_file(p);
1872 	} else if (config_dir_gral) {
1873 		char *rl_file = (char *)xnmalloc(strlen(config_dir_gral) + 14,
1874 							sizeof(char));
1875 		sprintf(rl_file, "%s/readline.cfm", config_dir_gral);
1876 		rl_read_init_file(rl_file);
1877 		free(rl_file);
1878 	}
1879 
1880 	/* Enable tab auto-completion for commands (in PATH) in case of
1881 	  * first entered string (if autocd and/or auto-open are enabled, check
1882 	  * for paths as well). The second and later entered strings will
1883 	  * be autocompleted with paths instead, just like in Bash, or with
1884 	  * listed file names, in case of ELN's. I use a custom completion
1885 	  * function to add command and ELN completion, since readline's
1886 	  * internal completer only performs path completion */
1887 
1888 	/* Define a function for path completion.
1889 	 * NULL means to use filename_entry_function (), the default
1890 	 * filename completer. */
1891 	rl_completion_entry_function = my_rl_path_completion;
1892 
1893 	/* Pointer to alternative function to create matches.
1894 	 * Function is called with TEXT, START, and END.
1895 	 * START and END are indices in RL_LINE_BUFFER saying what the
1896 	 * boundaries of TEXT are.
1897 	 * If this function exists and returns NULL then call the value of
1898 	 * rl_completion_entry_function to try to match, otherwise use the
1899 	 * array of strings returned. */
1900 	rl_attempted_completion_function = my_rl_completion;
1901 	rl_ignore_completion_duplicates = 1;
1902 
1903 	/* I'm using here a custom quoting function. If not specified,
1904 	 * readline uses the default internal function. */
1905 	rl_filename_quoting_function = my_rl_quote;
1906 
1907 	/* Tell readline what char to use for quoting. This is only the
1908 	 * readline internal quoting function, and for custom ones, like the
1909 	 * one I use above. However, custom quoting functions, though they
1910 	 * need to define their own quoting chars, won't be called at all
1911 	 * if this variable isn't set. */
1912 	rl_completer_quote_characters = "\"'";
1913 	rl_completer_word_break_characters = " ";
1914 
1915 	/* Whenever readline finds any of the following chars, it will call
1916 	 * the quoting function */
1917 	rl_filename_quote_characters = " \t\n\"\\'`@$><=,;|&{[()]}?!*^";
1918 	/* According to readline documentation, the following string is
1919 	 * the default and the one used by Bash: " \t\n\"\\'`@$><=;|&{(" */
1920 
1921 	/* Executed immediately before calling the completer function, it
1922 	 * tells readline if a space char, which is a word break character
1923 	 * (see the above rl_completer_word_break_characters variable) is
1924 	 * quoted or not. If it is, readline then passes the whole string
1925 	 * to the completer function (ex: "user\ file"), and if not, only
1926 	 * wathever it found after the space char (ex: "file")
1927 	 * Thanks to George Brocklehurst for pointing out this function:
1928 	 * https://thoughtbot.com/blog/tab-completion-in-gnu-readline*/
1929 	rl_char_is_quoted_p = quote_detector;
1930 
1931 	/* Define a function to handle suggestions and syntax highlighting */
1932 	rl_getc_function = my_rl_getc;
1933 
1934 	/* This function is executed inmediately before path completion. So,
1935 	 * if the string to be completed is, for instance, "user\ file" (see
1936 	 * the above comment), this function should return the dequoted
1937 	 * string so it won't conflict with system file names: you want
1938 	 * "user file", because "user\ file" does not exist, and, in this
1939 	 * latter case, readline won't find any matches */
1940 	rl_filename_dequoting_function = dequote_str;
1941 
1942 	/* Initialize the keyboard bindings function */
1943 	readline_kbinds();
1944 
1945 	/* Copy the list of quote chars to a global variable to be used
1946 	 * later by some of the program functions like split_str(),
1947 	 * my_rl_quote(), is_quote_char(), and my_rl_dequote() */
1948 	qc = savestring(rl_filename_quote_characters,
1949 	    strlen(rl_filename_quote_characters));
1950 
1951 #if !defined(_NO_SUGGESTIONS) && defined(__FreeBSD__)
1952 	if (!(flags & GUI) && getenv("CLIFM_FREEBSD_CONSOLE_SC"))
1953 		freebsd_sc_console = 1;
1954 #endif
1955 
1956 	return EXIT_SUCCESS;
1957 }
1958