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