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