1 #include "helpers.h"
2
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <sys/stat.h>
6
7 #ifdef __OpenBSD__
8 typedef char *rl_cpvfunc_t;
9 #include <ereadline/readline/readline.h>
10 #else
11 #include <readline/readline.h>
12 #endif
13 #include <errno.h>
14 #include <fcntl.h>
15
16 //#include <curses.h>
17
18 #include "exec.h"
19 #include "aux.h"
20 #include "misc.h"
21 #include "checks.h"
22 #include "strings.h"
23 #include "colors.h"
24 #include "navigation.h"
25
26 #ifndef _NO_HIGHLIGHT
27 #include "highlight.h"
28 #endif
29
30 #ifndef _NO_SUGGESTIONS
31 #include "suggestions.h"
32 #endif
33
34 #define PUTX(c) \
35 if (CTRL_CHAR(c)) { \
36 putc('^', rl_outstream); \
37 putc(UNCTRL(c), rl_outstream); \
38 } else if (c == RUBOUT) { \
39 putc('^', rl_outstream); \
40 putc('?', rl_outstream); \
41 } else \
42 putc(c, rl_outstream)
43
44 /* Return the character which best describes FILENAME.
45 `@' for symbolic links
46 `/' for directories
47 `*' for executables
48 `=' for sockets */
49 static int
stat_char(char * filename)50 stat_char(char *filename)
51 {
52 struct stat attr;
53 int r;
54
55 #if defined(S_ISLNK)
56 r = lstat(filename, &attr);
57 #else
58 r = stat(filename, &attr);
59 #endif
60
61 if (r == -1)
62 return 0;
63
64 int c = 0;
65 if (S_ISDIR(attr.st_mode)) {
66 c = '/';
67 #if defined(S_ISLNK)
68 } else if (S_ISLNK(attr.st_mode)) {
69 c = '@';
70 #endif /* S_ISLNK */
71 #if defined(S_ISSOCK)
72 } else if (S_ISSOCK(attr.st_mode)) {
73 c = '=';
74 #endif /* S_ISSOCK */
75 } else if (S_ISREG(attr.st_mode)) {
76 if (access(filename, X_OK) == 0)
77 c = '*';
78 #if defined(S_ISFIFO)
79 } else if (S_ISFIFO(attr.st_mode)) {
80 c = '|';
81 #endif /* S_ISFIFO */
82 /*#if defined(S_ISBLK)
83 } else if (S_ISBLK(attr.st_mode)) {
84 c = '%';
85 #endif
86 #if defined(S_ISCHR)
87 } else if (S_ISCHR(attr.st_mode)) {
88 c = '&';
89 #endif */
90 }
91
92 return c;
93 }
94
95 /* Stupid comparison routine for qsort () ing strings. */
96 /*static int
97 compare_strings(const void *s1, const void *s2)
98 {
99 int result;
100 char *ss1 = (char *)s1, *ss2 = (char *)s2;
101
102 result = (int)(ss1 - ss2);
103 if (result == 0)
104 result = strcmp(s1, s2);
105
106 return result;
107 } */
108
109 /* The user must press "y" or "n". Non-zero return means "y" pressed. */
110 static int
get_y_or_n(void)111 get_y_or_n(void)
112 {
113 for (;;) {
114 int c = fgetc(stdin);
115 if (c == 'y' || c == 'Y' || c == ' ')
116 return (1);
117 if (c == 'n' || c == 'N' || c == RUBOUT) {
118 putchar('\n');
119 return (0);
120 }
121 if (c == ABORT_CHAR)
122 rl_abort(0, 0);
123 rl_ding();
124 }
125 }
126
127 static int
print_filename(char * to_print,char * full_pathname)128 print_filename(char *to_print, char *full_pathname)
129 {
130 char *s;
131
132 if (colorize && (cur_comp_type == TCMP_PATH || cur_comp_type == TCMP_SEL
133 || cur_comp_type == TCMP_DESEL || cur_comp_type == TCMP_RANGES)) {
134 colors_list(to_print, 0, 0, 0);
135 } else {
136 for (s = to_print + tab_offset; *s; s++) {
137 PUTX(*s);
138 }
139 }
140
141 /* int rl_visible_stats = 1; */
142 if (rl_filename_completion_desired && !colorize) {
143 if (cur_comp_type == TCMP_CMD) {
144 putc('*', rl_outstream);
145 return 1;
146 }
147 /* If to_print != full_pathname, to_print is the basename of the
148 path passed. In this case, we try to expand the directory
149 name before checking for the stat character. */
150 int extension_char = 0;
151 if (to_print != full_pathname) {
152 /* Terminate the directory name. */
153 char c = to_print[-1];
154 to_print[-1] = '\0';
155
156 s = tilde_expand(full_pathname);
157 if (rl_directory_completion_hook)
158 (*rl_directory_completion_hook) (&s);
159
160 size_t slen = strlen(s);
161 size_t tlen = strlen(to_print);
162 char *new_full_pathname = (char *)xnmalloc(slen + tlen + 2, sizeof(char));
163 strcpy(new_full_pathname, s);
164 new_full_pathname[slen] = '/';
165 strcpy(new_full_pathname + slen + 1, to_print);
166
167 extension_char = stat_char(new_full_pathname);
168
169 free(new_full_pathname);
170 to_print[-1] = c;
171 } else {
172 s = tilde_expand(full_pathname);
173 extension_char = stat_char(s);
174 }
175
176 free(s);
177 if (extension_char)
178 putc(extension_char, rl_outstream);
179 return (extension_char != 0);
180 } else {
181 return 0;
182 }
183 }
184
185 /* Return the portion of PATHNAME that should be output when listing
186 possible completions. If we are hacking filename completion, we
187 are only interested in the basename, the portion following the
188 final slash. Otherwise, we return what we were passed. */
189 static char *
printable_part(char * pathname)190 printable_part(char *pathname)
191 {
192 char *temp = (char *)NULL;
193
194 if (rl_filename_completion_desired)
195 temp = strrchr(pathname, '/');
196
197 if (!temp)
198 return (pathname);
199 else
200 return (++temp);
201 }
202
203 /* Find the first occurrence in STRING1 of any character from STRING2.
204 Return a pointer to the character in STRING1. */
205 static char *
rl_strpbrk(char * s1,char * s2)206 rl_strpbrk(char *s1, char *s2)
207 {
208 register char *scan;
209
210 for (; *s1; s1++) {
211 for (scan = s2; *scan; scan++) {
212 if (*s1 == *scan) {
213 return (s1);
214 }
215 }
216 }
217 return (char *)NULL;
218 }
219
220 #ifndef _NO_FZF
221 #define FZFTABIN "/tmp/clifm.fzf.in"
222 #define FZFTABOUT "/tmp/clifm.fzf.out"
223
224 static char *
fzftab_color(char * filename,const struct stat attr)225 fzftab_color(char *filename, const struct stat attr)
226 {
227 switch(attr.st_mode & S_IFMT) {
228 case S_IFDIR:
229 if (!check_file_access(attr))
230 return nd_c;
231 return get_dir_color(filename, attr.st_mode);
232 case S_IFREG:
233 if (!check_file_access(attr))
234 return nf_c;
235 char *ext_cl = (char *)NULL;
236 char *ext = strrchr(filename, '.');
237 if (ext && ext != filename)
238 ext_cl = get_ext_color(ext);
239 if (ext_cl)
240 return ext_cl;
241 return get_file_color(filename, attr);
242 case S_IFSOCK: return so_c;
243 case S_IFIFO: return pi_c;
244 case S_IFBLK: return bd_c;
245 case S_IFCHR: return cd_c;
246 case S_IFLNK: return ln_c;
247 default: return uf_c;
248 }
249 }
250
251 static inline char *
get_entry_color(char ** matches,const size_t i)252 get_entry_color(char **matches, const size_t i)
253 {
254 struct stat attr;
255 char *cl = (char *)NULL;
256
257 if (*matches[i] == '/') { /* Absolute path */
258 if (colorize && (cur_comp_type == TCMP_PATH
259 || cur_comp_type == TCMP_SEL || cur_comp_type == TCMP_DESEL)) {
260 if (lstat(matches[i], &attr) != -1)
261 cl = fzftab_color(matches[i], attr);
262 }
263 } else if (*matches[i] == '~') { /* Tilde */
264 if (colorize && cur_comp_type == TCMP_PATH) {
265 char *exp_path = tilde_expand(matches[i]);
266 if (exp_path) {
267 char tmp_path[PATH_MAX + 1];
268 strncpy(tmp_path, exp_path, PATH_MAX);
269 free(exp_path);
270 if (lstat(tmp_path, &attr) != -1)
271 cl = fzftab_color(tmp_path, attr);
272 }
273 }
274 } else { /* Relative path */
275 if (colorize) {
276 if (cur_comp_type == TCMP_PATH || cur_comp_type == TCMP_RANGES) {
277 char tmp_path[PATH_MAX];
278 snprintf(tmp_path, PATH_MAX, "%s/%s", ws[cur_ws].path, matches[i]);
279 if (lstat(tmp_path, &attr) != -1)
280 cl = fzftab_color(tmp_path, attr);
281 } else if (cur_comp_type == TCMP_CMD) {
282 if (is_internal_c(matches[i]))
283 cl = hv_c;
284 }
285 }
286 }
287
288 return cl;
289 }
290
291 static inline void
write_completion(char * buf,const size_t * offset,int * exit_status)292 write_completion(char *buf, const size_t *offset, int *exit_status)
293 {
294 /* Remove ending new line char */
295 char *n = strchr(buf, '\n');
296 if (n)
297 *n = '\0';
298 if (cur_comp_type == TCMP_PATH) {
299 char *esc_buf = escape_str(buf);
300 if (esc_buf) {
301 rl_insert_text(esc_buf + *offset);
302 free(esc_buf);
303 } else {
304 rl_insert_text(buf + *offset);
305 }
306 } else {
307 rl_insert_text(buf + *offset);
308 }
309
310 /* Append slash for dirs and space for non-dirs */
311 char *pp = rl_line_buffer;
312 char *ss = (char *)NULL;
313 if (pp) {
314 while (*pp) {
315 if (pp == rl_line_buffer) {
316 pp++;
317 continue;
318 }
319
320 if (*pp == ' ' && *(pp - 1) != '\\' && *(pp + 1) != ' ')
321 ss = pp + 1;
322
323 pp++;
324 }
325 }
326
327 if (!ss || !*ss)
328 ss = rl_line_buffer;
329
330 if (!ss)
331 return;
332
333 char deq_str[PATH_MAX];
334 *deq_str = '\0';
335 if (strchr(ss, '\\')) {
336 size_t i = 0;
337 char *b = ss;
338 while (*b && i < (PATH_MAX - 1)) {
339 if (*b != '\\')
340 deq_str[i++] = *b;
341 b++;
342 }
343 deq_str[i] = '\0';
344 }
345
346 char _path[PATH_MAX];
347 *_path = '\0';
348 if (*ss != '/' && *ss != '.' && *ss != '~')
349 snprintf(_path, PATH_MAX, "%s/%s", ws[cur_ws].path, ss);
350
351 char *spath = *_path ? _path : (*deq_str ? deq_str : ss);
352 char *epath = (char *)NULL;
353 if (*spath == '~')
354 epath = tilde_expand(spath);
355 else if (*spath == '.') {
356 xchdir(ws[cur_ws].path, NO_TITLE);
357 epath = realpath(spath, NULL);
358 /* No need to change back to CWD. Done here */
359 *exit_status = -1;
360 }
361
362 if (epath)
363 spath = epath;
364
365 struct stat attr;
366 if (stat(spath, &attr) != -1 && S_ISDIR(attr.st_mode))
367 rl_insert_text("/");
368 else if (cur_comp_type != TCMP_OPENWITH)
369 rl_stuff_char(' ');
370
371 free(epath);
372 }
373
374 static inline char *
get_last_word(char * matches)375 get_last_word(char *matches)
376 {
377 /* Get word after last non-escaped space */
378 char *ss = matches, *s = (char *)NULL;
379 while (*ss) {
380 if (ss == matches) {
381 ss++;
382 continue;
383 }
384 if (*ss == ' ' && *(ss - 1) != '\\' && *(ss + 1) != ' ')
385 s = ss;
386 ss++;
387 }
388 if (!s)
389 s = matches;
390
391 /* Get word after last non-escaped slash */
392 char *sl = s;
393 char *d = (char *)NULL;
394 while (*sl) {
395 if (sl == s) {
396 if (*sl == '/')
397 d = sl;
398 } else if (*sl == '/' && *(sl - 1) != '\\') {
399 d = sl;
400 }
401 sl++;
402 }
403
404 if (!d)
405 d = s;
406 else if (*d == '/')
407 d++;
408
409 return d;
410 }
411
412 static inline int
run_fzf(const size_t * height,const int * offset,const char * lw)413 run_fzf(const size_t *height, const int *offset, const char *lw)
414 {
415 char cmd[PATH_MAX];
416 snprintf(cmd, PATH_MAX, "$(fzf %s "
417 "--height=%zu --margin=0,0,0,%d "
418 "%s "
419 "--query=\"%s\" "
420 "< %s > %s)",
421 fzftab_options,
422 *height, *offset,
423 case_sens_path_comp ? "+i" : "-i",
424 lw ? lw : "",
425 FZFTABIN, FZFTABOUT);
426 int ret = launch_execle(cmd);
427
428 return ret;
429 }
430
431 /*
432 static int
433 curses_tab(char **list)
434 {
435 if (!list || !list[0])
436 return (-1);
437
438 get_cursor_position(STDIN_FILENO, STDOUT_FILENO);
439
440 char *l = list[0];
441 list++;
442
443 size_t n = 0;
444 int i = 0;
445 for (; list[n]; n++);
446
447 WINDOW *w;
448 char item[PATH_MAX];
449 int c;
450
451 size_t height = 0;
452 if ((int)n + 1 > term_rows - 2)
453 height = term_rows - 2;
454 else
455 height = n;
456
457 // Calculate the offset (left padding) of the FZF window based on
458 // cursor position and current query string
459 int max_curses_offset = term_cols > 20 ? term_cols - 20 : 0;
460 int curses_offset = (rl_point + prompt_offset < max_curses_offset)
461 ? (rl_point + prompt_offset - 4) : 0;
462
463 // Calculate currently used lines to go back to the correct cursor
464 // position after quitting FZF
465 int lines = 1, total_line_len = 0;
466 total_line_len = rl_end + prompt_offset;
467 // PROMPT_OFFSET (the space used by the prompt in the current line)
468 // is calculated the first time we print the prompt (in my_rl_getc
469 // (readline.c))
470
471 if (total_line_len > term_cols) {
472 lines = total_line_len / term_cols;
473 int rem = (int)total_line_len % term_cols;
474 if (rem > 0)
475 lines++;
476 }
477
478 // if (currow + (int)height > term_rows) {
479 // printf("\x1b[%dB", term_rows - (currow + (int)height));
480 // }
481
482 int a = (int)height <= term_rows -2 ? (int)height : term_rows - 2;
483
484 initscr(); // initialize Ncurses
485 // start_color();
486 // init_pair(1, COLOR_RED, COLOR_BLACK);
487 // init_pair(2, COLOR_CYAN, COLOR_BLACK);
488
489 w = newwin(a + 1, PATH_MAX, 1, curses_offset); // create a new window
490 // box(w, 0, 0); // sets default borders for the window
491
492 // now print all the menu items and highlight the first one
493 for (i = 0; i < (int)n; i++) {
494 if (i == 0)
495 wattron(w, A_STANDOUT); // highlights the first item.
496 else
497 wattroff(w, A_STANDOUT);
498 sprintf(item, "%-7s", list[i]);
499 mvwprintw(w, i + 1, 2, "%s", item);
500 }
501
502 wrefresh(w); // update the terminal screen
503
504 i = 0;
505 scrollok(w, TRUE);
506 noecho(); // disable echoing of characters on the screen
507 keypad(w, TRUE); // enable keyboard input for the window.
508 curs_set(0); // hide the default screen cursor.
509
510 // get the input
511 while ((c = wgetch(w))) {
512 // right pad with spaces to make the items appear with even width.
513 sprintf(item, "%-7s", list[i]);
514 mvwprintw(w, i + 1, 2, "%s", item);
515 // use a variable to increment or decrement the value based on the input.
516 switch(c) {
517 case KEY_UP:
518 i--;
519 i = i < 0 ? (int)n - 1 : i;
520 // if (i > term_rows - 3) {
521 // wscrl(w, -1);
522 // wrefresh(w);
523 // }
524 break;
525 case KEY_DOWN:
526 i++;
527 i = i > (int)n - 1 ? 0 : i;
528 // if (i > term_rows - 3) {
529 // wscrl(w, 1);
530 // wrefresh(w);
531 // }
532 break;
533
534 // case CONTROL('c'): goto END;
535
536 // case ESC: // fallthrough
537 case 'q': goto END;
538
539 case 10: goto PRINT; // Enter
540 }
541
542 // now highlight the next item in the list.
543 wattron(w, A_STANDOUT);
544 sprintf(item, "%-7s", list[i]);
545 mvwprintw(w, (int)i + 1, 2, "%s", item);
546 wattroff(w, A_STANDOUT);
547 wrefresh(w);
548 }
549
550 END:
551 delwin(w);
552 endwin();
553 printf("\x1b[%dA", lines);
554 return (-1);
555
556 PRINT:
557 delwin(w);
558 endwin();
559 // Restore cursor position
560 printf("\x1b[%d;%dH", currow - lines, curcol);
561 char *d = escape_str(list[i]);
562 rl_insert_text(d + strlen(l));
563 free(d);
564 return EXIT_SUCCESS;
565 } */
566
567 /* Display possible completions using FZF. If one of these possible
568 * completions is selected, insert it into the current line buffer */
569 static int
fzftabcomp(char ** matches)570 fzftabcomp(char **matches)
571 {
572 FILE *fp = fopen(FZFTABIN, "w");
573 if (!fp) {
574 _err('e', PRINT_PROMPT, "%s: %s: %s\n", PROGRAM_NAME,
575 FZFTABIN, strerror(errno));
576 return EXIT_FAILURE;
577 }
578
579 int exit_status = EXIT_SUCCESS;
580
581 /* Store possible completions in FZFTABIN to pass them to FZF */
582 size_t i;
583 for (i = 1; matches[i]; i++) {
584 if (!matches[i] || !*matches[i])
585 continue;
586
587 char *cl = df_c;
588 char *color = df_c;
589 char *entry = matches[i];
590
591 if (cur_comp_type != TCMP_HIST && cur_comp_type != TCMP_JUMP) {
592 cl = get_entry_color(matches, i);
593
594 char ext_cl[MAX_COLOR + 5];
595 *ext_cl = '\0';
596 /* If color does not start with escape, then we have a color
597 * for a file extension. In this case, we need to properly
598 * construct the color code */
599 if (cl && *cl != _ESC)
600 snprintf(ext_cl, MAX_COLOR + 4, "\x1b[%sm", cl);
601
602 char *p = (char *)NULL;
603 if (cur_comp_type != TCMP_SEL && cur_comp_type != TCMP_DESEL
604 && cur_comp_type != TCMP_OPENWITH)
605 p = strrchr(matches[i], '/');
606 color = *ext_cl ? ext_cl : (cl ? cl : "");
607 entry = (p && *(++p)) ? p : matches[i];
608 }
609
610 if (*entry && !SELFORPARENT(entry))
611 fprintf(fp, "%s%s%s\n", color, entry, df_c);
612 }
613
614 fclose(fp);
615
616 /* Set a pointer to the last word (either space or slash) in the
617 * input buffer. We use this to highlight the matching prefix in FZF */
618 char *lw = get_last_word(matches[0]);
619
620 /* Calculate the height of the FZF window based on the amount
621 * of entries */
622 size_t height = 0;
623 if ((int)i + 1 > term_rows - 2)
624 height = term_rows - 2;
625 else
626 height = i;
627
628 /* Calculate the offset (left padding) of the FZF window based on
629 * cursor position and current query string */
630 int max_fzf_offset = term_cols > 20 ? term_cols - 20 : 0;
631 int fzf_offset = (rl_point + prompt_offset < max_fzf_offset)
632 ? (rl_point + prompt_offset - 4) : 0;
633
634 if (!lw)
635 fzf_offset++;
636 else
637 fzf_offset -= (int)(strlen(lw) - 1);
638
639 char *query = (char *)NULL;
640 switch(cur_comp_type) {
641 case TCMP_SEL: break;
642 case TCMP_DESEL: {
643 char *sp = strrchr(rl_line_buffer, ' ');
644 query = (sp && *(++sp)) ? sp : rl_line_buffer;
645 }
646 break;
647
648 case TCMP_RANGES: break;
649
650 case TCMP_HIST:
651 /* Skip the leading ! char of the input string */
652 query = rl_line_buffer + 1;
653 fzf_offset = 1 + prompt_offset - 3;
654 break;
655
656 case TCMP_JUMP: {
657 char *sp = strchr(rl_line_buffer, ' ');
658 if (sp && *(++sp)) {
659 query = sp;
660 if (*(rl_line_buffer + 1) == ' ')
661 /* The command is "j" */
662 fzf_offset = 2 + prompt_offset - 3;
663 else
664 /* The command is "jump" */
665 fzf_offset = 5 + prompt_offset - 3;
666 } else {
667 query = rl_line_buffer;
668 }
669 }
670 break;
671
672 default: query = lw;
673 }
674
675 if (fzf_offset < 0)
676 fzf_offset = 0;
677
678 /* Run FZF and store the ouput into the FZFTABOUT file */
679 int ret = run_fzf(&height, &fzf_offset, query);
680 unlink(FZFTABIN);
681
682 /* Calculate currently used lines to go back to the correct cursor
683 * position after quitting FZF */
684 int lines = 1, total_line_len = 0;
685 total_line_len = rl_end + prompt_offset;
686 /* PROMPT_OFFSET (the space used by the prompt in the current line)
687 * is calculated the first time we print the prompt (in my_rl_getc
688 * (readline.c)) */
689
690 if (total_line_len > term_cols) {
691 lines = total_line_len / term_cols;
692 int rem = (int)total_line_len % term_cols;
693 if (rem > 0)
694 lines++;
695 }
696
697 printf("\x1b[%dA", lines);
698
699 /* No results */
700 if (ret != EXIT_SUCCESS)
701 return EXIT_FAILURE;
702
703 fp = fopen(FZFTABOUT, "r");
704 if (!fp) {
705 _err('e', PRINT_PROMPT, "%s: %s: %s\n", PROGRAM_NAME,
706 FZFTABOUT, strerror(errno));
707 return EXIT_FAILURE;
708 }
709
710 /* Recover FZF ouput */
711 /* This should be enough space for a file name and the chars taken by the
712 * command itself */
713 char buf[PATH_MAX + NAME_MAX];
714 fgets(buf, sizeof(buf), fp);
715 fclose(fp);
716 unlink(FZFTABOUT);
717
718 /* Calculate the length of the matching prefix to insert into the
719 * line buffer only the non-matched part of the string returned
720 * by FZF */
721 size_t offset = 0, mlen = strlen(matches[0]);
722 if (mlen && matches[0][mlen - 1] != '/') {
723 char *q = strrchr(matches[0], '/');
724 if (q)
725 offset = strlen(q + 1);
726 else
727 offset = mlen;
728 }
729
730 if (cur_comp_type == TCMP_OPENWITH) {
731 /* Interpret the corresponding cmd line in the mimelist file
732 * and replace the input string by the interpreted line */
733 char *sp = strchr(rl_line_buffer, ' ');
734 if (!sp || !*(sp++))
735 return EXIT_FAILURE;
736
737 char *t = sp;
738 while (*t) {
739 if (t != sp && *t == ' ' && *(t - 1) != '\\') {
740 *t = '\0';
741 break;
742 }
743 ++t;
744 }
745
746 size_t splen = (size_t)(t - sp);
747 if (sp[splen - 1] == '/')
748 sp[--splen] = '\0';
749
750 rl_delete_text(0, rl_end);
751 rl_point = rl_end = 0;
752 offset = 0;
753
754 t = strchr(buf, '%');
755 if (t && *(t + 1) == 'f') {
756 char *ss = replace_substr(buf, "%f", sp);
757 if (ss) {
758 xstrsncpy(buf, ss, sizeof(buf));
759 free(ss);
760 }
761 } else {
762 size_t blen = strlen(buf);
763 if (buf[blen - 1] == '\n')
764 buf[--blen] = '\0';
765 snprintf(buf + blen, sizeof(buf) - blen, " %s", sp);
766 }
767
768 } else if (cur_comp_type == TCMP_DESEL) {
769 offset = strlen(query);
770
771 } else if (cur_comp_type == TCMP_HIST || cur_comp_type == TCMP_JUMP) {
772 rl_delete_text(0, rl_end);
773 rl_point = rl_end = 0;
774 offset = 0;
775
776 } else if (cur_comp_type == TCMP_RANGES || cur_comp_type == TCMP_SEL) {
777 char *s = strrchr(rl_line_buffer, ' ');
778 if (s) {
779 rl_point = (int)(s - rl_line_buffer + 1);
780 rl_delete_text(rl_point, rl_end);
781 rl_end = rl_point;
782 offset = 0;
783 }
784
785 } else if (!case_sens_path_comp && query) {
786 /* Honor case insensitive completion */
787 size_t query_len = strlen(query);
788 if (strncmp(query, buf, query_len) != 0) {
789 int bk = rl_point;
790 rl_delete_text(bk - (int)query_len, rl_end);
791 rl_point = rl_end = bk - (int)query_len;
792 offset = 0;
793 }
794 }
795
796 if (*buf) {
797 /* Some buffer clean up: remove new line char and ending spaces */
798 size_t blen = strlen(buf);
799 int j = (int)blen;
800 if (buf[j - 1] == '\n')
801 buf[--j] = '\0';
802 while (buf[--j] == ' ')
803 buf[j] = '\0';
804
805 char *q = (char *)NULL;
806 if (cur_comp_type != TCMP_OPENWITH && cur_comp_type != TCMP_PATH) {
807 q = escape_str(buf);
808 if (!q)
809 return EXIT_FAILURE;
810 } else {
811 q = savestring(buf, blen);
812 }
813
814 write_completion(q, &offset, &exit_status);
815 free(q);
816 }
817
818 return exit_status;
819 }
820 #endif /* !_NO_FZF */
821
822 /* Complete the word at or before point.
823 WHAT_TO_DO says what to do with the completion.
824 `?' means list the possible completions.
825 TAB means do standard completion.
826 `*' means insert all of the possible completions.
827 `!' means to do standard completion, and list all possible completions
828 if there is more than one. */
829 /* This function is taken from an old bash release (1.14.7) and modified
830 * to fit our needs */
831 int
tab_complete(int what_to_do)832 tab_complete(int what_to_do)
833 {
834 if (rl_notab)
835 return EXIT_SUCCESS;
836
837 rl_compentry_func_t *our_func = (rl_compentry_func_t *)NULL;
838
839 /* char *saved_line_buffer = (char *)NULL;
840 if (rl_line_buffer)
841 saved_line_buffer = savestring(rl_line_buffer, (size_t)rl_end); */
842
843 our_func = rl_completion_entry_function;
844
845 /* Only the completion entry function can change these. */
846 rl_filename_completion_desired = 0;
847 rl_filename_quoting_desired = 1;
848
849 int end = rl_point, delimiter = 0;
850 char quote_char = '\0';
851
852 /* We now look backwards for the start of a filename/variable word. */
853 if (rl_point) {
854 int scan = 0;
855
856 if (rl_completer_quote_characters) {
857 /* We have a list of characters which can be used in pairs to
858 * quote substrings for the completer. Try to find the start
859 * of an unclosed quoted substring. */
860 /* FOUND_QUOTE is set so we know what kind of quotes we found. */
861 int pass_next; //found_quote = 0;
862 for (scan = pass_next = 0; scan < end; scan++) {
863 if (pass_next) {
864 pass_next = 0;
865 continue;
866 }
867
868 if (rl_line_buffer[scan] == '\\') {
869 pass_next = 1;
870 // found_quote |= 4;
871 continue;
872 }
873
874 if (quote_char != '\0') {
875 /* Ignore everything until the matching close quote char. */
876 if (rl_line_buffer[scan] == quote_char) {
877 /* Found matching close. Abandon this substring. */
878 quote_char = '\0';
879 rl_point = end;
880 }
881 } else if (strchr(rl_completer_quote_characters,
882 rl_line_buffer[scan])) {
883 /* Found start of a quoted substring. */
884 quote_char = rl_line_buffer[scan];
885 rl_point = scan + 1;
886 /* Shell-like quoting conventions. */
887 /* if (quote_char == '\'')
888 found_quote |= 1;
889 else if (quote_char == '"')
890 found_quote |= 2; */
891 }
892 }
893 }
894
895 if (rl_point == end && quote_char == '\0') {
896 /* We didn't find an unclosed quoted substring upon which to do
897 * completion, so use the word break characters to find the
898 * substring on which to complete. */
899 while (--rl_point) {
900 scan = rl_line_buffer[rl_point];
901
902 if (strchr(rl_completer_word_break_characters, scan) == 0
903 || (scan == ' '
904 && rl_point && rl_line_buffer[rl_point - 1] == '\\'))
905 continue;
906
907 /* Convoluted code, but it avoids an n^2 algorithm with
908 * calls to char_is_quoted. */
909 break;
910 }
911 }
912
913 /* If we are at an unquoted word break, then advance past it. */
914 scan = rl_line_buffer[rl_point];
915 if (strchr(rl_completer_word_break_characters, scan)) {
916 /* If the character that caused the word break was a quoting
917 * character, then remember it as the delimiter. */
918 if (strchr("\"'", scan) && (end - rl_point) > 1)
919 delimiter = scan;
920
921 /* If the character isn't needed to determine something special
922 * about what kind of completion to perform, then advance past it. */
923 if (!rl_special_prefixes || strchr(rl_special_prefixes, scan) == 0)
924 rl_point++;
925 }
926 }
927
928 int start = rl_point;
929 rl_point = end;
930 char *text = rl_copy_text(start, end);
931 char **matches = (char **)NULL;
932
933 /* At this point, we know we have an open quote if quote_char != '\0'. */
934
935 /* If the user wants to TRY to complete, but then wants to give
936 * up and use the default completion function, they set the
937 * variable rl_attempted_completion_function. */
938 if (rl_attempted_completion_function) {
939 matches = (*rl_attempted_completion_function) (text, start, end);
940
941 if (matches || rl_attempted_completion_over) {
942 rl_attempted_completion_over = 0;
943 our_func = (rl_compentry_func_t *)NULL;
944 goto AFTER_USUAL_COMPLETION;
945 }
946 }
947
948 matches = rl_completion_matches(text, our_func);
949
950 AFTER_USUAL_COMPLETION:
951 free(text);
952
953 if (!matches || !matches[0]) {
954 // rl_ding();
955 rl_ring_bell();
956 return EXIT_FAILURE;
957 }
958
959 register size_t i;
960 int should_quote;
961
962 /* It seems to me that in all the cases we handle we would like
963 * to ignore duplicate possiblilities. Scan for the text to
964 * insert being identical to the other completions. */
965 if (rl_ignore_completion_duplicates) {
966 char *lowest_common;
967 size_t j;
968 size_t newlen = 0;
969 char dead_slot;
970 char **temp_array;
971
972 /* Sort the items. */
973 /* It is safe to sort this array, because the lowest common
974 denominator found in matches[0] will remain in place. */
975 // for (i = 0; matches[i]; i++);
976 /* Try sorting the array without matches[0], since we need it to
977 stay in place no matter what. */
978 // if (i)
979 // qsort(matches + 1, i - 1, sizeof (char *), compare_strings);
980
981 /* Remember the lowest common denominator for it may be unique. */
982 lowest_common = savestring(matches[0], strlen(matches[0]));
983
984 for (i = 0; matches[i + 1]; i++) {
985 if (strcmp(matches[i], matches[i + 1]) == 0) {
986 free(matches[i]);
987 matches[i] = (char *)&dead_slot;
988 } else {
989 newlen++;
990 }
991 }
992
993 /* We have marked all the dead slots with (char *)&dead_slot.
994 * Copy all the non-dead entries into a new array. */
995 temp_array = (char **)xnmalloc(3 + newlen, sizeof (char *));
996 for (i = j = 1; matches[i]; i++) {
997 if (matches[i] != (char *)&dead_slot)
998 temp_array[j++] = matches[i];
999 }
1000 temp_array[j] = (char *)NULL;
1001
1002 if (matches[0] != (char *)&dead_slot)
1003 free(matches[0]);
1004 free(matches);
1005
1006 matches = temp_array;
1007
1008 /* Place the lowest common denominator back in [0]. */
1009 matches[0] = lowest_common;
1010
1011 /* If there is one string left, and it is identical to the
1012 * lowest common denominator, then the LCD is the string to
1013 * insert. */
1014 if (j == 2 && strcmp(matches[0], matches[1]) == 0) {
1015 free(matches[1]);
1016 matches[1] = (char *)NULL;
1017 }
1018 }
1019
1020 switch (what_to_do) {
1021 // case TAB:
1022 case '!':
1023 /* If we are matching filenames, then here is our chance to
1024 * do clever processing by re-examining the list. Call the
1025 * ignore function with the array as a parameter. It can
1026 * munge the array, deleting matches as it desires. */
1027 if (rl_ignore_some_completions_function
1028 && our_func == rl_completion_entry_function) {
1029 (void)(*rl_ignore_some_completions_function)(matches);
1030 if (matches == 0 || matches[0] == 0) {
1031 if (matches)
1032 free(matches);
1033 rl_ding();
1034 return 0;
1035 }
1036 }
1037
1038 /* If we are doing completion on quoted substrings, and any matches
1039 * contain any of the completer_word_break_characters, then
1040 * automatically prepend the substring with a quote character
1041 * (just pick the first one from the list of such) if it does not
1042 * already begin with a quote string. FIXME: Need to remove any such
1043 * automatically inserted quote character when it no longer is necessary,
1044 * such as if we change the string we are completing on and the new
1045 * set of matches don't require a quoted substring. */
1046 char *replacement = matches[0];
1047
1048 should_quote = matches[0] && rl_completer_quote_characters &&
1049 rl_filename_completion_desired && rl_filename_quoting_desired;
1050
1051 if (should_quote)
1052 should_quote = should_quote && !quote_char;
1053
1054 if (should_quote) {
1055 int do_replace;
1056
1057 do_replace = NO_MATCH;
1058
1059 /* If there is a single match, see if we need to quote it.
1060 This also checks whether the common prefix of several
1061 matches needs to be quoted. If the common prefix should
1062 not be checked, add !matches[1] to the if clause. */
1063 should_quote = rl_strpbrk(matches[0],
1064 rl_completer_word_break_characters) != 0;
1065
1066 if (should_quote)
1067 do_replace = matches[1] ? MULT_MATCH : SINGLE_MATCH;
1068
1069 if (do_replace != NO_MATCH) {
1070 /* Found an embedded word break character in a potential
1071 match, so we need to prepend a quote character if we
1072 are replacing the completion string. */
1073 replacement = escape_str(matches[0]);
1074 }
1075 }
1076
1077 if (replacement && cur_comp_type != TCMP_HIST
1078 && cur_comp_type != TCMP_JUMP && cur_comp_type != TCMP_RANGES
1079 && (cur_comp_type != TCMP_SEL || !fzftab || sel_n == 1)) {
1080 if ((cur_comp_type == TCMP_SEL || cur_comp_type == TCMP_DESEL
1081 || cur_comp_type == TCMP_NET) && !strchr(replacement, '\\')) {
1082 char *r = escape_str(replacement);
1083 if (!r) {
1084 if (replacement != matches[0])
1085 free(replacement);
1086 break;
1087 }
1088 if (replacement != matches[0])
1089 free(replacement);
1090 replacement = r;
1091 }
1092
1093 rl_begin_undo_group();
1094 rl_delete_text(start, rl_point);
1095 rl_point = start;
1096 #ifndef _NO_HIGHLIGHT
1097 if (highlight && !wrong_cmd) {
1098 size_t k, l = 0;
1099 char *cc = cur_color;
1100 fputs("\x1b[?25l", stdout);
1101 char t[PATH_MAX];
1102 for (k = 0; replacement[k]; k++) {
1103 rl_highlight(replacement, k, SET_COLOR);
1104 if (replacement[k] < 0) {
1105 t[l++] = replacement[k];
1106 if (replacement[k + 1] >= 0) {
1107 t[l] = '\0';
1108 l = 0;
1109 rl_insert_text(t);
1110 rl_redisplay();
1111 }
1112 continue;
1113 }
1114 t[0] = (char)replacement[k];
1115 t[1] = '\0';
1116 rl_insert_text(t);
1117 rl_redisplay();
1118 }
1119 fputs("\x1b[?25h", stdout);
1120 cur_color = cc;
1121 if (cur_color)
1122 fputs(cur_color, stdout);
1123 } else {
1124 rl_insert_text(replacement);
1125 }
1126 #else
1127 rl_insert_text(replacement);
1128 #endif
1129 rl_end_undo_group();
1130
1131 if (replacement != matches[0])
1132 free(replacement);
1133 }
1134
1135 /* If there are more matches, ring the bell to indicate.
1136 If this was the only match, and we are hacking files,
1137 check the file to see if it was a directory. If so,
1138 add a '/' to the name. If not, and we are at the end
1139 of the line, then add a space. */
1140 if (matches[1]) {
1141 if (what_to_do == '!')
1142 goto DISPLAY_MATCHES; /* XXX */
1143 else if (rl_editing_mode != 0) /* vi_mode */
1144 rl_ding(); /* There are other matches remaining. */
1145 } else {
1146 char temp_string[4];
1147 int temp_string_index = 0;
1148
1149 if (quote_char)
1150 temp_string[temp_string_index++] = quote_char;
1151
1152 temp_string[temp_string_index++] = (char)(delimiter
1153 ? delimiter : ' ');
1154 temp_string[temp_string_index++] = '\0';
1155
1156 if (rl_filename_completion_desired) {
1157 struct stat finfo;
1158 char *filename = tilde_expand(matches[0]);
1159
1160 if ((stat(filename, &finfo) == 0) && S_ISDIR(finfo.st_mode)) {
1161 if (rl_line_buffer[rl_point] != '/') {
1162 #ifndef _NO_HIGHLIGHT
1163 if (highlight && !wrong_cmd) {
1164 char *cc = cur_color;
1165 fputs(hd_c, stdout);
1166 rl_insert_text ("/");
1167 rl_redisplay();
1168 fputs(cc, stdout);
1169 } else {
1170 rl_insert_text ("/");
1171 }
1172 #else
1173 rl_insert_text ("/");
1174 #endif
1175 }
1176 } else {
1177 if (rl_point == rl_end)
1178 rl_insert_text(temp_string);
1179 }
1180 free(filename);
1181 } else {
1182 if (rl_point == rl_end)
1183 rl_insert_text(temp_string);
1184 }
1185 }
1186 break;
1187
1188 /* case '*': {
1189 i = 1;
1190
1191 rl_begin_undo_group();
1192 rl_delete_text(start, rl_point);
1193 rl_point = start;
1194 if (matches[1]) {
1195 while (matches[i]) {
1196 rl_insert_text(matches[i++]);
1197 rl_insert_text(" ");
1198 }
1199 } else {
1200 rl_insert_text(matches[0]);
1201 rl_insert_text(" ");
1202 }
1203 rl_end_undo_group();
1204 }
1205 break; */
1206
1207 case '?': {
1208 int len = 0, count = 0, limit = 0, max = 0;
1209 int j = 0, k = 0, l = 0;
1210
1211 /* Handle simple case first. What if there is only one answer? */
1212 if (!matches[1]) {
1213 char *temp;
1214 temp = printable_part(matches[0]);
1215 rl_crlf();
1216 print_filename(temp, matches[0]);
1217 rl_crlf();
1218 goto RESTART;
1219 }
1220
1221 /* There is more than one answer. Find out how many there are,
1222 and find out what the maximum printed length of a single entry
1223 is. */
1224
1225 DISPLAY_MATCHES:
1226 /*#ifndef _NO_SUGGESTIONS
1227 if (wrong_cmd)
1228 recover_from_wrong_cmd();
1229 #endif */
1230
1231 for (max = 0, i = 1; matches[i]; i++) {
1232 char *temp;
1233 size_t name_length;
1234
1235 temp = printable_part(matches[i]);
1236 name_length = strlen(temp);
1237
1238 if ((int)name_length > max)
1239 max = (int)name_length;
1240 }
1241
1242 len = (int)i - 1;
1243
1244 /* If there are many items, then ask the user if she
1245 really wants to see them all. */
1246 #ifndef _NO_FZF
1247 if (!fzftab) {
1248 #endif
1249 {
1250 if (len >= rl_completion_query_items) {
1251 putchar('\n');
1252 #ifndef _NO_HIGHLIGHT
1253 if (highlight && cur_color != tx_c && !wrong_cmd) {
1254 cur_color = tx_c;
1255 fputs(tx_c, stdout);
1256 }
1257 #endif
1258 // rl_crlf();
1259 fprintf(rl_outstream,
1260 "Display all %d possibilities? (y or n) ", len);
1261 // rl_crlf();
1262 fflush(rl_outstream);
1263 if (!get_y_or_n()) {
1264 // rl_crlf();
1265 goto RESTART;
1266 }
1267 }
1268 }
1269 #ifndef _NO_FZF
1270 }
1271 #endif
1272
1273
1274 #ifndef _NO_FZF
1275 if (!fzftab) {
1276 #endif
1277 {
1278 /* How many items of MAX length can we fit in the screen window? */
1279 max += 2;
1280 limit = term_cols / max;
1281 if (limit != 1 && (limit * max == term_cols))
1282 limit--;
1283
1284 /* Avoid a possible floating exception. If max > screenwidth,
1285 limit will be 0 and a divide-by-zero fault will result. */
1286 if (limit == 0)
1287 limit = 1;
1288
1289 /* How many iterations of the printing loop? */
1290 count = (len + (limit - 1)) / limit;
1291 }
1292 #ifndef _NO_FZF
1293 }
1294 #endif
1295
1296 /* Watch out for special case. If LEN is less than LIMIT, then
1297 just do the inner printing loop.
1298 0 < len <= limit implies count = 1. */
1299
1300 /* Sort the items if they are not already sorted. */
1301 // if (!rl_ignore_completion_duplicates)
1302 // qsort(matches + 1, len ? (size_t)len - 1 : 0, sizeof (char *), compare_strings);
1303
1304 /* Print the sorted items, up-and-down alphabetically, like
1305 ls might. */
1306 // rl_crlf();
1307 putchar('\n');
1308 #ifndef _NO_HIGHLIGHT
1309 if (highlight && cur_color != tx_c && !wrong_cmd) {
1310 cur_color = tx_c;
1311 fputs(tx_c, stdout);
1312 }
1313 #endif
1314 char *qq = (char *)NULL;
1315 if (cur_comp_type != TCMP_PATH)
1316 goto CALC_OFFSET;
1317
1318 if (*matches[0] == '~') {
1319 char *exp_path = tilde_expand(matches[0]);
1320 if (exp_path) {
1321 xchdir(exp_path, NO_TITLE);
1322 free(exp_path);
1323 }
1324 } else {
1325 char *p = strrchr(matches[0], '/');
1326 if (!p)
1327 goto CALC_OFFSET;
1328
1329 if (p == matches[0]) {
1330 if (*(p + 1)) {
1331 char pp = *(p + 1);
1332 *(p + 1) = '\0';
1333 xchdir(matches[0], NO_TITLE);
1334 *(p + 1) = pp;
1335 } else {
1336 /* We have the root dir */
1337 xchdir(matches[0], NO_TITLE);
1338 }
1339 } else {
1340 *p = '\0';
1341 xchdir(matches[0], NO_TITLE);
1342 *p = '/';
1343 }
1344 }
1345
1346 CALC_OFFSET:
1347 qq = strrchr(matches[0], '/');
1348 if (qq) {
1349 if (*(++qq)) {
1350 tab_offset = strlen(qq);
1351 } else if (cur_comp_type == TCMP_DESEL) {
1352 tab_offset = strlen(matches[0]);
1353 qq = matches[0];
1354 }
1355 } else {
1356 tab_offset = strlen(matches[0]);
1357 }
1358 if (cur_comp_type == TCMP_RANGES)
1359 tab_offset = 0;
1360
1361 /* if (curses_tab(matches) == -1)
1362 goto RESTART;
1363 goto RESET_PATH; */
1364
1365 #ifndef _NO_FZF
1366 if (fzftab == 1) {
1367 if (fzftabcomp(matches) == -1)
1368 goto RESTART;
1369 goto RESET_PATH;
1370 }
1371 #endif
1372
1373 for (i = 1; i <= (size_t)count; i++) {
1374 if (i >= term_rows) {
1375 /* A little pager */
1376 fputs("\x1b[7;97m--Mas--\x1b[0;49m", stdout);
1377 int c = 0;
1378 while ((c = xgetchar()) == _ESC);
1379 if (c == 'q') {
1380 /* Delete the --Mas-- label */
1381 fputs("\x1b[7D\x1b[7X\x1b[1A\n", stdout);
1382 break;
1383 }
1384 fputs("\x1b[7D\x1b[0K", stdout);
1385 }
1386
1387 for (j = 0, l = (int)i; j < limit; j++) {
1388 if (l > len || !matches[l]) {
1389 break;
1390 } else {
1391 if (tab_offset) {
1392 /* Print the matching part of the match */
1393 printf("\x1b[0m%s%s\x1b[0m%s", ts_c, qq ? qq : matches[0],
1394 (cur_comp_type == TCMP_CMD) ? (colorize
1395 ? ex_c : "") : dc_c);
1396 }
1397
1398 /* Now print the non-matching part of the match */
1399 char *temp;
1400 int printed_length;
1401 temp = printable_part(matches[l]);
1402 printed_length = (int)strlen(temp);
1403 printed_length += print_filename(temp, matches[l]);
1404
1405 if (j + 1 < limit) {
1406 for (k = 0; k < max - printed_length; k++)
1407 putc(' ', rl_outstream);
1408 }
1409 }
1410 l += count;
1411 }
1412 putchar('\n');
1413 // rl_crlf();
1414 }
1415 tab_offset = 0;
1416
1417 if (!wrong_cmd && colorize && cur_comp_type == TCMP_CMD)
1418 fputs(tx_c, stdout);
1419
1420 #ifndef _NO_FZF
1421 RESET_PATH:
1422 #endif
1423 if (cur_comp_type == TCMP_PATH)
1424 xchdir(ws[cur_ws].path, NO_TITLE);
1425
1426 RESTART:
1427 rl_on_new_line();
1428 #ifndef _NO_HIGHLIGHT
1429 if (highlight && !wrong_cmd) {
1430 int bk = rl_point;
1431 fputs("\x1b[?25l", stdout);
1432 char *ss = rl_copy_text(0, rl_end);
1433 rl_delete_text(0, rl_end);
1434 rl_redisplay();
1435 rl_point = rl_end = 0;
1436 int wc = wrong_cmd_line;
1437 if (wc) {
1438 cur_color = hw_c;
1439 fputs(cur_color, stdout);
1440 }
1441 l = 0;
1442 char t[PATH_MAX];
1443 for (k = 0; ss[k]; k++) {
1444 if (ss[k] == ' ')
1445 wc = 0;
1446
1447 if (!wc)
1448 rl_highlight(ss, (size_t)k, SET_COLOR);
1449
1450 if (ss[k] < 0) {
1451 t[l++] = ss[k];
1452 if (ss[k + 1] >= 0) {
1453 t[l] = '\0';
1454 l = 0;
1455 rl_insert_text(t);
1456 rl_redisplay();
1457 }
1458 continue;
1459 }
1460
1461 t[0] = (char)ss[k];
1462 t[1] = '\0';
1463 rl_insert_text(t);
1464 rl_redisplay();
1465 }
1466 fputs("\x1b[?25h", stdout);
1467 rl_point = rl_end = bk;
1468 free(ss);
1469 }
1470 #endif
1471 }
1472 break;
1473
1474 default:
1475 fprintf(stderr, "\r\nreadline: bad value for what_to_do in rl_complete\n");
1476 abort();
1477 }
1478
1479 for (i = 0; matches[i]; i++)
1480 free(matches[i]);
1481 free(matches);
1482
1483 /* Check to see if the line has changed through all of this manipulation. */
1484 // if (saved_line_buffer) {
1485 /* if (strcmp (rl_line_buffer, saved_line_buffer) != 0)
1486 completion_changed_buffer = 1;
1487 else
1488 completion_changed_buffer = 0; */
1489 // free(saved_line_buffer);
1490 // }
1491 return EXIT_SUCCESS;
1492 }
1493