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) \
66 fprintf (stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags (flags))
67 #else
68 # define SHOW_C_CTX(func)
69 #endif
70
71 #define DO_INSERTION 1
72 #define DO_QUERY 2
73
74
75
76 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
77
78 typedef struct
79 {
80 size_t in_command_position;
81 char *word;
82 char *p;
83 char *q;
84 char *r;
85 gboolean is_cd;
86 input_complete_t flags;
87 } try_complete_automation_state_t;
88
89
90
91 MC_MOCKABLE GPtrArray *try_complete (char *text, int *lc_start, int *lc_end,
92 input_complete_t flags);
93 void complete_engine_fill_completions (WInput *in);
94
95
96
97 static WInput *input;
98 static int min_end;
99 static int start = 0;
100 static int end = 0;
101
102
103
104
105
106 #ifdef DO_COMPLETION_DEBUG
107
108
109
110 static const char *
111 show_c_flags (input_complete_t flags)
112 {
113 static char s_cf[] = "FHCVUDS";
114
115 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
116 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
117 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
118 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
119 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
120 s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
121 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
122
123 return s_cf;
124 }
125 #endif
126
127
128
129 static char *
130 filename_completion_function (const char *text, int state, input_complete_t flags)
131 {
132 static DIR *directory = NULL;
133 static char *filename = NULL;
134 static char *dirname = NULL;
135 static char *users_dirname = NULL;
136 static size_t filename_len = 0;
137 static vfs_path_t *dirname_vpath = NULL;
138
139 gboolean isdir = TRUE, isexec = FALSE;
140 struct vfs_dirent *entry = NULL;
141
142 SHOW_C_CTX ("filename_completion_function");
143
144 if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
145 {
146 char *u_text;
147 char *result;
148 char *e_result;
149
150 u_text = str_shell_unescape (text);
151
152 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
153 g_free (u_text);
154
155 e_result = str_shell_escape (result);
156 g_free (result);
157
158 return e_result;
159 }
160
161
162 if (state == 0)
163 {
164 const char *temp;
165
166 g_free (dirname);
167 g_free (filename);
168 g_free (users_dirname);
169 vfs_path_free (dirname_vpath, TRUE);
170
171 if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
172 {
173 filename = g_strdup (++temp);
174 dirname = g_strndup (text, temp - text);
175 }
176 else
177 {
178 dirname = g_strdup (".");
179 filename = g_strdup (text);
180 }
181
182
183
184
185 users_dirname = dirname;
186 dirname = tilde_expand (dirname);
187 canonicalize_pathname (dirname);
188 dirname_vpath = vfs_path_from_str (dirname);
189
190
191
192
193
194 directory = mc_opendir (dirname_vpath);
195 filename_len = strlen (filename);
196 }
197
198
199
200 while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
201 {
202 if (!str_is_valid_string (entry->d_name))
203 continue;
204
205
206
207 if (filename_len == 0)
208 {
209 if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
210 continue;
211 }
212 else
213 {
214
215
216 if (entry->d_name[0] != filename[0] || entry->d_len < filename_len
217 || strncmp (filename, entry->d_name, filename_len) != 0)
218 continue;
219 }
220
221 isdir = TRUE;
222 isexec = FALSE;
223
224 {
225 struct stat tempstat;
226 vfs_path_t *tmp_vpath;
227
228 tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
229
230
231 if (mc_stat (tmp_vpath, &tempstat) == 0)
232 {
233 uid_t my_uid;
234 gid_t my_gid;
235
236 my_uid = getuid ();
237 my_gid = getgid ();
238
239 if (!S_ISDIR (tempstat.st_mode))
240 {
241 isdir = FALSE;
242
243 if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0)
244 || (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0)
245 || (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0)
246 || (tempstat.st_mode & 0001) != 0)
247 isexec = TRUE;
248 }
249 }
250 else
251 {
252
253 isdir = FALSE;
254 }
255 vfs_path_free (tmp_vpath, TRUE);
256 }
257
258 if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
259 break;
260 if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
261 break;
262 if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
263 break;
264 }
265
266 if (entry == NULL)
267 {
268 if (directory != NULL)
269 {
270 mc_closedir (directory);
271 directory = NULL;
272 }
273 MC_PTR_FREE (dirname);
274 vfs_path_free (dirname_vpath, TRUE);
275 dirname_vpath = NULL;
276 MC_PTR_FREE (filename);
277 MC_PTR_FREE (users_dirname);
278 return NULL;
279 }
280
281 {
282 GString *temp;
283
284 temp = g_string_sized_new (16);
285
286 if (users_dirname != NULL && !DIR_IS_DOT (users_dirname))
287 {
288 g_string_append (temp, users_dirname);
289
290
291 if (!IS_PATH_SEP (temp->str[temp->len - 1]))
292 g_string_append_c (temp, PATH_SEP);
293 }
294 g_string_append_len (temp, entry->d_name, entry->d_len);
295 if (isdir)
296 g_string_append_c (temp, PATH_SEP);
297
298 return g_string_free (temp, FALSE);
299 }
300 }
301
302
303
304
305
306 static char *
307 username_completion_function (const char *text, int state, input_complete_t flags)
308 {
309 static struct passwd *entry = NULL;
310 static size_t userlen = 0;
311
312 (void) flags;
313 SHOW_C_CTX ("username_completion_function");
314
315 if (text[0] == '\\' && text[1] == '~')
316 text++;
317 if (state == 0)
318 {
319 setpwent ();
320 userlen = strlen (text + 1);
321 }
322
323 while ((entry = getpwent ()) != NULL)
324 {
325
326 if (userlen == 0)
327 break;
328 if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
329 break;
330 }
331
332 if (entry != NULL)
333 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
334
335 endpwent ();
336 return NULL;
337 }
338
339
340
341
342
343 static char *
344 variable_completion_function (const char *text, int state, input_complete_t flags)
345 {
346 static char **env_p = NULL;
347 static gboolean isbrace = FALSE;
348 static size_t varlen = 0;
349 const char *p = NULL;
350
351 (void) flags;
352 SHOW_C_CTX ("variable_completion_function");
353
354 if (state == 0)
355 {
356 isbrace = (text[1] == '{');
357 varlen = strlen (text + 1 + isbrace);
358 env_p = environ;
359 }
360
361 while (*env_p != NULL)
362 {
363 p = strchr (*env_p, '=');
364 if (p != NULL && ((size_t) (p - *env_p) >= varlen)
365 && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
366 break;
367 env_p++;
368 }
369
370 if (*env_p == NULL)
371 return NULL;
372
373 {
374 GString *temp;
375
376 temp = g_string_new_len (*env_p, p - *env_p);
377
378 if (isbrace)
379 {
380 g_string_prepend_c (temp, '{');
381 g_string_append_c (temp, '}');
382 }
383 g_string_prepend_c (temp, '$');
384
385 env_p++;
386
387 return g_string_free (temp, FALSE);
388 }
389 }
390
391
392
393 static gboolean
394 host_equal_func (gconstpointer a, gconstpointer b)
395 {
396 return (strcmp ((const char *) a, (const char *) b) == 0);
397 }
398
399
400
401 static void
402 fetch_hosts (const char *filename, GPtrArray *hosts)
403 {
404 FILE *file;
405 char buffer[BUF_MEDIUM];
406 char *bi;
407
408 file = fopen (filename, "r");
409 if (file == NULL)
410 return;
411
412 while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
413 {
414
415 for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
416 ;
417
418
419 if (bi[0] == '#')
420 continue;
421
422
423 if (strncmp (bi, "$include ", 9) == 0)
424 {
425 char *includefile, *t;
426
427
428 for (includefile = bi + 9; includefile[0] != '\0' && whitespace (includefile[0]);
429 includefile++)
430 ;
431 t = includefile;
432
433
434 for (; t[0] != '\0' && !str_isspace (t); str_next_char (&t))
435 ;
436 *t = '\0';
437
438 fetch_hosts (includefile, hosts);
439 continue;
440 }
441
442
443 for (; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
444 ;
445
446
447 while (bi[0] != '\0' && bi[0] != '#')
448 {
449 char *lc_start, *name;
450
451 for (; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
452 ;
453 if (bi[0] == '#')
454 continue;
455
456 for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
457 ;
458
459 if (bi == lc_start)
460 continue;
461
462 name = g_strndup (lc_start, bi - lc_start);
463 if (!g_ptr_array_find_with_equal_func (hosts, name, host_equal_func, NULL))
464 g_ptr_array_add (hosts, name);
465 else
466 g_free (name);
467 }
468 }
469
470 fclose (file);
471 }
472
473
474
475 static char *
476 hostname_completion_function (const char *text, int state, input_complete_t flags)
477 {
478 static GPtrArray *hosts = NULL;
479 static unsigned int host_p = 0;
480 static size_t textstart = 0;
481 static size_t textlen = 0;
482
483 (void) flags;
484 SHOW_C_CTX ("hostname_completion_function");
485
486 if (state == 0)
487 {
488 const char *p;
489
490 if (hosts != NULL)
491 g_ptr_array_free (hosts, TRUE);
492 hosts = g_ptr_array_new_with_free_func (g_free);
493 p = getenv ("HOSTFILE");
494 fetch_hosts (p != NULL ? p : "/etc/hosts", hosts);
495 host_p = 0;
496 textstart = (*text == '@') ? 1 : 0;
497 textlen = strlen (text + textstart);
498 }
499
500 for (; host_p < hosts->len; host_p++)
501 {
502 if (textlen == 0)
503 break;
504 if (strncmp (text + textstart, g_ptr_array_index (hosts, host_p), textlen) == 0)
505 break;
506 }
507
508 if (host_p == hosts->len)
509 {
510 g_ptr_array_free (hosts, TRUE);
511 hosts = NULL;
512 return NULL;
513 }
514
515 {
516 GString *temp;
517
518 temp = g_string_sized_new (8);
519
520 if (textstart != 0)
521 g_string_append_c (temp, '@');
522 g_string_append (temp, g_ptr_array_index (hosts, host_p));
523 host_p++;
524
525 return g_string_free (temp, FALSE);
526 }
527 }
528
529
530
531
532
533
534
535
536
537 static char *
538 command_completion_function (const char *text, int state, input_complete_t flags)
539 {
540 static const char *path_end = NULL;
541 static gboolean isabsolute = FALSE;
542 static int phase = 0;
543 static size_t text_len = 0;
544 static const char *const *words = NULL;
545 static char *path = NULL;
546 static char *cur_path = NULL;
547 static char *cur_word = NULL;
548 static int init_state = 0;
549 static const char *const bash_reserved[] = {
550 "if", "then", "else", "elif", "fi", "case", "esac", "for",
551 "select", "while", "until", "do", "done", "in", "function", 0,
552 };
553 static const char *const bash_builtins[] = {
554 "alias", "bg", "bind", "break", "builtin", "cd", "command", "continue",
555 "declare", "dirs", "echo", "enable", "eval", "exec", "exit", "export",
556 "fc", "fg", "getopts", "hash", "help", "history", "jobs", "kill",
557 "let", "local", "logout", "popd", "pushd", "pwd", "read", "readonly",
558 "return", "set", "shift", "source", "suspend", "test", "times", "trap",
559 "type", "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 == '>')
828 || (ti != text && 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 = completion_matches (state->word, command_completion_function,
876 state->flags & (~INPUT_COMPLETE_FILENAMES));
877 }
878 else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
879 {
880 if (state->is_cd)
881 state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
882 SHOW_C_CTX ("try_complete:filename_subst_1");
883 matches = completion_matches (state->word, filename_completion_function, state->flags);
884
885 if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
886 {
887 state->q = text + *lc_start;
888 for (state->p = text;
889 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
890 str_next_char (&state->p))
891 ;
892 if (strncmp (state->p, "cd", 2) == 0)
893 for (state->p += 2;
894 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
895 str_next_char (&state->p))
896 ;
897 if (state->p == state->q)
898 {
899 char *cdpath_ref, *cdpath;
900 char c;
901
902 cdpath_ref = g_strdup (getenv ("CDPATH"));
903 cdpath = cdpath_ref;
904 c = (cdpath == NULL) ? '\0' : ':';
905
906 while (matches == NULL && c == ':')
907 {
908 char *s;
909
910 s = strchr (cdpath, ':');
911
912 if (s == NULL)
913 s = strchr (cdpath, '\0');
914 c = *s;
915 *s = '\0';
916 if (*cdpath != '\0')
917 {
918 state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
919 SHOW_C_CTX ("try_complete:filename_subst_2");
920 matches = completion_matches (state->r, filename_completion_function,
921 state->flags);
922 g_free (state->r);
923 }
924 *s = c;
925 cdpath = str_get_next_char (s);
926 }
927 g_free (cdpath_ref);
928 }
929 }
930 }
931 return matches;
932 }
933
934
935
936 static gboolean
937 insert_text (WInput *in, const char *text, ssize_t size)
938 {
939 size_t text_len;
940 int buff_len;
941 ssize_t new_size;
942
943 text_len = strlen (text);
944 buff_len = str_length (in->buffer->str);
945 if (size < 0)
946 size = (ssize_t) text_len;
947 else
948 size = MIN (size, (ssize_t) text_len);
949
950 new_size = size + start - end;
951 if (new_size != 0)
952 {
953
954
955 size_t tail_len;
956
957 tail_len = in->buffer->len - end;
958 if (tail_len != 0)
959 {
960 char *tail;
961 size_t hole_end;
962
963 tail = g_strndup (in->buffer->str + end, tail_len);
964
965 hole_end = end + new_size;
966 if (in->buffer->len < hole_end)
967 g_string_set_size (in->buffer, hole_end + tail_len);
968
969 g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
970
971 g_free (tail);
972 }
973 }
974
975 g_string_overwrite_len (in->buffer, start, text, size);
976
977 in->point += str_length (in->buffer->str) - buff_len;
978 input_update (in, TRUE);
979 end += new_size;
980
981 return new_size != 0;
982 }
983
984
985
986 static cb_ret_t
987 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
988 {
989 static int bl = 0;
990
991 WGroup *g = GROUP (w);
992 WDialog *h = DIALOG (w);
993
994 switch (msg)
995 {
996 case MSG_KEY:
997 switch (parm)
998 {
999 case KEY_LEFT:
1000 case KEY_RIGHT:
1001 bl = 0;
1002 h->ret_value = 0;
1003 dlg_close (h);
1004 return MSG_HANDLED;
1005
1006 case KEY_BACKSPACE:
1007 bl = 0;
1008
1009 if (end == 0)
1010 {
1011 h->ret_value = 0;
1012 dlg_close (h);
1013 }
1014
1015 else if (end == min_end)
1016 {
1017 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1018 input_handle_char (input, parm);
1019 h->ret_value = B_USER;
1020 dlg_close (h);
1021 }
1022 else
1023 {
1024 int new_end;
1025 int i;
1026 GList *e;
1027
1028 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1029
1030 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1031 i++, e = g_list_next (e))
1032 {
1033 WLEntry *le = LENTRY (e->data);
1034
1035 if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1036 {
1037 listbox_set_current (LISTBOX (g->current->data), i);
1038 end = new_end;
1039 input_handle_char (input, parm);
1040 widget_draw (WIDGET (g->current->data));
1041 break;
1042 }
1043 }
1044 }
1045 return MSG_HANDLED;
1046
1047 default:
1048 if (parm < 32 || parm > 255)
1049 {
1050 bl = 0;
1051 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1052 return MSG_NOT_HANDLED;
1053
1054 if (end == min_end)
1055 return MSG_HANDLED;
1056
1057
1058 h->ret_value = B_USER;
1059 dlg_close (h);
1060 }
1061 else
1062 {
1063 static char buff[MB_LEN_MAX] = "";
1064 GList *e;
1065 int i;
1066 int need_redraw = 0;
1067 int low = 4096;
1068 char *last_text = NULL;
1069
1070 buff[bl++] = (char) parm;
1071 buff[bl] = '\0';
1072
1073 switch (str_is_valid_char (buff, bl))
1074 {
1075 case -1:
1076 bl = 0;
1077 MC_FALLTHROUGH;
1078 case -2:
1079 return MSG_HANDLED;
1080 default:
1081 break;
1082 }
1083
1084 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1085 i++, e = g_list_next (e))
1086 {
1087 WLEntry *le = LENTRY (e->data);
1088
1089 if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1090 && strncmp (le->text + end - start, buff, bl) == 0)
1091 {
1092 if (need_redraw == 0)
1093 {
1094 need_redraw = 1;
1095 listbox_set_current (LISTBOX (g->current->data), i);
1096 last_text = le->text;
1097 }
1098 else
1099 {
1100 char *si, *sl;
1101 int si_num = 0;
1102 int sl_num = 0;
1103
1104
1105 for (si = le->text + start; si < le->text + end;
1106 str_next_char (&si), si_num++)
1107 ;
1108 for (sl = last_text + start; sl < last_text + end;
1109 str_next_char (&sl), sl_num++)
1110 ;
1111
1112
1113 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1114 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1115
1116 while (si[0] != '\0' && sl[0] != '\0')
1117 {
1118 char *nexti, *nextl;
1119
1120 nexti = str_get_next_char (si);
1121 nextl = str_get_next_char (sl);
1122
1123 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1124 break;
1125
1126 si = nexti;
1127 sl = nextl;
1128
1129 si_num++;
1130 }
1131
1132 last_text = le->text;
1133
1134 si = &last_text[str_offset_to_pos (last_text, si_num)];
1135 if (low > si - last_text)
1136 low = si - last_text;
1137
1138 need_redraw = 2;
1139 }
1140 }
1141 }
1142
1143 if (need_redraw == 2)
1144 {
1145 insert_text (input, last_text, low);
1146 widget_draw (WIDGET (g->current->data));
1147 }
1148 else if (need_redraw == 1)
1149 {
1150 h->ret_value = B_ENTER;
1151 dlg_close (h);
1152 }
1153 bl = 0;
1154 }
1155 }
1156 return MSG_HANDLED;
1157
1158 default:
1159 return dlg_default_callback (w, sender, msg, parm, data);
1160 }
1161 }
1162
1163
1164
1165
1166 static gboolean
1167 complete_engine (WInput *in, int what_to_do)
1168 {
1169 if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1170 input_complete_free (in);
1171
1172 if (in->completions == NULL)
1173 complete_engine_fill_completions (in);
1174
1175 if (in->completions == NULL)
1176 tty_beep ();
1177 else
1178 {
1179 if ((what_to_do & DO_INSERTION) != 0
1180 || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1181 {
1182 const char *lc_complete;
1183
1184 lc_complete = g_ptr_array_index (in->completions, 0);
1185 if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1186 tty_beep ();
1187 else
1188 input_complete_free (in);
1189 }
1190
1191 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1192 {
1193 int maxlen = 0;
1194 int i;
1195 size_t k;
1196 int count;
1197 int x, y, w, h;
1198 int start_x, start_y;
1199 char *q;
1200 WDialog *complete_dlg;
1201 WListbox *complete_list;
1202
1203 for (k = 1; k < in->completions->len; k++)
1204 {
1205 q = g_ptr_array_index (in->completions, k);
1206 i = str_term_width1 (q);
1207 maxlen = MAX (maxlen, i);
1208 }
1209
1210 count = in->completions->len - 1;
1211 start_x = WIDGET (in)->rect.x;
1212 start_y = WIDGET (in)->rect.y;
1213 if (start_y - 2 >= count)
1214 {
1215 y = start_y - 2 - count;
1216 h = 2 + count;
1217 }
1218 else if (start_y >= LINES - start_y - 1)
1219 {
1220 y = 0;
1221 h = start_y;
1222 }
1223 else
1224 {
1225 y = start_y + 1;
1226 h = LINES - start_y - 1;
1227 }
1228 x = start - in->term_first_shown - 2 + start_x;
1229 w = maxlen + 4;
1230 if (x + w > COLS)
1231 x = COLS - w;
1232 if (x < 0)
1233 x = 0;
1234 if (x + w > COLS)
1235 w = COLS;
1236
1237 input = in;
1238 min_end = end;
1239
1240 complete_dlg = dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE, dialog_colors,
1241 complete_callback, NULL, "[Completion]", NULL);
1242 complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1243 group_add_widget (GROUP (complete_dlg), complete_list);
1244
1245 for (k = 1; k < in->completions->len; k++)
1246 {
1247 q = g_ptr_array_index (in->completions, k);
1248 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1249 }
1250
1251 i = dlg_run (complete_dlg);
1252 q = NULL;
1253 if (i == B_ENTER)
1254 {
1255 listbox_get_current (complete_list, &q, NULL);
1256 if (q != NULL)
1257 insert_text (in, q, -1);
1258 }
1259 if (q != NULL || end != min_end)
1260 input_complete_free (in);
1261 widget_destroy (WIDGET (complete_dlg));
1262
1263
1264 return (i == B_USER);
1265 }
1266 }
1267
1268 return FALSE;
1269 }
1270
1271
1272
1273
1274
1275
1276 GPtrArray *
1277 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1278 {
1279 try_complete_automation_state_t state;
1280 GPtrArray *matches = NULL;
1281
1282 memset (&state, 0, sizeof (state));
1283 state.flags = flags;
1284
1285 SHOW_C_CTX ("try_complete");
1286 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1287
1288 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1289
1290
1291
1292
1293
1294 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1295 try_complete_commands_prepare (&state, text, lc_start);
1296
1297 try_complete_find_start_sign (&state);
1298
1299
1300 if (state.p > state.q && state.p > state.r)
1301 {
1302 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1303 matches = completion_matches (str_cget_next_char (state.p), command_completion_function,
1304 state.flags & (~INPUT_COMPLETE_FILENAMES));
1305 if (matches != NULL)
1306 *lc_start += str_get_next_char (state.p) - state.word;
1307 }
1308
1309
1310 else if (state.q > state.p && state.q > state.r)
1311 {
1312 SHOW_C_CTX ("try_complete:var_subst");
1313 matches = completion_matches (state.q, variable_completion_function, state.flags);
1314 if (matches != NULL)
1315 *lc_start += state.q - state.word;
1316 }
1317
1318
1319
1320 else if (state.r > state.p && state.r > state.q)
1321 {
1322 SHOW_C_CTX ("try_complete:host_subst");
1323 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1324 if (matches != NULL)
1325 *lc_start += state.r - state.word;
1326 }
1327
1328
1329
1330 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1331 && strchr (state.word, PATH_SEP) == NULL)
1332 {
1333 SHOW_C_CTX ("try_complete:user_subst");
1334 matches = completion_matches (state.word, username_completion_function, state.flags);
1335 }
1336
1337
1338
1339
1340 if (matches == NULL)
1341 matches = try_complete_all_possible (&state, text, lc_start);
1342
1343
1344 if (matches == NULL)
1345 {
1346 state.in_command_position = 0;
1347 matches = try_complete_all_possible (&state, text, lc_start);
1348 }
1349
1350 g_free (state.word);
1351
1352 if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0
1353 && (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1354 {
1355
1356 size_t i;
1357
1358 for (i = 0; i < matches->len; i++)
1359 {
1360 char *p;
1361
1362 p = g_ptr_array_index (matches, i);
1363
1364
1365 g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1366 g_free (p);
1367 }
1368 }
1369
1370 return matches;
1371 }
1372
1373
1374
1375 void
1376 complete_engine_fill_completions (WInput *in)
1377 {
1378 char *s;
1379 const char *word_separators;
1380
1381 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1382
1383 end = str_offset_to_pos (in->buffer->str, in->point);
1384
1385 s = in->buffer->str;
1386 if (in->point != 0)
1387 {
1388
1389 size_t i;
1390
1391 for (i = in->point - 1; i > 0; i--)
1392 str_next_char (&s);
1393 }
1394
1395 for (; s >= in->buffer->str; str_prev_char (&s))
1396 {
1397 start = s - in->buffer->str;
1398 if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1399 break;
1400 }
1401
1402 if (start < end)
1403 {
1404 str_next_char (&s);
1405 start = s - in->buffer->str;
1406 }
1407
1408 in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1409 }
1410
1411
1412
1413
1414 void
1415 input_complete (WInput *in)
1416 {
1417 int engine_flags;
1418
1419 if (!str_is_valid_string (in->buffer->str))
1420 return;
1421
1422 if (in->completions != NULL)
1423 engine_flags = DO_QUERY;
1424 else
1425 {
1426 engine_flags = DO_INSERTION;
1427
1428 if (mc_global.widget.show_all_if_ambiguous)
1429 engine_flags |= DO_QUERY;
1430 }
1431
1432 while (complete_engine (in, engine_flags))
1433 ;
1434 }
1435
1436
1437
1438 void
1439 input_complete_free (WInput *in)
1440 {
1441 if (in->completions != NULL)
1442 {
1443 g_ptr_array_free (in->completions, TRUE);
1444 in->completions = NULL;
1445 }
1446 }
1447
1448