1 /* suggestions.c -- functions to manage the suggestions system */
2 
3 /*
4  * This file is part of CliFM
5  *
6  * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7  * All rights reserved.
8 
9  * CliFM is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * CliFM is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  * MA 02110-1301, USA.
23 */
24 #ifndef _NO_SUGGESTIONS
25 
26 #include "helpers.h"
27 
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <stdio.h>
31 #include <string.h>
32 #ifdef __OpenBSD__
33 #include <strings.h>
34 #endif
35 #include <unistd.h>
36 #include <errno.h>
37 #include <dirent.h>
38 #include <termios.h>
39 
40 #ifdef __linux__
41 #include <sys/capability.h>
42 #endif
43 
44 #ifdef __OpenBSD__
45 typedef char *rl_cpvfunc_t;
46 #include <ereadline/readline/readline.h>
47 #else
48 #include <readline/readline.h>
49 #endif
50 
51 #include "aux.h"
52 #include "checks.h"
53 #include "colors.h"
54 #include "jump.h"
55 #include "readline.h"
56 #include "builtins.h"
57 
58 #ifndef _NO_HIGHLIGHT
59 #include "highlight.h"
60 #endif
61 
62 #define NO_MATCH 0
63 #define PARTIAL_MATCH 1
64 #define FULL_MATCH 2
65 
66 #define CHECK_MATCH 0
67 #define PRINT_MATCH 1
68 
69 static int free_color = 0;
70 char *last_word = (char *)NULL;
71 int last_word_offset = 0;
72 
73 #ifndef _NO_HIGHLIGHT
74 /* Change the color of the word _LAST_WORD, at offset OFFSET, to COLOR
75  * in the current input string */
76 /*static void
77 change_word_color(const char *_last_word, const int offset, const char *color)
78 {
79 	int bk = rl_point;
80 	fputs("\x1b[?25l", stdout);
81 	rl_delete_text(offset, rl_end);
82 	rl_point = rl_end = offset;
83 	rl_redisplay();
84 	fputs(color, stdout);
85 	rl_insert_text(_last_word);
86 	rl_point = bk;
87 	fputs("\x1b[?25h", stdout);
88 } */
89 #endif
90 
91 int
recover_from_wrong_cmd(void)92 recover_from_wrong_cmd(void)
93 {
94 	/* Check rl_dispathing to know whether we are called from a keybind,
95 	 * in which case we should skip this check */
96 	if (rl_line_buffer && !rl_dispatching) {
97 		char *p = (strrchr(rl_line_buffer, ' '));
98 		if (p && p != rl_line_buffer && *(p - 1) != '\\' && *(p + 1) != ' ')
99 			return EXIT_FAILURE;
100 	}
101 
102 	rl_restore_prompt();
103 	rl_clear_message();
104 
105 #ifndef _NO_HIGHLIGHT
106 	if (highlight)
107 		recolorize_line();
108 #endif
109 
110 	wrong_cmd = 0;
111 	return EXIT_SUCCESS;
112 }
113 
114 /* This function is only used before running a keybind command. We don't
115  * want the suggestion buffer after running a keybind */
116 void
free_suggestion(void)117 free_suggestion(void)
118 {
119 	free(suggestion_buf);
120 	suggestion_buf = (char *)NULL;
121 	suggestion.printed = 0;
122 	suggestion.nlines = 0;
123 }
124 
125 void
clear_suggestion(const int free_sug)126 clear_suggestion(const int free_sug)
127 {
128 	/* Delete everything in the current line starting from the current
129 	 * cursor position */
130 	if (write(STDOUT_FILENO, DLFC, DLFC_LEN) <= 0) {}
131 
132 	if (suggestion.nlines > 1) {
133 		/* Save cursor position */
134 		get_cursor_position(STDIN_FILENO, STDOUT_FILENO);
135 
136 		int i = (int)suggestion.nlines;
137 		while (--i > 0) {
138 			/* Move the cursor to the beginning of the next line */
139 			if (write(STDOUT_FILENO, "\x1b[1E", 4) <= 0) {}
140 			/* Delete the line */
141 			if (write(STDOUT_FILENO, "\x1b[0K", 4) <= 0) {}
142 		}
143 		/* Restore cursor position */
144 		printf("\x1b[%d;%dH", currow, curcol);
145 		fflush(stdout);
146 		suggestion.nlines = 0;
147 	}
148 
149 	suggestion.printed = 0;
150 	if (free_sug) {
151 		free(suggestion_buf);
152 		suggestion_buf = (char *)NULL;
153 	}
154 }
155 
156 void
remove_suggestion_not_end(void)157 remove_suggestion_not_end(void)
158 {
159 	printf("\x1b[%dC", rl_end - rl_point);
160 	fflush(stdout);
161 	clear_suggestion(CS_FREEBUF);
162 	printf("\x1b[%dD", rl_end - rl_point);
163 	fflush(stdout);
164 }
165 
166 /* Clear the line, print the suggestion (STR) at OFFSET in COLOR, and
167  * move the cursor back to the original position.
168  * OFFSET marks the point in STR that is already typed: the suggestion
169  * will be printed starting from this point */
170 void
print_suggestion(const char * str,size_t offset,const char * color)171 print_suggestion(const char *str, size_t offset, const char *color)
172 {
173 	if (!str || !*str)
174 		return;
175 
176 	int baej_offset = 2;
177 
178 	if (wrong_cmd && !recover_from_wrong_cmd()
179 #ifndef _NO_HIGHLIGHT
180 	&& (rl_point == rl_end || !highlight))
181 #else
182 	&& rl_point == rl_end)
183 #endif
184 		offset++;
185 
186 	if (suggestion.printed && str != suggestion_buf)
187 		clear_suggestion(CS_FREEBUF);
188 
189 	/* Store cursor position into two global variables: currow and curcol */
190 	get_cursor_position(STDIN_FILENO, STDOUT_FILENO);
191 #ifndef _NO_HIGHLIGHT
192 	/* The highlight function modifies the terminal's idea of the current
193 	 * cursor position: let's correct it */
194 	if (highlight && rl_point != rl_end) {
195 		printf("\x1b[%dD", rl_end - rl_point);
196 		fflush(stdout);
197 		offset++;
198 	}
199 #endif
200 
201 	if (offset > strlen(str))
202 		return;
203 
204 	/* Do not print suggestions bigger than what the current terminal
205 	 * window size can hold */
206 	size_t suggestion_len = wc_xstrlen(str + offset);
207 	if ((int)suggestion_len > (term_cols * term_rows) - curcol)
208 		return;
209 	size_t cuc = (size_t)curcol; /* Current cursor column position*/
210 	int baej = 0; /* Bookmark, alias, ELN, or jump */
211 
212 	if (suggestion.type == BOOKMARK_SUG || suggestion.type == ALIAS_SUG
213 	|| suggestion.type == ELN_SUG || suggestion.type == JCMD_SUG
214 	|| suggestion.type == JCMD_SUG_NOACD) {
215 		/* 3 = 1 (one char forward) + 2 (" >") */
216 //		cuc += 4;
217 		cuc += suggestion.type == ELN_SUG ? 3 : 4;
218 		baej = 1;
219 	}
220 
221 	size_t cucs = cuc + suggestion_len;
222 	/* slines: amount of lines we need to print the suggestion, including
223 	 * the current line */
224 	size_t slines = 1;
225 
226 	if (cucs > term_cols) {
227 		slines = cucs / (size_t)term_cols;
228 		fflush(stdout);
229 		int cucs_rem = (int)cucs % term_cols;
230 		if (cucs_rem > 0)
231 			slines++;
232 	}
233 
234 	if (slines > (size_t)term_rows)
235 		return;
236 
237 	/* In some cases (accepting first suggested word), we might want to
238 	 * reprint the suggestion buffer, in which case it is already stored */
239 	if (str != suggestion_buf) {
240 		/* Store the suggestion in a buffer to be used later by the
241 		 * rl_accept_suggestion function (keybinds.c) */
242 		suggestion_buf = (char *)xnmalloc(strlen(str) + 1, sizeof(char));
243 		strcpy(suggestion_buf, str);
244 	}
245 
246 	/* If not at the end of the line, move the cursor there */
247 	if (rl_end > rl_point) {
248 		printf("\x1b[%dC", rl_end - rl_point);
249 		fflush(stdout);
250 	}
251 	/* rl_end and rl_point are not updated: they do not include
252 	 * the last typed char. However, since we only care here about
253 	 * the difference between them, it doesn't matter: the result
254 	 * is the same (7 - 4 == 6 - 3 == 1) */
255 
256 	/* Erase everything after the current cursor position */
257 	if (write(STDOUT_FILENO, DLFC, DLFC_LEN) <= 0) {}
258 
259 	if (baej) {
260 		/* Move the cursor %d columns to the right and print "> " */
261 		printf("\x1b[%dC%s> \x1b[0m", baej_offset, sp_c);
262 	}
263 
264 	/* Print the suggestion */
265 	printf("%s%s", color, str + offset - (offset ? 1 : 0));
266 	fflush(stdout);
267 
268 	/* Update the row number, if needed */
269 	/* If the cursor is in the last row, printing a multi-line suggestion
270 	 * will move the beginning of the current line up the number of
271 	 * lines taken by the suggestion, so that we need to update the
272 	 * value to move the cursor back to the correct row (the beginning
273 	 * of the line) */
274 	int old_currow = currow;
275 	/* extra_rows: amount of extra rows we need to print the suggestion
276 	 * (excluding the current row) */
277 	int extra_rows = (int)slines - 1;
278 	if (extra_rows && old_currow + extra_rows >= term_rows)
279 		currow -= extra_rows - (term_rows - old_currow);
280 
281 	/* Restore cursor position */
282 	printf("\x1b[%d;%dH", currow, curcol);
283 
284 	/* Store the amount of lines taken by the current command line
285 	 * (plus the suggestion's length) to be able to correctly
286 	 * remove it later (via the clear_suggestion function) */
287 	suggestion.nlines = slines;
288 	/* Let's keep a record of the suggestion color in case we need to
289 	 * reprint it */
290 	suggestion.color = (char *)color;
291 
292 	return;
293 }
294 
295 /* Used by the check_completions function to get file names color
296  * according to file type */
297 static char *
get_comp_color(const char * filename,const struct stat attr)298 get_comp_color(const char *filename, const struct stat attr)
299 {
300 	char *color = no_c;
301 
302 	switch(attr.st_mode & S_IFMT) {
303 	case S_IFDIR:
304 		if (light_mode)
305 			return di_c;
306 		if (access(filename, R_OK | X_OK) != 0)
307 			color = nd_c;
308 		else
309 			color = get_dir_color(filename, attr.st_mode);
310 		break;
311 
312 	case S_IFREG:
313 		if (light_mode)
314 			return fi_c;
315 		if (access(filename, R_OK) == -1)
316 			color = nf_c;
317 		else if (attr.st_mode & S_ISUID)
318 			color = su_c;
319 		else if (attr.st_mode & S_ISGID)
320 			color = sg_c;
321 		else {
322 #ifdef _LINUX_CAP
323 			cap_t cap = cap_get_file(filename);
324 			if (cap) {
325 				color = ca_c;
326 				cap_free(cap);
327 			} else if (attr.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
328 #else
329 			if (attr.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
330 #endif
331 				if (attr.st_size == 0)
332 					color = ee_c;
333 				else
334 					color = ex_c;
335 			} else if (attr.st_size == 0)
336 				color = ef_c;
337 			else if (attr.st_nlink > 1)
338 				color = mh_c;
339 			else {
340 				char *ext = strrchr(filename, '.');
341 				if (ext && ext != filename) {
342 					char *extcolor = get_ext_color(ext);
343 					if (extcolor) {
344 						char *ext_color = (char *)xnmalloc(strlen(extcolor)
345 											+ 4, sizeof(char));
346 						sprintf(ext_color, "\x1b[%sm", extcolor);
347 						color = ext_color;
348 						free_color = 1;
349 						extcolor = (char *)NULL;
350 					} else  {
351 						color = fi_c;
352 					}
353 				} else {
354 					color = fi_c;
355 				}
356 			}
357 		}
358 		break;
359 
360 	case S_IFLNK: {
361 		if (light_mode)
362 			return ln_c;
363 		char *linkname = realpath(filename, (char *)NULL);
364 		if (linkname)
365 			color = ln_c;
366 		else
367 			color = or_c;
368 		}
369 		break;
370 
371 	case S_IFSOCK: color = so_c; break;
372 	case S_IFBLK: color = bd_c; break;
373 	case S_IFCHR: color = cd_c; break;
374 	case S_IFIFO: color = pi_c; break;
375 	default: color = no_c; break;
376 	}
377 
378 	return color;
379 }
380 
381 static int
382 check_completions(char *str, size_t len, const unsigned char c,
383 				const int print)
384 {
385 	if (!str || !*str)
386 		return NO_MATCH;
387 
388 	if (len) {
389 		while (str[len - 1] == ' ')
390 			str[--len] = '\0';
391 	}
392 
393 	int printed = NO_MATCH;
394 	size_t i;
395 	struct stat attr;
396 	char **_matches = rl_completion_matches(str, rl_completion_entry_function);
397 
398 	suggestion.filetype = DT_REG;
399 	free_color = 0;
400 
401 	char *color = (char *)NULL, *_color = (char *)NULL;
402 	if (suggest_filetype_color)
403 		color = no_c;
404 	else
405 		color = sf_c;
406 
407 	if (!_matches)
408 		return NO_MATCH;
409 
410 	if (!len)
411 		goto FREE;
412 
413 	/* If only one match */
414 //	if (_matches[0] && *_matches[0] && strlen(_matches[0]) > len)
415 	if (!_matches[1] || !*_matches[1]) {
416 		if (!print) {
417 			if (suggestion.printed && suggestion_buf)
418 				clear_suggestion(CS_FREEBUF);
419 			if (strlen(_matches[0]) == len)
420 				printed = FULL_MATCH;
421 			else
422 				printed = PARTIAL_MATCH;
423 			goto FREE;
424 		}
425 
426 		int append_slash = 0;
427 
428 		char *p = (char *)NULL;
429 		if (*_matches[0] == '~')
430 			p = tilde_expand(_matches[0]);
431 
432 		if (lstat(p ? p : _matches[0], &attr) != -1) {
433 			if ((attr.st_mode & S_IFMT) == S_IFDIR) {
434 				append_slash = 1;
435 				suggestion.filetype = DT_DIR;
436 			}
437 			if (suggest_filetype_color)
438 				color = get_comp_color(p ? p : _matches[0], attr);
439 		} else {
440 			/* We have a partial completion. Set filetype to DT_DIR
441 			 * so that the rl_accept_suggestion function won't append
442 			 * a space after the file name */
443 			suggestion.filetype = DT_DIR;
444 		}
445 
446 		free(p);
447 
448 		char t[NAME_MAX + 2];
449 		*t = '\0';
450 		if (append_slash)
451 			snprintf(t, NAME_MAX + 2, "%s/", _matches[0]);
452 		char *tmp = escape_str(*t ? t : _matches[0]);
453 
454 		if (c != BS)
455 			suggestion.type = COMP_SUG;
456 
457 		if (tmp) {
458 			print_suggestion(tmp, len, color);
459 			free(tmp);
460 		} else {
461 			print_suggestion(_matches[0], len, color);
462 		}
463 
464 		printed = PARTIAL_MATCH;
465 	} else {
466 		/* If multiple matches, suggest the first one */
467 		if (_matches[1] && *_matches[1]) {
468 
469 			if (!print) {
470 				if (suggestion.printed && suggestion_buf)
471 					clear_suggestion(CS_FREEBUF);
472 				if (strlen(_matches[1]) == len || str[len - 1] == '/')
473 					printed = FULL_MATCH;
474 				else
475 					printed = PARTIAL_MATCH;
476 				goto FREE;
477 			}
478 
479 /*			if (strlen(_matches[1]) == len || str[len - 1] == '/') {
480 				if (suggestion.printed && suggestion_buf)
481 					clear_suggestion(CS_FREEBUF);
482 				printed = FULL_MATCH;
483 				goto FREE;
484 			} */
485 
486 			int append_slash = 0;
487 
488 			char *p = (char *)NULL;
489 			if (*_matches[1] == '~')
490 				p = tilde_expand(_matches[1]);
491 
492 			if (lstat(p ? p : _matches[1], &attr) != -1) {
493 				if ((attr.st_mode & S_IFMT) == S_IFDIR) {
494 					append_slash = 1;
495 					suggestion.filetype = DT_DIR;
496 				}
497 
498 				if (suggest_filetype_color) {
499 					_color = get_comp_color(p ? p : _matches[1], attr);
500 					if (_color)
501 						color = _color;
502 				}
503 			} else {
504 				suggestion.filetype = DT_DIR;
505 			}
506 
507 			free(p);
508 
509 			char _tmp[NAME_MAX + 2];
510 			*_tmp = '\0';
511 			if (append_slash)
512 				snprintf(_tmp, NAME_MAX + 2, "%s/", _matches[1]);
513 			char *tmp = escape_str(*_tmp ? _tmp : _matches[1]);
514 
515 			if (c != BS)
516 				suggestion.type = COMP_SUG;
517 
518 			if (tmp) {
519 				print_suggestion(tmp, len, color);
520 				free(tmp);
521 			} else {
522 				print_suggestion(_matches[1], len, color);
523 			}
524 
525 			printed = PARTIAL_MATCH;
526 		}
527 	}
528 
529 FREE:
530 	for (i = 0; _matches[i]; i++)
531 		free(_matches[i]);
532 	free(_matches);
533 
534 	if (free_color) {
535 		free(_color);
536 		free_color = 0;
537 	}
538 
539 	return printed;
540 }
541 
542 static int
543 check_filenames(char *str, size_t len, const unsigned char c,
544 				const int first_word, const size_t full_word)
545 {
546 	int i = (int)files, dot_slash = 0;
547 	char *color = (char *)NULL;
548 
549 	if (len >= 2 && *str == '.' && *(str + 1) == '/') {
550 		dot_slash = 1;
551 		str += 2;
552 		len -= 2;
553 	}
554 
555 	if (len) {
556 		while (str[len - 1] == ' ')
557 			str[--len] = '\0';
558 		if (str[len - 1] == '/')
559 			str[--len] = '\0';
560 	}
561 
562 	if (suggest_filetype_color)
563 		color = no_c;
564 	else
565 		color = sf_c;
566 
567 	while (--i >= 0) {
568 		if (!file_info[i].name || TOUPPER(*str) != TOUPPER(*file_info[i].name))
569 			continue;
570 
571 		if (full_word) {
572 			if ((case_sens_path_comp ? strcmp(str, file_info[i].name)
573 			: strcasecmp(str, file_info[i].name)) == 0)
574 				return FULL_MATCH;
575 			continue;
576 		}
577 
578 		if (len && (case_sens_path_comp ? strncmp(str, file_info[i].name, len)
579 		: strncasecmp(str, file_info[i].name, len)) == 0) {
580 			if (file_info[i].len == len)
581 				return FULL_MATCH;
582 
583 			if (suggest_filetype_color)
584 				color = file_info[i].color;
585 
586 			if (file_info[i].dir) {
587 				if (first_word && !autocd)
588 					continue;
589 
590 				suggestion.filetype = DT_DIR;
591 
592 				char tmp[NAME_MAX + 2];
593 				snprintf(tmp, NAME_MAX + 2, "%s/", file_info[i].name);
594 
595 				if (c != BS)
596 					suggestion.type = FILE_SUG;
597 
598 				char *_tmp = escape_str(tmp);
599 				if (_tmp) {
600 					print_suggestion(_tmp, len, color);
601 					free(_tmp);
602 				} else {
603 					print_suggestion(tmp, len, color);
604 				}
605 			} else {
606 				if (first_word && !auto_open)
607 					continue;
608 
609 				if (c != BS)
610 					suggestion.type = FILE_SUG;
611 				suggestion.filetype = DT_REG;
612 
613 				char *tmp = escape_str(file_info[i].name);
614 				if (tmp) {
615 					if (dot_slash) {
616 						/* Reinsert './', removed to check file name*/
617 						char t[NAME_MAX + 2];
618 						snprintf(t, NAME_MAX + 1, "./%s", tmp);
619 						print_suggestion(t, len + 2, color);
620 					} else {
621 						print_suggestion(tmp, len, color);
622 					}
623 					free(tmp);
624 				} else {
625 					if (dot_slash) {
626 						char t[NAME_MAX + 2];
627 						snprintf(t, NAME_MAX + 1, "./%s", file_info[i].name);
628 						print_suggestion(t, len + 2, color);
629 					} else {
630 						print_suggestion(file_info[i].name, len, color);
631 					}
632 				}
633 			}
634 			return PARTIAL_MATCH;
635 		}
636 	}
637 
638 	return NO_MATCH;
639 }
640 
641 static int
642 check_history(const char *str, const size_t len)
643 {
644 	if (!str || !*str || !len)
645 		return 0;
646 
647 	int i = (int)current_hist_n;
648 	while (--i >= 0) {
649 		if (!history[i] || TOUPPER(*str) != TOUPPER(*history[i]))
650 			continue;
651 
652 		if ((case_sens_path_comp ? strncmp(str, history[i], len)
653 		: strncasecmp(str, history[i], len)) == 0) {
654 			if (strlen(history[i]) > len) {
655 				suggestion.type = HIST_SUG;
656 				print_suggestion(history[i], len, sh_c);
657 				return PARTIAL_MATCH;
658 			}
659 			return FULL_MATCH;
660 		}
661 	}
662 
663 	return NO_MATCH;
664 }
665 
666 static int
667 check_builtins(const char *str, const size_t len, const int print)
668 {
669 	char **b = (char **)NULL;
670 
671 	switch(shell) {
672 	case SHELL_NONE: return NO_MATCH;
673 	case SHELL_BASH: b = bash_builtins; break;
674 	case SHELL_DASH: b = dash_builtins; break;
675 	case SHELL_KSH: b = ksh_builtins; break;
676 	case SHELL_TCSH: b = tcsh_builtins; break;
677 	case SHELL_ZSH: b = zsh_builtins; break;
678 	default: return NO_MATCH;
679 	}
680 
681 	size_t i = 0;
682 	for(; b[i]; i++) {
683 		if (*str != *b[i])
684 			continue;
685 
686 		if (!print) {
687 			if (strcmp(str, b[i]) == 0)
688 				return FULL_MATCH;
689 			continue;
690 		}
691 
692 		if (strncmp(b[i], str, len) != 0)
693 			continue;
694 
695 		size_t blen = strlen(b[i]);
696 		if (blen > len) {
697 			suggestion.type = CMD_SUG;
698 			print_suggestion(b[i], len, sb_c);
699 			return PARTIAL_MATCH;
700 		} else {
701 			return FULL_MATCH;
702 		}
703 	}
704 
705 	return NO_MATCH;
706 }
707 
708 int
709 check_cmds(char *str, const size_t len, const int print)
710 {
711 	if (!len)
712 		return 0;
713 
714 	int i = (int)path_progsn;
715 	while (--i >= 0) {
716 		if (!bin_commands[i] || *str != *bin_commands[i])
717 			continue;
718 
719 		if (!print) {
720 			if (strcmp(str, bin_commands[i]) == 0)
721 				return FULL_MATCH;
722 			continue;
723 		}
724 
725 		if (strncmp(str, bin_commands[i], len) != 0)
726 			continue;
727 
728 		if (is_internal_c(bin_commands[i])) {
729 			if (strlen(bin_commands[i]) > len) {
730 				suggestion.type = CMD_SUG;
731 				print_suggestion(bin_commands[i], len, sx_c);
732 				return PARTIAL_MATCH;
733 			} else {
734 				return FULL_MATCH;
735 			}
736 		} else if (ext_cmd_ok) {
737 			if (strlen(bin_commands[i]) > len) {
738 				suggestion.type = CMD_SUG;
739 				print_suggestion(bin_commands[i], len, sc_c);
740 				return PARTIAL_MATCH;
741 			} else {
742 				return FULL_MATCH;
743 			}
744 		} else {
745 			continue;
746 		}
747 	}
748 
749 	/* Check internal command with fused parameter */
750 	char *p = (char *)NULL;
751 	size_t j = 0;
752 	for (; str[j]; j++) {
753 		if (str[j] >= '1' && str[j] <= '9') {
754 			p = str + j;
755 			break;
756 		}
757 	}
758 
759 	if (!p || p == str)
760 		return check_builtins(str, len, print);
761 
762 	*p = '\0';
763 	if (!is_internal_c(str))
764 		return NO_MATCH;
765 
766 	return FULL_MATCH;
767 }
768 
769 static int
770 check_jumpdb(const char *str, const size_t len, const int print)
771 {
772 	char *color = (char *)NULL;
773 
774 	if (suggest_filetype_color)
775 		color = di_c;
776 	else
777 		color = sf_c;
778 	int i = (int)jump_n;
779 	while (--i >= 0) {
780 		if (!jump_db[i].path || TOUPPER(*str) != TOUPPER(*jump_db[i].path))
781 			continue;
782 
783 		if (!print) {
784 			if ((case_sens_path_comp ? strcmp(str, jump_db[i].path)
785 			: strcasecmp(str, jump_db[i].path)) == 0)
786 				return FULL_MATCH;
787 			continue;
788 		}
789 
790 		size_t db_len = strlen(jump_db[i].path);
791 
792 		if (len && (case_sens_path_comp ? strncmp(str, jump_db[i].path, len)
793 		: strncasecmp(str, jump_db[i].path, len)) == 0) {
794 			if (db_len > len) {
795 				suggestion.type = FILE_SUG;
796 				suggestion.filetype = DT_DIR;
797 				char tmp[PATH_MAX + 2];
798 				*tmp = '\0';
799 				if (jump_db[i].path[db_len - 1] != '/')
800 					snprintf(tmp, PATH_MAX + 2, "%s/", jump_db[i].path);
801 				print_suggestion(*tmp ? tmp : jump_db[i].path, len, color);
802 				return PARTIAL_MATCH;
803 			}
804 			return FULL_MATCH;
805 		}
806 	}
807 
808 	return NO_MATCH;
809 }
810 
811 static int
812 check_bookmarks(const char *str, const size_t len, const int print)
813 {
814 	if (!bm_n)
815 		return 0;
816 
817 	char *color = (char *)NULL;
818 	struct stat attr;
819 	if (!suggest_filetype_color)
820 		color = sf_c;
821 
822 	int i = (int)bm_n;
823 	while (--i >= 0) {
824 		if (!bookmarks[i].name || TOUPPER(*str) != TOUPPER(*bookmarks[i].name))
825 			continue;
826 
827 		if (!print) {
828 			if ((case_sens_path_comp ? strcmp(str, bookmarks[i].name)
829 			: strcasecmp(str, bookmarks[i].name)) == 0)
830 				return FULL_MATCH;
831 			continue;
832 		}
833 
834 		if (len && (case_sens_path_comp ? strncmp(str, bookmarks[i].name, len)
835 		: strncasecmp(str, bookmarks[i].name, len)) == 0) {
836 			if (lstat(bookmarks[i].path, &attr) == -1)
837 				continue;
838 
839 			else if ((attr.st_mode & S_IFMT) == S_IFDIR) {
840 				suggestion.type = BOOKMARK_SUG;
841 				suggestion.filetype = DT_DIR;
842 
843 				char tmp[PATH_MAX + 2];
844 				size_t path_len = strlen(bookmarks[i].path);
845 				if (bookmarks[i].path[path_len - 1] != '/')
846 					snprintf(tmp, PATH_MAX + 2, "%s/", bookmarks[i].path);
847 				else
848 					xstrsncpy(tmp, bookmarks[i].path, PATH_MAX + 2);
849 
850 				if (suggest_filetype_color)
851 					color = di_c;
852 
853 				char *_tmp = escape_str(tmp);
854 				print_suggestion(_tmp ? _tmp : tmp, 1, color);
855 				if (_tmp)
856 					free(_tmp);
857 			} else {
858 				suggestion.type = BOOKMARK_SUG;
859 				suggestion.filetype = DT_REG;
860 
861 				if (suggest_filetype_color)
862 					color = get_comp_color(bookmarks[i].path, attr);
863 
864 				char *_tmp = escape_str(bookmarks[i].path);
865 				print_suggestion(_tmp ? _tmp : bookmarks[i].path, 1, color);
866 				if (_tmp)
867 					free(_tmp);
868 			}
869 			return PARTIAL_MATCH;
870 		}
871 	}
872 
873 	return NO_MATCH;
874 }
875 
876 static int
877 check_int_params(const char *str, const size_t len)
878 {
879 	size_t i;
880 	for (i = 0; param_str[i]; i++) {
881 		if (*str != *param_str[i])
882 			continue;
883 		if (len && strncmp(str, param_str[i], len) == 0
884 		&& strlen(param_str[i]) > len) {
885 			suggestion.type = INT_CMD;
886 			print_suggestion(param_str[i], len, sx_c);
887 			return PARTIAL_MATCH;
888 		}
889 	}
890 
891 	return NO_MATCH;
892 }
893 
894 static int
895 check_eln(const char *str, const int print)
896 {
897 	if (!str || !*str)
898 		return NO_MATCH;
899 
900 	int n = atoi(str);
901 	if (n < 1 || n > (int)files || !file_info[n - 1].name)
902 		return NO_MATCH;
903 
904 	if (!print)
905 		return FULL_MATCH;
906 
907 	n--;
908 	char *color = sf_c;
909 	suggestion.type = ELN_SUG;
910 	if (suggest_filetype_color)
911 		color = file_info[n].color;
912 
913 	char tmp[NAME_MAX + 1];
914 	*tmp = '\0';
915 	if (file_info[n].dir) {
916 		snprintf(tmp, NAME_MAX + 1, "%s/", file_info[n].name);
917 		suggestion.filetype = DT_DIR;
918 	} else {
919 		suggestion.filetype = DT_REG;
920 	}
921 
922 	print_suggestion(!*tmp ? file_info[n].name : tmp, 0, color);
923 	return PARTIAL_MATCH;
924 }
925 
926 static int
927 check_aliases(const char *str, const size_t len, const int print)
928 {
929 	if (!aliases_n)
930 		return NO_MATCH;
931 
932 	char *color = sc_c;
933 
934 	int i = (int)aliases_n;
935 	while (--i >= 0) {
936 		if (!aliases[i].name)
937 			continue;
938 		char *p = aliases[i].name;
939 		if (TOUPPER(*p) != TOUPPER(*str))
940 			continue;
941 
942 		if (!print) {
943 			if ((case_sens_path_comp ? strcmp(p, str)
944 			: strcasecmp(p, str)) == 0)
945 				return FULL_MATCH;
946 			continue;
947 		}
948 
949 		if ((case_sens_path_comp ? strncmp(p, str, len)
950 		: strncasecmp(p, str, len)) != 0)
951 			continue;
952 		if (!aliases[i].cmd || !*aliases[i].cmd)
953 			continue;
954 
955 		suggestion.type = ALIAS_SUG;
956 		print_suggestion(aliases[i].cmd, 1, color);
957 		return PARTIAL_MATCH;
958 	}
959 
960 	return NO_MATCH;
961 }
962 
963 /* Get a match from the jump database and print the suggestion */
964 static int
965 check_jcmd(char *line)
966 {
967 	if (suggestion_buf)
968 		clear_suggestion(CS_FREEBUF);
969 
970 	/* Split line into an array of substrings */
971 	char **substr = get_substr(line, ' ');
972 	if (!substr)
973 		return NO_MATCH;
974 
975 	/* Check the jump database for a match. If a match is found, it will
976 	 * be stored in jump_suggestion (global) */
977 	dirjump(substr, SUG_JUMP);
978 
979 	size_t i;
980 	for (i = 0; substr[i]; i++)
981 		free(substr[i]);
982 	free(substr);
983 
984 	if (!jump_suggestion)
985 		return NO_MATCH;
986 
987 	suggestion.type = JCMD_SUG;
988 	suggestion.filetype = DT_DIR;
989 	if (!autocd) {
990 		char *tmp = xnmalloc(strlen(jump_suggestion) + 4, sizeof(char));
991 		sprintf(tmp, "cd %s", jump_suggestion);
992 		print_suggestion(tmp, 1, suggest_filetype_color ? di_c : sf_c);
993 		suggestion.type = JCMD_SUG_NOACD;
994 		free(tmp);
995 	} else {
996 		print_suggestion(jump_suggestion, 1, suggest_filetype_color ? di_c
997 					: sf_c);
998 	}
999 //	suggestion.offset = 0;
1000 	free(jump_suggestion);
1001 	jump_suggestion = (char *)NULL;
1002 	return 1;
1003 }
1004 
1005 /* Check if we must suggest --help for internal commands */
1006 static int
1007 check_help(char *full_line, const char *_last_word)
1008 {
1009 	size_t len = strlen(_last_word);
1010 	if (strncmp(_last_word, "--help", len) != 0)
1011 		return NO_MATCH;
1012 
1013 	char *ret = strchr(full_line, ' ');
1014 	if (!ret)
1015 		return NO_MATCH;
1016 
1017 	*ret = '\0';
1018 	int retval = is_internal_c(full_line);
1019 	*ret = ' ';
1020 
1021 	if (!retval)
1022 		return NO_MATCH;
1023 
1024 	suggestion.type = CMD_SUG;
1025 	print_suggestion("--help", len, sx_c);
1026 	return PARTIAL_MATCH;
1027 }
1028 
1029 static int
1030 check_variables(const char *str, const size_t len)
1031 {
1032 	int printed = NO_MATCH;
1033 	size_t i;
1034 	for (i = 0; environ[i]; i++) {
1035 		if (TOUPPER(*environ[i]) == TOUPPER(*str)
1036 		&& strncasecmp(str, environ[i], len) == 0) {
1037 			char *ret = strchr(environ[i], '=');
1038 			*ret = '\0';
1039 			suggestion.type = VAR_SUG;
1040 			char t[NAME_MAX + 1];
1041 			snprintf(t, NAME_MAX + 1, "$%s", environ[i]);
1042 			print_suggestion(t, len + 1, sh_c);
1043 			printed = PARTIAL_MATCH;
1044 			*ret = '=';
1045 			break;
1046 		}
1047 	}
1048 
1049 	if (printed)
1050 		return PARTIAL_MATCH;
1051 
1052 	if (!usrvar_n)
1053 		return NO_MATCH;
1054 
1055 	for (i = 0; usr_var[i].name; i++) {
1056 		if (TOUPPER(*str) == TOUPPER(*usr_var[i].name)
1057 		&& strncasecmp(str, usr_var[i].name, len) == 0) {
1058 			suggestion.type = CMD_SUG;
1059 			char t[NAME_MAX + 1];
1060 			snprintf(t, NAME_MAX + 1, "$%s", usr_var[i].name);
1061 			print_suggestion(t, len + 1, sh_c);
1062 			printed = 1;
1063 			break;
1064 		}
1065 	}
1066 
1067 	if (printed)
1068 		return PARTIAL_MATCH;
1069 
1070 	return NO_MATCH;
1071 }
1072 
1073 static char*
1074 get_last_word(const char *last_space, size_t buflen)
1075 {
1076 	if (last_space) {
1077 		char *rl = rl_line_buffer;
1078 //		int j = (int)buflen;
1079 		int j = rl_end;
1080 		while (--j >= 0) {
1081 			if (j + 1 && rl[j] == ' ' && rl[j + 1] && rl[j + 1] != ' ')
1082 //			if (rl_line_buffer[j] == ' ')
1083 				break;
1084 		}
1085 		last_word_offset = j + 1;
1086 //		printf("'%d'", last_word_offset);
1087 		buflen = strlen(last_space);
1088 
1089 		if (*(++last_space)) {
1090 			buflen--;
1091 			last_word = (char *)xrealloc(last_word, (buflen + 2) * sizeof(char));
1092 			strcpy(last_word, last_space);
1093 		} else {
1094 			last_word = (char *)xrealloc(last_word, 2 * sizeof(char));
1095 			*last_word = '\0';
1096 		}
1097 	} else {
1098 		last_word = (char *)xrealloc(last_word, (buflen + 2) * sizeof(char));
1099 		strcpy(last_word, rl_line_buffer);
1100 	}
1101 
1102 	return last_word;
1103 }
1104 
1105 static int
1106 is_last_word(void)
1107 {
1108 	int lw = 1;
1109 
1110 	if (rl_point >= rl_end)
1111 		return lw;
1112 
1113 	char *p = strchr(rl_line_buffer + rl_point, ' ');
1114 	if (!p)
1115 		return lw;
1116 
1117 	while (*(++p)) {
1118 		if (*p != ' ') {
1119 			lw = 0;
1120 			break;
1121 		}
1122 	}
1123 
1124 	return lw;
1125 }
1126 
1127 static size_t
1128 count_words(size_t *start_word, size_t *full_word)
1129 {
1130 	rl_last_word_start = 0;
1131 	size_t words = 0, w = 0, first_non_space = 0;
1132 	char q = 0;
1133 	char *b = rl_line_buffer;
1134 	for (; b[w]; w++) {
1135 		/* Keep track of open quotes */
1136 		if (b[w] == '\'' || b[w] == '"')
1137 			q = q == b[w] ? 0 : b[w];
1138 
1139 		if (!first_non_space && b[w] != ' ') {
1140 			words = 1;
1141 			*start_word = w;
1142 			first_non_space = 1;
1143 			continue;
1144 		}
1145 		if (w && b[w] == ' ' && b[w - 1] != '\\') {
1146 			if (b[w + 1] && b[w + 1] != ' ')
1147 				rl_last_word_start = (int)w + (b + 1 ? 1 : 0);
1148 			if (!*full_word && b[w - 1] != '|'
1149 			&& b[w - 1] != ';' && b[w - 1] != '&')
1150 				*full_word = w; /* Index of the end of the first full word (cmd) */
1151 			if (b[w + 1] && b[w + 1] != ' ') {
1152 				words++;
1153 			}
1154 		}
1155 		/* If a process separator char is found, reset variables so that we
1156 		 * can start counting again for the new command */
1157 		if (!q && cur_color != hq_c && w && b[w - 1] != '\\'
1158 		&& ((b[w] == '&' && b[w - 1] == '&') || b[w] == '|' || b[w] == ';')) {
1159 			words = first_non_space = *full_word = 0;
1160 		}
1161 	}
1162 
1163 	return words;
1164 }
1165 
1166 static void
1167 print_warning_prompt(const char c)
1168 {
1169 	if (warning_prompt == 1 && !wrong_cmd
1170 	&& c != ';' && c != ':' && c != '#'
1171 	&& c != '$' && c != '\'' && c != '"') {
1172 		if (suggestion.printed)
1173 			clear_suggestion(CS_FREEBUF);
1174 		wrong_cmd = 1;
1175 
1176 		rl_save_prompt();
1177 
1178 		/* Print the warning prompt */
1179 		char tprompt[PATH_MAX];
1180 		snprintf(tprompt, PATH_MAX, "\1%s\2%s", wp_c, wprompt_str);
1181 		rl_set_prompt(tprompt);
1182 	}
1183 }
1184 
1185 /* Check for available suggestions. Returns zero if true, one if not,
1186  * and -1 if C was inserted before the end of the current line.
1187  * If a suggestion is found, it will be printed by print_suggestion() */
1188 int
1189 rl_suggestions(const unsigned char c)
1190 {
1191 	int printed = 0, zero_offset = 0;
1192 	last_word_offset = 0;
1193 
1194 	if (rl_end == 0 && rl_point == 0) {
1195 		free(suggestion_buf);
1196 		suggestion_buf = (char *)NULL;
1197 		if (wrong_cmd)
1198 			recover_from_wrong_cmd();
1199 		return EXIT_SUCCESS;
1200 	}
1201 
1202 	/* If we are not at the end of the input string, make sure we are
1203 	 * at the last word: we only suggest stuff for the last entered
1204 	 * word */
1205 /*	int lw = is_last_word();
1206 	if (!lw)
1207 		return EXIT_SUCCESS; */
1208 
1209 	size_t buflen = (size_t)rl_end;
1210 	suggestion.full_line_len = buflen + 1;
1211 	char *last_space = strrchr(rl_line_buffer, ' ');
1212 	if (last_space && last_space != rl_line_buffer
1213 	&& *(last_space - 1) == '\\')
1214 		last_space = (char *)NULL;
1215 
1216 	/* Reset the wrong cmd flag whenever we have a new word or a new line */
1217 	if (rl_end == 0 || c == '\n') {
1218 		if (wrong_cmd)
1219 			recover_from_wrong_cmd();
1220 		wrong_cmd_line = 0;
1221 	}
1222 
1223 	/* We need a copy of the complete line */
1224 	char *full_line = rl_line_buffer;
1225 
1226 	/* A copy of the last entered word */
1227 	last_word = get_last_word(last_space, buflen);
1228 
1229 	/* Count words */
1230 	size_t full_word = 0, start_word = 0;
1231 	nwords = count_words(&start_word, &full_word);
1232 
1233 	/* And a copy of the first word as well */
1234 	char *first_word = (char *)NULL;
1235 	if (full_word) {
1236 		rl_line_buffer[full_word] = '\0';
1237 		char *q = rl_line_buffer + start_word;
1238 		first_word = savestring(q, strlen(q));
1239 		rl_line_buffer[full_word] = ' ';
1240 	}
1241 
1242 	char *word = (nwords == 1 && c != ' ' && first_word) ? first_word : last_word;
1243 	size_t wlen = strlen(word);
1244 
1245 	/* If more than one word and the cursor is on the first word,
1246 	 * jump to the check command name section */
1247 	int point_is_first_word = 0;
1248 	if (nwords >= 2 && rl_point <= (int)full_word + 1) {
1249 		point_is_first_word = 1;
1250 		goto CHECK_CMD;
1251 	}
1252 
1253 	/* If not on the first word and not at the end of the last word,
1254 	 * do nothing */
1255 	int lw = is_last_word();
1256 	if (!lw)
1257 		goto SUCCESS;
1258 
1259 		/* ######################################
1260 		 * #	    Search for suggestions		#
1261 		 * ######################################*/
1262 
1263 	char *lb = rl_line_buffer;
1264 	/* 3.a) Let's suggest non-fixed parameters for internal commands */
1265 
1266 	switch(*lb) {
1267 	case 'b': /* Bookmarks names */
1268 		if (lb[1] == 'm' && lb[2] == ' ' && strncmp(lb + 3, "add", 3) != 0) {
1269 			size_t i = 0;
1270 			for (; bookmark_names[i]; i++) {
1271 				if (*word == *bookmark_names[i]
1272 				&& strncmp(bookmark_names[i], word, wlen) == 0) {
1273 					suggestion.type = CMD_SUG;
1274 //					suggestion.offset = last_word_offset;
1275 					print_suggestion(bookmark_names[i], wlen, sx_c);
1276 					printed = 1;
1277 					break;
1278 				}
1279 			}
1280 			if (printed) {
1281 				goto SUCCESS;
1282 			}
1283 		}
1284 		break;
1285 
1286 	case 'c': /* Color schemes */
1287 		if (lb[1] == 's' && lb[2] == ' ') {
1288 			size_t i = 0;
1289 			for (; color_schemes[i]; i++) {
1290 				if (*last_word == *color_schemes[i]
1291 				&& strncmp(color_schemes[i], word, wlen) == 0) {
1292 					suggestion.type = CMD_SUG;
1293 //					suggestion.offset = last_word_offset;
1294 					print_suggestion(color_schemes[i], wlen, sx_c);
1295 					printed = 1;
1296 					break;
1297 				}
1298 			}
1299 			if (printed) {
1300 				goto SUCCESS;
1301 			}
1302 		}
1303 		break;
1304 
1305 	case 'j': /* j command */
1306 		if (lb[1] == ' '  || ((lb[1] == 'c'	|| lb[1] == 'o'
1307 		|| lb[1] == 'p') && lb[2] == ' ')) {
1308 			printed = check_jcmd(full_line);
1309 			if (printed) {
1310 				zero_offset = 1;
1311 				goto SUCCESS;
1312 			} else {
1313 				goto FAIL;
1314 			}
1315 		}
1316 		break;
1317 
1318 	case 'n': /* Remotes */
1319 		if (lb[1] == 'e' && lb[2] == 't' && lb[3] == ' ') {
1320 			size_t i = 0;
1321 			for (; remotes[i].name; i++) {
1322 				if (*word == *remotes[i].name
1323 				&& strncmp(remotes[i].name, word, wlen) == 0) {
1324 					suggestion.type = CMD_SUG;
1325 //					suggestion.offset = last_word_offset;
1326 					print_suggestion(remotes[i].name, wlen, sx_c);
1327 					printed = 1;
1328 					break;
1329 				}
1330 			}
1331 			if (printed)
1332 				goto SUCCESS;
1333 		}
1334 		break;
1335 
1336 	case 'p': /* Profiles */
1337 		if (lb[1] == 'f' && lb[2] == ' ' && (strncmp(lb + 3, "set", 3) == 0
1338 		|| strncmp(lb + 3, "del", 3) == 0)) {
1339 			size_t i = 0;
1340 			for (; profile_names[i]; i++) {
1341 				if (*word == *profile_names[i]
1342 				&& strncmp(profile_names[i], word, wlen) == 0) {
1343 					suggestion.type = CMD_SUG;
1344 //					suggestion.offset = last_word_offset;
1345 					print_suggestion(profile_names[i], wlen, sx_c);
1346 					printed = 1;
1347 					break;
1348 				}
1349 			}
1350 			if (printed) {
1351 				goto SUCCESS;
1352 			} else {
1353 				goto FAIL;
1354 			}
1355 		}
1356 		break;
1357 
1358 	default: break;
1359 	}
1360 
1361 	/* 3.b) Check already suggested string */
1362 	if (suggestion_buf && suggestion.printed && !_ISDIGIT(c)
1363 	&& strncmp(full_line, suggestion_buf, (size_t)rl_end) == 0) {
1364 		printed = zero_offset = 1;
1365 //		suggestion.offset = 0;
1366 		goto SUCCESS;
1367 	}
1368 
1369 	/* 3.c) Check CliFM internal parameters */
1370 	char *ret = strchr(full_line, ' ');
1371 	if (ret) {
1372 		/* 3.c.1) Suggest the sel keyword only if not first word */
1373 		if (*word == 's' && strncmp(word, "sel", wlen) == 0) {
1374 			suggestion.type = SEL_SUG;
1375 //			suggestion.offset = last_word_offset;
1376 			printed = 1;
1377 			print_suggestion("sel", wlen, sx_c);
1378 			goto SUCCESS;
1379 		}
1380 
1381 		/* 3.c.2) Check commands fixed parameters */
1382 		printed = check_int_params(full_line, (size_t)rl_end);
1383 		if (printed) {
1384 			zero_offset = 1;
1385 //			suggestion.offset = 0;
1386 			goto SUCCESS;
1387 		}
1388 	}
1389 
1390 	/* 3.c.3) Let's suggest --help for internal commands */
1391 	if (*word == '-') {
1392 		printed = check_help(full_line, word);
1393 		if (printed) {
1394 //			suggestion.offset = last_word_offset;
1395 			goto SUCCESS;
1396 		}
1397 	}
1398 
1399 	/* If more than one word and the cursor is on the first word,
1400 	 * jump to the check command name section */
1401 /*	int point_is_first_word = 0;
1402 	if (nwords >= 2 && rl_point <= (int)full_word + 1) {
1403 		point_is_first_word = 1;
1404 		goto CHECK_CMD;
1405 	} */
1406 
1407 	/* 3.d) Execute the following check in the order specified by
1408 	 * suggestion_strategy (the value is taken form the configuration
1409 	 * file) */
1410 	size_t st = 0;
1411 	int flag = 0;
1412 
1413 	for (; st < SUG_STRATS; st++) {
1414 		switch(suggestion_strategy[st]) {
1415 
1416 		case 'a': /* 3.d.1) Aliases */
1417 			flag = c == ' ' ? CHECK_MATCH : PRINT_MATCH;
1418 			if (flag == CHECK_MATCH && suggestion.printed)
1419 				clear_suggestion(CS_FREEBUF);
1420 
1421 			printed = check_aliases(word, wlen, flag);
1422 			if (printed) {
1423 //				suggestion.offset = last_word_offset;
1424 				goto SUCCESS;
1425 			}
1426 			break;
1427 
1428 		case 'b': /* 3.d.2) Bookmarks */
1429 			if (last_space || autocd || auto_open) {
1430 				flag = c == ' ' ? CHECK_MATCH : PRINT_MATCH;
1431 				if (flag == CHECK_MATCH && suggestion.printed)
1432 					clear_suggestion(CS_FREEBUF);
1433 
1434 				printed = check_bookmarks(word, wlen, flag);
1435 				if (printed) {
1436 //					suggestion.offset = last_word_offset;
1437 					goto SUCCESS;
1438 				}
1439 			}
1440 			break;
1441 
1442 		case 'c': /* 3.d.3) Possible completions */
1443 			if (last_space || autocd || auto_open) {
1444 				if (nwords == 1) {
1445 					word = first_word ? first_word : last_word;
1446 					wlen = strlen(word);
1447 				}
1448 				if (wlen && word[wlen - 1] == ' ')
1449 					word[wlen - 1] = '\0';
1450 
1451 				flag = c == ' ' ? CHECK_MATCH : PRINT_MATCH;
1452 
1453 				printed = check_completions(word, wlen, c, flag);
1454 
1455 				if (printed) {
1456 					if (flag == CHECK_MATCH) {
1457 						if (printed == FULL_MATCH) {
1458 //							if (suggestion.printed)
1459 //								clear_suggestion(CS_FREEBUF);
1460 							goto SUCCESS;
1461 						}
1462 					} else {
1463 //						suggestion.offset = last_word_offset;
1464 						goto SUCCESS;
1465 					}
1466 				}
1467 			}
1468 			break;
1469 
1470 		case 'e': /* 3.d.4) ELN's */
1471 			if (nwords == 1 && first_word) {
1472 				word = first_word;
1473 				wlen = strlen(word);
1474 			}
1475 
1476 			if (wlen == 0)
1477 				break;
1478 
1479 			int nlen = (int)wlen;
1480 			while (word[nlen - 1] == ' ')
1481 				word[--nlen] = '\0';
1482 
1483 			flag = c == ' ' ? CHECK_MATCH : PRINT_MATCH;
1484 
1485 			if (flag == CHECK_MATCH && suggestion.printed)
1486 				clear_suggestion(CS_FREEBUF);
1487 
1488 			if (*word >= '1' && *word <= '9' && is_number(word)) {
1489 				printed = check_eln(word, flag);
1490 
1491 				if (printed) {
1492 //					suggestion.offset = last_word_offset;
1493 					goto SUCCESS;
1494 				}
1495 			}
1496 			break;
1497 
1498 		case 'f': /* 3.d.5) File names in CWD */
1499 			/* Do not check dirs and filenames if first word and
1500 			 * neither autocd nor auto-open are enabled */
1501 			if (last_space || autocd || auto_open) {
1502 				if (nwords == 1) {
1503 					word = (first_word && *first_word) ? first_word : last_word;
1504 					wlen = strlen(word);
1505 				}
1506 				if (wlen && word[wlen - 1] == ' ')
1507 					word[wlen - 1] = '\0';
1508 
1509 				if (c == ' ' && suggestion.printed)
1510 					clear_suggestion(CS_FREEBUF);
1511 
1512 				printed = check_filenames(word, wlen,
1513 							c, last_space ? 0 : 1, full_word);
1514 
1515 				if (printed) {
1516 //					suggestion.offset = last_word_offset;
1517 					goto SUCCESS;
1518 				}
1519 			}
1520 			break;
1521 
1522 		case 'h': /* 3.d.6) Commands history */
1523 			printed = check_history(full_line, (size_t)rl_end);
1524 			if (printed) {
1525 				zero_offset = 1;
1526 //				suggestion.offset = 0;
1527 				goto SUCCESS;
1528 			}
1529 			break;
1530 
1531 		case 'j': /* 3.d.7) Jump database */
1532 			/* We don't care about auto-open here: the jump function
1533 			 * deals with directories only */
1534 			if (last_space || autocd) {
1535 				if (nwords == 1) {
1536 					word = (first_word && *first_word) ? first_word : last_word;
1537 					wlen = strlen(word);
1538 				}
1539 				if (wlen && word[wlen - 1] == ' ')
1540 					word[wlen - 1] = '\0';
1541 
1542 				flag = (c == ' ' || full_word) ? CHECK_MATCH : PRINT_MATCH;
1543 
1544 				if (flag == CHECK_MATCH && suggestion.printed)
1545 					clear_suggestion(CS_FREEBUF);
1546 
1547 				printed = check_jumpdb(word, wlen, flag);
1548 
1549 				if (printed) {
1550 //					suggestion.offset = last_word_offset;
1551 					goto SUCCESS;
1552 				}
1553 			}
1554 			break;
1555 
1556 		case '-': break; /* Ignore check */
1557 
1558 		default: break;
1559 		}
1560 	}
1561 
1562 	/* 3.e) Variable names, both environment and internal */
1563 	if (*word == '$') {
1564 		printed = check_variables(word + 1, wlen - 1);
1565 		if (printed) {
1566 //			suggestion.offset = last_word_offset;
1567 			goto SUCCESS;
1568 		}
1569 	}
1570 
1571 	/* 3.f) Check commands in PATH and CliFM internals commands, but
1572 	 * only for the first word */
1573 
1574 	if (nwords >= 2)
1575 		goto NO_SUGGESTION;
1576 
1577 /*	if (nwords >= 2) {
1578 		if (rl_point == rl_end)
1579 			goto NO_SUGGESTION;
1580 		if (rl_point > (int)full_word + 1)
1581 			goto NO_SUGGESTION;
1582 	} */
1583 
1584 CHECK_CMD:
1585 	word = first_word ? first_word : last_word;
1586 	if (!word || !*word || (c == ' ' && (*word == '\''
1587 	|| *word == '"' || *word == '$' || *word == '#')) || *word == '<'
1588 	|| *word == '>' || *word == '!' || *word == '{' || *word == '['
1589 	|| *word == '(' || strchr(word, '=') || *rl_line_buffer == ' '
1590 	|| *word == '|' || *word == ';' || *word == '&') {
1591 		if (suggestion.printed && suggestion_buf)
1592 			clear_suggestion(CS_FREEBUF);
1593 		goto SUCCESS;
1594 	}
1595 
1596 	/* If absolute path */
1597 	if (point_is_first_word && *word == '/' && access(word, X_OK) == 0) {
1598 		printed = 1;
1599 	} else {
1600 		wlen = strlen(word);
1601 
1602 		if (wlen && word[wlen - 1] == ' ')
1603 			word[wlen - 1] = '\0';
1604 
1605 		flag = (c == ' ' || full_word) ? CHECK_MATCH : PRINT_MATCH;
1606 		printed = check_cmds(word, wlen, flag);
1607 	}
1608 
1609 	if (printed) {
1610 		if (wrong_cmd && (nwords == 1 || point_is_first_word)) {
1611 			rl_dispatching = 1;
1612 			recover_from_wrong_cmd();
1613 			rl_dispatching = 0;
1614 		}
1615 //		suggestion.offset = last_word_offset;
1616 		goto SUCCESS;
1617 
1618 	/* Let's suppose that two slashes do not constitue a search
1619 	 * expression */
1620 	} else if (*word != '/' || strchr(word + 1, '/')) {
1621 	/* There's no suggestion nor any command name matching the
1622 	 * first entered word. So, we assume we have an invalid
1623 	 * command name. Switch to the warning prompt to warn the
1624 	 * user */
1625 		print_warning_prompt(*word);
1626 	}
1627 
1628 NO_SUGGESTION:
1629 	/* No suggestion found */
1630 
1631 	/* Clear current suggestion, if any, only if no escape char is contained
1632 	 * in the current input sequence. This is mainly to avoid erasing
1633 	 * the suggestion if moving thought the text via the arrow keys */
1634 	if (suggestion.printed) {
1635 		if (!strchr(word, '\x1b')) {
1636 			clear_suggestion(CS_FREEBUF);
1637 			goto FAIL;
1638 		} else {
1639 			/* Go to SUCCESS to avoid removing the suggestion buffer */
1640 			printed = 1;
1641 			goto SUCCESS;
1642 		}
1643 	}
1644 
1645 SUCCESS:
1646 	if (printed) {
1647 		suggestion.offset = zero_offset ? 0 : last_word_offset;
1648 
1649 		if (printed == FULL_MATCH && suggestion_buf) {
1650 			clear_suggestion(CS_FREEBUF);
1651 		}
1652 
1653 		if (wrong_cmd && nwords == 1) {
1654 			rl_dispatching = 1;
1655 			recover_from_wrong_cmd();
1656 			rl_dispatching = 0;
1657 		}
1658 
1659 		fputs("\x1b[0m", stdout);
1660 		suggestion.printed = 1;
1661 		/* Restore color */
1662 		if (!wrong_cmd) {
1663 			fputs(cur_color ? cur_color : tx_c, stdout);
1664 		} else {
1665 			fputs(wp_c, stdout);
1666 		}
1667 	} else {
1668 		if (wrong_cmd) {
1669 			fputs("\x1b[0m", stdout);
1670 			fputs(wp_c, stdout);
1671 		}
1672 		suggestion.printed = 0;
1673 	}
1674 	free(first_word);
1675 	free(last_word);
1676 	last_word = (char *)NULL;
1677 	return EXIT_SUCCESS;
1678 
1679 FAIL:
1680 	suggestion.printed = 0;
1681 	free(first_word);
1682 	free(last_word);
1683 	last_word = (char *)NULL;
1684 	free(suggestion_buf);
1685 	suggestion_buf = (char *)NULL;
1686 	return EXIT_FAILURE;
1687 }
1688 #else
1689 void *__skip_me_suggestions;
1690 #endif /* _NO_SUGGESTIONS */
1691