This source file includes following definitions.
- show_c_flags
- filename_completion_function
- username_completion_function
- variable_completion_function
- host_equal_func
- fetch_hosts
- hostname_completion_function
- command_completion_function
- match_compare
- completion_matches
- check_is_cd
- try_complete_commands_prepare
- try_complete_find_start_sign
- try_complete_all_possible
- insert_text
- complete_callback
- complete_engine
- try_complete
- complete_engine_fill_completions
- input_complete
- input_complete_free
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 #include <config.h>
34
35 #include <ctype.h>
36 #include <limits.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <dirent.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <pwd.h>
44 #include <unistd.h>
45
46 #include "lib/global.h"
47
48 #include "lib/tty/tty.h"
49 #include "lib/tty/key.h"
50 #include "lib/vfs/vfs.h"
51 #include "lib/strutil.h"
52 #include "lib/util.h"
53 #include "lib/widget.h"
54
55
56
57 #if !HAVE_DECL_ENVIRON
58 extern char **environ;
59 #endif
60
61
62
63
64 #ifdef DO_COMPLETION_DEBUG
65 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
66 #else
67 #define SHOW_C_CTX(func)
68 #endif
69
70 #define DO_INSERTION 1
71 #define DO_QUERY 2
72
73
74
75 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
76
77 typedef struct
78 {
79 size_t in_command_position;
80 char *word;
81 char *p;
82 char *q;
83 char *r;
84 gboolean is_cd;
85 input_complete_t flags;
86 } try_complete_automation_state_t;
87
88
89
90 MC_MOCKABLE GPtrArray *try_complete (char *text, int *lc_start, int *lc_end,
91 input_complete_t flags);
92 void complete_engine_fill_completions (WInput * in);
93
94
95
96 static WInput *input;
97 static int min_end;
98 static int start = 0;
99 static int end = 0;
100
101
102
103
104
105 #ifdef DO_COMPLETION_DEBUG
106
107
108
109 static const char *
110 show_c_flags (input_complete_t flags)
111 {
112 static char s_cf[] = "FHCVUDS";
113
114 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
115 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
116 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
117 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
118 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
119 s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
120 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
121
122 return s_cf;
123 }
124 #endif
125
126
127
128 static char *
129 filename_completion_function (const char *text, int state, input_complete_t flags)
130 {
131 static DIR *directory = NULL;
132 static char *filename = NULL;
133 static char *dirname = NULL;
134 static char *users_dirname = NULL;
135 static size_t filename_len = 0;
136 static vfs_path_t *dirname_vpath = NULL;
137
138 gboolean isdir = TRUE, isexec = FALSE;
139 struct vfs_dirent *entry = NULL;
140
141 SHOW_C_CTX ("filename_completion_function");
142
143 if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
144 {
145 char *u_text;
146 char *result;
147 char *e_result;
148
149 u_text = str_shell_unescape (text);
150
151 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
152 g_free (u_text);
153
154 e_result = str_shell_escape (result);
155 g_free (result);
156
157 return e_result;
158 }
159
160
161 if (state == 0)
162 {
163 const char *temp;
164
165 g_free (dirname);
166 g_free (filename);
167 g_free (users_dirname);
168 vfs_path_free (dirname_vpath, TRUE);
169
170 if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
171 {
172 filename = g_strdup (++temp);
173 dirname = g_strndup (text, temp - text);
174 }
175 else
176 {
177 dirname = g_strdup (".");
178 filename = g_strdup (text);
179 }
180
181
182
183
184 users_dirname = dirname;
185 dirname = tilde_expand (dirname);
186 canonicalize_pathname (dirname);
187 dirname_vpath = vfs_path_from_str (dirname);
188
189
190
191
192
193 directory = mc_opendir (dirname_vpath);
194 filename_len = strlen (filename);
195 }
196
197
198
199 while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
200 {
201 if (!str_is_valid_string (entry->d_name))
202 continue;
203
204
205
206 if (filename_len == 0)
207 {
208 if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
209 continue;
210 }
211 else
212 {
213
214
215 if (entry->d_name[0] != filename[0] || entry->d_len < filename_len
216 || strncmp (filename, entry->d_name, filename_len) != 0)
217 continue;
218 }
219
220 isdir = TRUE;
221 isexec = FALSE;
222
223 {
224 struct stat tempstat;
225 vfs_path_t *tmp_vpath;
226
227 tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
228
229
230 if (mc_stat (tmp_vpath, &tempstat) == 0)
231 {
232 uid_t my_uid;
233 gid_t my_gid;
234
235 my_uid = getuid ();
236 my_gid = getgid ();
237
238 if (!S_ISDIR (tempstat.st_mode))
239 {
240 isdir = FALSE;
241
242 if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
243 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
244 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
245 (tempstat.st_mode & 0001) != 0)
246 isexec = TRUE;
247 }
248 }
249 else
250 {
251
252 isdir = FALSE;
253 }
254 vfs_path_free (tmp_vpath, TRUE);
255 }
256
257 if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
258 break;
259 if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
260 break;
261 if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
262 break;
263 }
264
265 if (entry == NULL)
266 {
267 if (directory != NULL)
268 {
269 mc_closedir (directory);
270 directory = NULL;
271 }
272 MC_PTR_FREE (dirname);
273 vfs_path_free (dirname_vpath, TRUE);
274 dirname_vpath = NULL;
275 MC_PTR_FREE (filename);
276 MC_PTR_FREE (users_dirname);
277 return NULL;
278 }
279
280 {
281 GString *temp;
282
283 temp = g_string_sized_new (16);
284
285 if (users_dirname != NULL && !DIR_IS_DOT (users_dirname))
286 {
287 g_string_append (temp, users_dirname);
288
289
290 if (!IS_PATH_SEP (temp->str[temp->len - 1]))
291 g_string_append_c (temp, PATH_SEP);
292 }
293 g_string_append_len (temp, entry->d_name, entry->d_len);
294 if (isdir)
295 g_string_append_c (temp, PATH_SEP);
296
297 return g_string_free (temp, FALSE);
298 }
299 }
300
301
302
303
304
305 static char *
306 username_completion_function (const char *text, int state, input_complete_t flags)
307 {
308 static struct passwd *entry = NULL;
309 static size_t userlen = 0;
310
311 (void) flags;
312 SHOW_C_CTX ("username_completion_function");
313
314 if (text[0] == '\\' && text[1] == '~')
315 text++;
316 if (state == 0)
317 {
318 setpwent ();
319 userlen = strlen (text + 1);
320 }
321
322 while ((entry = getpwent ()) != NULL)
323 {
324
325 if (userlen == 0)
326 break;
327 if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
328 break;
329 }
330
331 if (entry != NULL)
332 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
333
334 endpwent ();
335 return NULL;
336 }
337
338
339
340
341
342 static char *
343 variable_completion_function (const char *text, int state, input_complete_t flags)
344 {
345 static char **env_p = NULL;
346 static gboolean isbrace = FALSE;
347 static size_t varlen = 0;
348 const char *p = NULL;
349
350 (void) flags;
351 SHOW_C_CTX ("variable_completion_function");
352
353 if (state == 0)
354 {
355 isbrace = (text[1] == '{');
356 varlen = strlen (text + 1 + isbrace);
357 env_p = environ;
358 }
359
360 while (*env_p != NULL)
361 {
362 p = strchr (*env_p, '=');
363 if (p != NULL && ((size_t) (p - *env_p) >= varlen)
364 && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
365 break;
366 env_p++;
367 }
368
369 if (*env_p == NULL)
370 return NULL;
371
372 {
373 GString *temp;
374
375 temp = g_string_new_len (*env_p, p - *env_p);
376
377 if (isbrace)
378 {
379 g_string_prepend_c (temp, '{');
380 g_string_append_c (temp, '}');
381 }
382 g_string_prepend_c (temp, '$');
383
384 env_p++;
385
386 return g_string_free (temp, FALSE);
387 }
388 }
389
390
391
392 static gboolean
393 host_equal_func (gconstpointer a, gconstpointer b)
394 {
395 return (strcmp ((const char *) a, (const char *) b) == 0);
396 }
397
398
399
400 static void
401 fetch_hosts (const char *filename, GPtrArray *hosts)
402 {
403 FILE *file;
404 char buffer[BUF_MEDIUM];
405 char *bi;
406
407 file = fopen (filename, "r");
408 if (file == NULL)
409 return;
410
411 while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
412 {
413
414 for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
415 ;
416
417
418 if (bi[0] == '#')
419 continue;
420
421
422 if (strncmp (bi, "$include ", 9) == 0)
423 {
424 char *includefile, *t;
425
426
427 for (includefile = bi + 9; includefile[0] != '\0' && whitespace (includefile[0]);
428 includefile++)
429 ;
430 t = includefile;
431
432
433 for (; t[0] != '\0' && !str_isspace (t); str_next_char (&t))
434 ;
435 *t = '\0';
436
437 fetch_hosts (includefile, hosts);
438 continue;
439 }
440
441
442 for (; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
443 ;
444
445
446 while (bi[0] != '\0' && bi[0] != '#')
447 {
448 char *lc_start, *name;
449
450 for (; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
451 ;
452 if (bi[0] == '#')
453 continue;
454
455 for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
456 ;
457
458 if (bi == lc_start)
459 continue;
460
461 name = g_strndup (lc_start, bi - lc_start);
462 if (!g_ptr_array_find_with_equal_func (hosts, name, host_equal_func, NULL))
463 g_ptr_array_add (hosts, name);
464 else
465 g_free (name);
466 }
467 }
468
469 fclose (file);
470 }
471
472
473
474 static char *
475 hostname_completion_function (const char *text, int state, input_complete_t flags)
476 {
477 static GPtrArray *hosts = NULL;
478 static unsigned int host_p = 0;
479 static size_t textstart = 0;
480 static size_t textlen = 0;
481
482 (void) flags;
483 SHOW_C_CTX ("hostname_completion_function");
484
485 if (state == 0)
486 {
487 const char *p;
488
489 if (hosts != NULL)
490 g_ptr_array_free (hosts, TRUE);
491 hosts = g_ptr_array_new_with_free_func (g_free);
492 p = getenv ("HOSTFILE");
493 fetch_hosts (p != NULL ? p : "/etc/hosts", hosts);
494 host_p = 0;
495 textstart = (*text == '@') ? 1 : 0;
496 textlen = strlen (text + textstart);
497 }
498
499 for (; host_p < hosts->len; host_p++)
500 {
501 if (textlen == 0)
502 break;
503 if (strncmp (text + textstart, g_ptr_array_index (hosts, host_p), textlen) == 0)
504 break;
505 }
506
507 if (host_p == hosts->len)
508 {
509 g_ptr_array_free (hosts, TRUE);
510 hosts = NULL;
511 return NULL;
512 }
513
514 {
515 GString *temp;
516
517 temp = g_string_sized_new (8);
518
519 if (textstart != 0)
520 g_string_append_c (temp, '@');
521 g_string_append (temp, g_ptr_array_index (hosts, host_p));
522 host_p++;
523
524 return g_string_free (temp, FALSE);
525 }
526 }
527
528
529
530
531
532
533
534
535
536 static char *
537 command_completion_function (const char *text, int state, input_complete_t flags)
538 {
539 static const char *path_end = NULL;
540 static gboolean isabsolute = FALSE;
541 static int phase = 0;
542 static size_t text_len = 0;
543 static const char *const *words = NULL;
544 static char *path = NULL;
545 static char *cur_path = NULL;
546 static char *cur_word = NULL;
547 static int init_state = 0;
548 static const char *const bash_reserved[] = {
549 "if", "then", "else", "elif", "fi", "case", "esac", "for",
550 "select", "while", "until", "do", "done", "in", "function", 0
551 };
552 static const char *const bash_builtins[] = {
553 "alias", "bg", "bind", "break", "builtin", "cd", "command",
554 "continue", "declare", "dirs", "echo", "enable", "eval",
555 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
556 "help", "history", "jobs", "kill", "let", "local", "logout",
557 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
558 "shift", "source", "suspend", "test", "times", "trap", "type",
559 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
560 };
561
562 char *u_text;
563 char *p, *found;
564
565 SHOW_C_CTX ("command_completion_function");
566
567 if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
568 return NULL;
569
570 u_text = str_shell_unescape (text);
571 flags &= ~INPUT_COMPLETE_SHELL_ESC;
572
573 if (state == 0)
574 {
575 isabsolute = strchr (u_text, PATH_SEP) != NULL;
576 if (!isabsolute)
577 {
578 words = bash_reserved;
579 phase = 0;
580 text_len = strlen (u_text);
581
582 if (path == NULL)
583 {
584 path = g_strdup (getenv ("PATH"));
585 if (path != NULL)
586 {
587 p = path;
588 path_end = strchr (p, '\0');
589 while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
590 *p++ = '\0';
591 }
592 }
593 }
594 }
595
596 if (isabsolute)
597 {
598 p = filename_completion_function (u_text, state, flags);
599
600 if (p != NULL)
601 {
602 char *temp_p = p;
603
604 p = str_shell_escape (p);
605 g_free (temp_p);
606 }
607
608 g_free (u_text);
609 return p;
610 }
611
612 found = NULL;
613 switch (phase)
614 {
615 case 0:
616 for (; *words != NULL; words++)
617 if (strncmp (*words, u_text, text_len) == 0)
618 {
619 g_free (u_text);
620 return g_strdup (*(words++));
621 }
622 phase++;
623 words = bash_builtins;
624 MC_FALLTHROUGH;
625 case 1:
626 for (; *words != NULL; words++)
627 if (strncmp (*words, u_text, text_len) == 0)
628 {
629 g_free (u_text);
630 return g_strdup (*(words++));
631 }
632 phase++;
633 if (path == NULL)
634 break;
635 cur_path = path;
636 cur_word = NULL;
637 MC_FALLTHROUGH;
638 case 2:
639 while (found == NULL)
640 {
641 if (cur_word == NULL)
642 {
643 char *expanded;
644
645 if (cur_path >= path_end)
646 break;
647 expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
648 cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
649 g_free (expanded);
650 cur_path = strchr (cur_path, '\0') + 1;
651 init_state = state;
652 }
653 found = filename_completion_function (cur_word, state - init_state, flags);
654 if (found == NULL)
655 MC_PTR_FREE (cur_word);
656 }
657 MC_FALLTHROUGH;
658 default:
659 break;
660 }
661
662 if (found == NULL)
663 MC_PTR_FREE (path);
664 else
665 {
666 p = strrchr (found, PATH_SEP);
667 if (p != NULL)
668 {
669 char *tmp = found;
670
671 found = str_shell_escape (p + 1);
672 g_free (tmp);
673 }
674 }
675
676 g_free (u_text);
677 return found;
678 }
679
680
681
682 static int
683 match_compare (gconstpointer a, gconstpointer b)
684 {
685 return strcmp (*(char *const *) a, *(char *const *) b);
686 }
687
688
689
690
691
692
693
694
695
696
697 static GPtrArray *
698 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
699 {
700 GPtrArray *match_list;
701 char *string;
702
703 match_list = g_ptr_array_new_with_free_func (g_free);
704
705 while ((string = entry_function (text, match_list->len, flags)) != NULL)
706 g_ptr_array_add (match_list, string);
707
708
709
710 if (match_list->len == 0)
711 {
712
713 g_ptr_array_free (match_list, TRUE);
714 return NULL;
715 }
716
717
718
719 if (match_list->len > 1)
720 {
721 size_t i, j;
722 size_t low = 4096;
723
724 g_ptr_array_sort (match_list, match_compare);
725
726
727
728
729 for (i = 0, j = 1; j < match_list->len;)
730 {
731 char *si, *sj, *mi;
732
733 si = g_ptr_array_index (match_list, i);
734 sj = g_ptr_array_index (match_list, j);
735 mi = si;
736
737 while (si[0] != '\0' && sj[0] != '\0')
738 {
739 char *ni, *nj;
740
741 ni = str_get_next_char (si);
742 nj = str_get_next_char (sj);
743
744 if (ni - si != nj - sj || strncmp (si, sj, ni - si) != 0)
745 break;
746
747 si = ni;
748 sj = nj;
749 }
750
751 if (si[0] == '\0' && sj[0] == '\0')
752 {
753
754 g_ptr_array_remove_index (match_list, j);
755 }
756 else
757 {
758 low = MIN (low, (size_t) (si - mi));
759
760 i++;
761 j++;
762 }
763 }
764
765 string = g_ptr_array_index (match_list, 0);
766 g_ptr_array_insert (match_list, 0, g_strndup (string, low));
767 }
768
769 return match_list;
770 }
771
772
773
774 static gboolean
775 check_is_cd (const char *text, int lc_start, input_complete_t flags)
776 {
777 const char *p, *q;
778
779 SHOW_C_CTX ("check_is_cd");
780
781 if ((flags & INPUT_COMPLETE_CD) == 0)
782 return FALSE;
783
784
785 p = text;
786 q = text + lc_start;
787 while (p < q && p[0] != '\0' && str_isspace (p))
788 str_cnext_char (&p);
789
790
791 return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
792 }
793
794
795
796 static void
797 try_complete_commands_prepare (try_complete_automation_state_t *state, char *text, int *lc_start)
798 {
799 const char *command_separator_chars = ";|&{(`";
800 char *ti;
801
802 if (*lc_start == 0)
803 ti = text;
804 else
805 {
806 ti = str_get_prev_char (&text[*lc_start]);
807 while (ti > text && whitespace (ti[0]))
808 str_prev_char (&ti);
809 }
810
811 if (ti == text)
812 state->in_command_position++;
813 else if (strchr (command_separator_chars, ti[0]) != NULL)
814 {
815 state->in_command_position++;
816 if (ti != text)
817 {
818 int this_char, prev_char;
819
820
821
822 this_char = ti[0];
823 prev_char = str_get_prev_char (ti)[0];
824
825
826 if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
827 || (this_char == '|' && prev_char == '>') || (ti != text
828 && str_get_prev_char (ti)[0] == '\\'))
829 state->in_command_position = 0;
830 }
831 }
832 }
833
834
835
836 static void
837 try_complete_find_start_sign (try_complete_automation_state_t *state)
838 {
839 if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
840 state->p = strrchr (state->word, '`');
841 if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
842 {
843 state->q = strrchr (state->word, '$');
844
845
846 if (str_is_char_escaped (state->word, state->q))
847 {
848
849 str_move (state->q - 1, state->q);
850
851 state->flags &= ~INPUT_COMPLETE_VARIABLES;
852 state->q = NULL;
853 }
854 }
855 if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
856 state->r = strrchr (state->word, '@');
857 if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
858 {
859 if (state->q > state->p)
860 state->p = str_get_next_char (state->q);
861 state->q = NULL;
862 }
863 }
864
865
866
867 static GPtrArray *
868 try_complete_all_possible (try_complete_automation_state_t *state, char *text, int *lc_start)
869 {
870 GPtrArray *matches = NULL;
871
872 if (state->in_command_position != 0)
873 {
874 SHOW_C_CTX ("try_complete:cmd_subst");
875 matches =
876 completion_matches (state->word, command_completion_function,
877 state->flags & (~INPUT_COMPLETE_FILENAMES));
878 }
879 else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
880 {
881 if (state->is_cd)
882 state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
883 SHOW_C_CTX ("try_complete:filename_subst_1");
884 matches = completion_matches (state->word, filename_completion_function, state->flags);
885
886 if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
887 {
888 state->q = text + *lc_start;
889 for (state->p = text;
890 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
891 str_next_char (&state->p))
892 ;
893 if (strncmp (state->p, "cd", 2) == 0)
894 for (state->p += 2;
895 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
896 str_next_char (&state->p))
897 ;
898 if (state->p == state->q)
899 {
900 char *cdpath_ref, *cdpath;
901 char c;
902
903 cdpath_ref = g_strdup (getenv ("CDPATH"));
904 cdpath = cdpath_ref;
905 c = (cdpath == NULL) ? '\0' : ':';
906
907 while (matches == NULL && c == ':')
908 {
909 char *s;
910
911 s = strchr (cdpath, ':');
912
913 if (s == NULL)
914 s = strchr (cdpath, '\0');
915 c = *s;
916 *s = '\0';
917 if (*cdpath != '\0')
918 {
919 state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
920 SHOW_C_CTX ("try_complete:filename_subst_2");
921 matches =
922 completion_matches (state->r, filename_completion_function,
923 state->flags);
924 g_free (state->r);
925 }
926 *s = c;
927 cdpath = str_get_next_char (s);
928 }
929 g_free (cdpath_ref);
930 }
931 }
932 }
933 return matches;
934 }
935
936
937
938 static gboolean
939 insert_text (WInput *in, const char *text, ssize_t size)
940 {
941 size_t text_len;
942 int buff_len;
943 ssize_t new_size;
944
945 text_len = strlen (text);
946 buff_len = str_length (in->buffer->str);
947 if (size < 0)
948 size = (ssize_t) text_len;
949 else
950 size = MIN (size, (ssize_t) text_len);
951
952 new_size = size + start - end;
953 if (new_size != 0)
954 {
955
956
957 size_t tail_len;
958
959 tail_len = in->buffer->len - end;
960 if (tail_len != 0)
961 {
962 char *tail;
963 size_t hole_end;
964
965 tail = g_strndup (in->buffer->str + end, tail_len);
966
967 hole_end = end + new_size;
968 if (in->buffer->len < hole_end)
969 g_string_set_size (in->buffer, hole_end + tail_len);
970
971 g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
972
973 g_free (tail);
974 }
975 }
976
977 g_string_overwrite_len (in->buffer, start, text, size);
978
979 in->point += str_length (in->buffer->str) - buff_len;
980 input_update (in, TRUE);
981 end += new_size;
982
983 return new_size != 0;
984 }
985
986
987
988 static cb_ret_t
989 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
990 {
991 static int bl = 0;
992
993 WGroup *g = GROUP (w);
994 WDialog *h = DIALOG (w);
995
996 switch (msg)
997 {
998 case MSG_KEY:
999 switch (parm)
1000 {
1001 case KEY_LEFT:
1002 case KEY_RIGHT:
1003 bl = 0;
1004 h->ret_value = 0;
1005 dlg_close (h);
1006 return MSG_HANDLED;
1007
1008 case KEY_BACKSPACE:
1009 bl = 0;
1010
1011 if (end == 0)
1012 {
1013 h->ret_value = 0;
1014 dlg_close (h);
1015 }
1016
1017 else if (end == min_end)
1018 {
1019 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1020 input_handle_char (input, parm);
1021 h->ret_value = B_USER;
1022 dlg_close (h);
1023 }
1024 else
1025 {
1026 int new_end;
1027 int i;
1028 GList *e;
1029
1030 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1031
1032 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1033 e != NULL; i++, e = g_list_next (e))
1034 {
1035 WLEntry *le = LENTRY (e->data);
1036
1037 if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1038 {
1039 listbox_set_current (LISTBOX (g->current->data), i);
1040 end = new_end;
1041 input_handle_char (input, parm);
1042 widget_draw (WIDGET (g->current->data));
1043 break;
1044 }
1045 }
1046 }
1047 return MSG_HANDLED;
1048
1049 default:
1050 if (parm < 32 || parm > 255)
1051 {
1052 bl = 0;
1053 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1054 return MSG_NOT_HANDLED;
1055
1056 if (end == min_end)
1057 return MSG_HANDLED;
1058
1059
1060 h->ret_value = B_USER;
1061 dlg_close (h);
1062 }
1063 else
1064 {
1065 static char buff[MB_LEN_MAX] = "";
1066 GList *e;
1067 int i;
1068 int need_redraw = 0;
1069 int low = 4096;
1070 char *last_text = NULL;
1071
1072 buff[bl++] = (char) parm;
1073 buff[bl] = '\0';
1074
1075 switch (str_is_valid_char (buff, bl))
1076 {
1077 case -1:
1078 bl = 0;
1079 MC_FALLTHROUGH;
1080 case -2:
1081 return MSG_HANDLED;
1082 default:
1083 break;
1084 }
1085
1086 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1087 e != NULL; i++, e = g_list_next (e))
1088 {
1089 WLEntry *le = LENTRY (e->data);
1090
1091 if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1092 && strncmp (le->text + end - start, buff, bl) == 0)
1093 {
1094 if (need_redraw == 0)
1095 {
1096 need_redraw = 1;
1097 listbox_set_current (LISTBOX (g->current->data), i);
1098 last_text = le->text;
1099 }
1100 else
1101 {
1102 char *si, *sl;
1103 int si_num = 0;
1104 int sl_num = 0;
1105
1106
1107 for (si = le->text + start; si < le->text + end;
1108 str_next_char (&si), si_num++)
1109 ;
1110 for (sl = last_text + start; sl < last_text + end;
1111 str_next_char (&sl), sl_num++)
1112 ;
1113
1114
1115 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1116 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1117
1118 while (si[0] != '\0' && sl[0] != '\0')
1119 {
1120 char *nexti, *nextl;
1121
1122 nexti = str_get_next_char (si);
1123 nextl = str_get_next_char (sl);
1124
1125 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1126 break;
1127
1128 si = nexti;
1129 sl = nextl;
1130
1131 si_num++;
1132 }
1133
1134 last_text = le->text;
1135
1136 si = &last_text[str_offset_to_pos (last_text, si_num)];
1137 if (low > si - last_text)
1138 low = si - last_text;
1139
1140 need_redraw = 2;
1141 }
1142 }
1143 }
1144
1145 if (need_redraw == 2)
1146 {
1147 insert_text (input, last_text, low);
1148 widget_draw (WIDGET (g->current->data));
1149 }
1150 else if (need_redraw == 1)
1151 {
1152 h->ret_value = B_ENTER;
1153 dlg_close (h);
1154 }
1155 bl = 0;
1156 }
1157 }
1158 return MSG_HANDLED;
1159
1160 default:
1161 return dlg_default_callback (w, sender, msg, parm, data);
1162 }
1163 }
1164
1165
1166
1167
1168 static gboolean
1169 complete_engine (WInput *in, int what_to_do)
1170 {
1171 if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1172 input_complete_free (in);
1173
1174 if (in->completions == NULL)
1175 complete_engine_fill_completions (in);
1176
1177 if (in->completions == NULL)
1178 tty_beep ();
1179 else
1180 {
1181 if ((what_to_do & DO_INSERTION) != 0
1182 || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1183 {
1184 const char *lc_complete;
1185
1186 lc_complete = g_ptr_array_index (in->completions, 0);
1187 if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1188 tty_beep ();
1189 else
1190 input_complete_free (in);
1191 }
1192
1193 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1194 {
1195 int maxlen = 0;
1196 int i;
1197 size_t k;
1198 int count;
1199 int x, y, w, h;
1200 int start_x, start_y;
1201 char *q;
1202 WDialog *complete_dlg;
1203 WListbox *complete_list;
1204
1205 for (k = 1; k < in->completions->len; k++)
1206 {
1207 q = g_ptr_array_index (in->completions, k);
1208 i = str_term_width1 (q);
1209 maxlen = MAX (maxlen, i);
1210 }
1211
1212 count = in->completions->len - 1;
1213 start_x = WIDGET (in)->rect.x;
1214 start_y = WIDGET (in)->rect.y;
1215 if (start_y - 2 >= count)
1216 {
1217 y = start_y - 2 - count;
1218 h = 2 + count;
1219 }
1220 else if (start_y >= LINES - start_y - 1)
1221 {
1222 y = 0;
1223 h = start_y;
1224 }
1225 else
1226 {
1227 y = start_y + 1;
1228 h = LINES - start_y - 1;
1229 }
1230 x = start - in->term_first_shown - 2 + start_x;
1231 w = maxlen + 4;
1232 if (x + w > COLS)
1233 x = COLS - w;
1234 if (x < 0)
1235 x = 0;
1236 if (x + w > COLS)
1237 w = COLS;
1238
1239 input = in;
1240 min_end = end;
1241
1242 complete_dlg =
1243 dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE,
1244 dialog_colors, complete_callback, NULL, "[Completion]", NULL);
1245 complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1246 group_add_widget (GROUP (complete_dlg), complete_list);
1247
1248 for (k = 1; k < in->completions->len; k++)
1249 {
1250 q = g_ptr_array_index (in->completions, k);
1251 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1252 }
1253
1254 i = dlg_run (complete_dlg);
1255 q = NULL;
1256 if (i == B_ENTER)
1257 {
1258 listbox_get_current (complete_list, &q, NULL);
1259 if (q != NULL)
1260 insert_text (in, q, -1);
1261 }
1262 if (q != NULL || end != min_end)
1263 input_complete_free (in);
1264 widget_destroy (WIDGET (complete_dlg));
1265
1266
1267 return (i == B_USER);
1268 }
1269 }
1270
1271 return FALSE;
1272 }
1273
1274
1275
1276
1277
1278
1279 GPtrArray *
1280 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1281 {
1282 try_complete_automation_state_t state;
1283 GPtrArray *matches = NULL;
1284
1285 memset (&state, 0, sizeof (state));
1286 state.flags = flags;
1287
1288 SHOW_C_CTX ("try_complete");
1289 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1290
1291 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1292
1293
1294
1295
1296
1297 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1298 try_complete_commands_prepare (&state, text, lc_start);
1299
1300 try_complete_find_start_sign (&state);
1301
1302
1303 if (state.p > state.q && state.p > state.r)
1304 {
1305 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1306 matches = completion_matches (str_cget_next_char (state.p),
1307 command_completion_function,
1308 state.flags & (~INPUT_COMPLETE_FILENAMES));
1309 if (matches != NULL)
1310 *lc_start += str_get_next_char (state.p) - state.word;
1311 }
1312
1313
1314 else if (state.q > state.p && state.q > state.r)
1315 {
1316 SHOW_C_CTX ("try_complete:var_subst");
1317 matches = completion_matches (state.q, variable_completion_function, state.flags);
1318 if (matches != NULL)
1319 *lc_start += state.q - state.word;
1320 }
1321
1322
1323
1324 else if (state.r > state.p && state.r > state.q)
1325 {
1326 SHOW_C_CTX ("try_complete:host_subst");
1327 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1328 if (matches != NULL)
1329 *lc_start += state.r - state.word;
1330 }
1331
1332
1333
1334 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1335 && strchr (state.word, PATH_SEP) == NULL)
1336 {
1337 SHOW_C_CTX ("try_complete:user_subst");
1338 matches = completion_matches (state.word, username_completion_function, state.flags);
1339 }
1340
1341
1342
1343
1344 if (matches == NULL)
1345 matches = try_complete_all_possible (&state, text, lc_start);
1346
1347
1348 if (matches == NULL)
1349 {
1350 state.in_command_position = 0;
1351 matches = try_complete_all_possible (&state, text, lc_start);
1352 }
1353
1354 g_free (state.word);
1355
1356 if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0 &&
1357 (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1358 {
1359
1360 size_t i;
1361
1362 for (i = 0; i < matches->len; i++)
1363 {
1364 char *p;
1365
1366 p = g_ptr_array_index (matches, i);
1367
1368
1369 g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1370 g_free (p);
1371 }
1372 }
1373
1374 return matches;
1375 }
1376
1377
1378
1379 void
1380 complete_engine_fill_completions (WInput *in)
1381 {
1382 char *s;
1383 const char *word_separators;
1384
1385 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1386
1387 end = str_offset_to_pos (in->buffer->str, in->point);
1388
1389 s = in->buffer->str;
1390 if (in->point != 0)
1391 {
1392
1393 size_t i;
1394
1395 for (i = in->point - 1; i > 0; i--)
1396 str_next_char (&s);
1397 }
1398
1399 for (; s >= in->buffer->str; str_prev_char (&s))
1400 {
1401 start = s - in->buffer->str;
1402 if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1403 break;
1404 }
1405
1406 if (start < end)
1407 {
1408 str_next_char (&s);
1409 start = s - in->buffer->str;
1410 }
1411
1412 in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1413 }
1414
1415
1416
1417
1418 void
1419 input_complete (WInput *in)
1420 {
1421 int engine_flags;
1422
1423 if (!str_is_valid_string (in->buffer->str))
1424 return;
1425
1426 if (in->completions != NULL)
1427 engine_flags = DO_QUERY;
1428 else
1429 {
1430 engine_flags = DO_INSERTION;
1431
1432 if (mc_global.widget.show_all_if_ambiguous)
1433 engine_flags |= DO_QUERY;
1434 }
1435
1436 while (complete_engine (in, engine_flags))
1437 ;
1438 }
1439
1440
1441
1442 void
1443 input_complete_free (WInput *in)
1444 {
1445 if (in->completions != NULL)
1446 {
1447 g_ptr_array_free (in->completions, TRUE);
1448 in->completions = NULL;
1449 }
1450 }
1451
1452