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