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 <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <dirent.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <pwd.h>
43 #include <unistd.h>
44
45 #include "lib/global.h"
46
47 #include "lib/tty/tty.h"
48 #include "lib/tty/key.h"
49 #include "lib/vfs/vfs.h"
50 #include "lib/strutil.h"
51 #include "lib/util.h"
52 #include "lib/widget.h"
53
54
55
56 #if !HAVE_DECL_ENVIRON
57 extern char **environ;
58 #endif
59
60
61
62
63 #ifdef DO_COMPLETION_DEBUG
64 #define SHOW_C_CTX(func) \
65 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", "continue",
554 "declare", "dirs", "echo", "enable", "eval", "exec", "exit", "export",
555 "fc", "fg", "getopts", "hash", "help", "history", "jobs", "kill",
556 "let", "local", "logout", "popd", "pushd", "pwd", "read", "readonly",
557 "return", "set", "shift", "source", "suspend", "test", "times", "trap",
558 "type", "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0,
559 };
560
561 char *u_text;
562 char *p, *found;
563
564 SHOW_C_CTX ("command_completion_function");
565
566 if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
567 return NULL;
568
569 u_text = str_shell_unescape (text);
570 flags &= ~INPUT_COMPLETE_SHELL_ESC;
571
572 if (state == 0)
573 {
574 isabsolute = strchr (u_text, PATH_SEP) != NULL;
575 if (!isabsolute)
576 {
577 words = bash_reserved;
578 phase = 0;
579 text_len = strlen (u_text);
580
581 if (path == NULL)
582 {
583 path = g_strdup (getenv ("PATH"));
584 if (path != NULL)
585 {
586 p = path;
587 path_end = strchr (p, '\0');
588 while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
589 *p++ = '\0';
590 }
591 }
592 }
593 }
594
595 if (isabsolute)
596 {
597 p = filename_completion_function (u_text, state, flags);
598
599 if (p != NULL)
600 {
601 char *temp_p = p;
602
603 p = str_shell_escape (p);
604 g_free (temp_p);
605 }
606
607 g_free (u_text);
608 return p;
609 }
610
611 found = NULL;
612 switch (phase)
613 {
614 case 0:
615 for (; *words != NULL; words++)
616 if (strncmp (*words, u_text, text_len) == 0)
617 {
618 g_free (u_text);
619 return g_strdup (*(words++));
620 }
621 phase++;
622 words = bash_builtins;
623 MC_FALLTHROUGH;
624 case 1:
625 for (; *words != NULL; words++)
626 if (strncmp (*words, u_text, text_len) == 0)
627 {
628 g_free (u_text);
629 return g_strdup (*(words++));
630 }
631 phase++;
632 if (path == NULL)
633 break;
634 cur_path = path;
635 cur_word = NULL;
636 MC_FALLTHROUGH;
637 case 2:
638 while (found == NULL)
639 {
640 if (cur_word == NULL)
641 {
642 char *expanded;
643
644 if (cur_path >= path_end)
645 break;
646 expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
647 cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
648 g_free (expanded);
649 cur_path = strchr (cur_path, '\0') + 1;
650 init_state = state;
651 }
652 found = filename_completion_function (cur_word, state - init_state, flags);
653 if (found == NULL)
654 MC_PTR_FREE (cur_word);
655 }
656 MC_FALLTHROUGH;
657 default:
658 break;
659 }
660
661 if (found == NULL)
662 MC_PTR_FREE (path);
663 else
664 {
665 p = strrchr (found, PATH_SEP);
666 if (p != NULL)
667 {
668 char *tmp = found;
669
670 found = str_shell_escape (p + 1);
671 g_free (tmp);
672 }
673 }
674
675 g_free (u_text);
676 return found;
677 }
678
679
680
681 static int
682 match_compare (gconstpointer a, gconstpointer b)
683 {
684 return strcmp (*(char *const *) a, *(char *const *) b);
685 }
686
687
688
689
690
691
692
693
694
695
696 static GPtrArray *
697 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
698 {
699 GPtrArray *match_list;
700 char *string;
701
702 match_list = g_ptr_array_new_with_free_func (g_free);
703
704 while ((string = entry_function (text, match_list->len, flags)) != NULL)
705 g_ptr_array_add (match_list, string);
706
707
708
709 if (match_list->len == 0)
710 {
711
712 g_ptr_array_free (match_list, TRUE);
713 return NULL;
714 }
715
716
717
718 if (match_list->len > 1)
719 {
720 size_t i, j;
721 size_t low = 4096;
722
723 g_ptr_array_sort (match_list, match_compare);
724
725
726
727
728 for (i = 0, j = 1; j < match_list->len;)
729 {
730 char *si, *sj, *mi;
731
732 si = g_ptr_array_index (match_list, i);
733 sj = g_ptr_array_index (match_list, j);
734 mi = si;
735
736 while (si[0] != '\0' && sj[0] != '\0')
737 {
738 char *ni, *nj;
739
740 ni = str_get_next_char (si);
741 nj = str_get_next_char (sj);
742
743 if (ni - si != nj - sj || strncmp (si, sj, ni - si) != 0)
744 break;
745
746 si = ni;
747 sj = nj;
748 }
749
750 if (si[0] == '\0' && sj[0] == '\0')
751 {
752
753 g_ptr_array_remove_index (match_list, j);
754 }
755 else
756 {
757 low = MIN (low, (size_t) (si - mi));
758
759 i++;
760 j++;
761 }
762 }
763
764 string = g_ptr_array_index (match_list, 0);
765 g_ptr_array_insert (match_list, 0, g_strndup (string, low));
766 }
767
768 return match_list;
769 }
770
771
772
773 static gboolean
774 check_is_cd (const char *text, int lc_start, input_complete_t flags)
775 {
776 const char *p, *q;
777
778 SHOW_C_CTX ("check_is_cd");
779
780 if ((flags & INPUT_COMPLETE_CD) == 0)
781 return FALSE;
782
783
784 p = text;
785 q = text + lc_start;
786 while (p < q && p[0] != '\0' && str_isspace (p))
787 str_cnext_char (&p);
788
789
790 return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
791 }
792
793
794
795 static void
796 try_complete_commands_prepare (try_complete_automation_state_t *state, char *text, int *lc_start)
797 {
798 const char *command_separator_chars = ";|&{(`";
799 char *ti;
800
801 if (*lc_start == 0)
802 ti = text;
803 else
804 {
805 ti = str_get_prev_char (&text[*lc_start]);
806 while (ti > text && whitespace (ti[0]))
807 str_prev_char (&ti);
808 }
809
810 if (ti == text)
811 state->in_command_position++;
812 else if (strchr (command_separator_chars, ti[0]) != NULL)
813 {
814 state->in_command_position++;
815 if (ti != text)
816 {
817 int this_char, prev_char;
818
819
820
821 this_char = ti[0];
822 prev_char = str_get_prev_char (ti)[0];
823
824
825 if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
826 || (this_char == '|' && prev_char == '>')
827 || (ti != text && str_get_prev_char (ti)[0] == '\\'))
828 state->in_command_position = 0;
829 }
830 }
831 }
832
833
834
835 static void
836 try_complete_find_start_sign (try_complete_automation_state_t *state)
837 {
838 if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
839 state->p = strrchr (state->word, '`');
840 if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
841 {
842 state->q = strrchr (state->word, '$');
843
844
845 if (str_is_char_escaped (state->word, state->q))
846 {
847
848 str_move (state->q - 1, state->q);
849
850 state->flags &= ~INPUT_COMPLETE_VARIABLES;
851 state->q = NULL;
852 }
853 }
854 if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
855 state->r = strrchr (state->word, '@');
856 if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
857 {
858 if (state->q > state->p)
859 state->p = str_get_next_char (state->q);
860 state->q = NULL;
861 }
862 }
863
864
865
866 static GPtrArray *
867 try_complete_all_possible (try_complete_automation_state_t *state, char *text, int *lc_start)
868 {
869 GPtrArray *matches = NULL;
870
871 if (state->in_command_position != 0)
872 {
873 SHOW_C_CTX ("try_complete:cmd_subst");
874 matches = completion_matches (state->word, command_completion_function,
875 state->flags & (~INPUT_COMPLETE_FILENAMES));
876 }
877 else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
878 {
879 if (state->is_cd)
880 state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
881 SHOW_C_CTX ("try_complete:filename_subst_1");
882 matches = completion_matches (state->word, filename_completion_function, state->flags);
883
884 if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
885 {
886 state->q = text + *lc_start;
887 for (state->p = text;
888 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
889 str_next_char (&state->p))
890 ;
891 if (strncmp (state->p, "cd", 2) == 0)
892 for (state->p += 2;
893 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
894 str_next_char (&state->p))
895 ;
896 if (state->p == state->q)
897 {
898 char *cdpath_ref, *cdpath;
899 char c;
900
901 cdpath_ref = g_strdup (getenv ("CDPATH"));
902 cdpath = cdpath_ref;
903 c = (cdpath == NULL) ? '\0' : ':';
904
905 while (matches == NULL && c == ':')
906 {
907 char *s;
908
909 s = strchr (cdpath, ':');
910 if (s == NULL)
911 s = strchr (cdpath, '\0');
912 c = *s;
913 *s = '\0';
914 if (*cdpath != '\0')
915 {
916 state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
917 SHOW_C_CTX ("try_complete:filename_subst_2");
918 matches = completion_matches (state->r, filename_completion_function,
919 state->flags);
920 g_free (state->r);
921 }
922 *s = c;
923 cdpath = str_get_next_char (s);
924 }
925 g_free (cdpath_ref);
926 }
927 }
928 }
929 return matches;
930 }
931
932
933
934 static gboolean
935 insert_text (WInput *in, const char *text, ssize_t size)
936 {
937 size_t text_len;
938 int buff_len;
939 ssize_t new_size;
940
941 text_len = strlen (text);
942 buff_len = str_length (in->buffer->str);
943 if (size < 0)
944 size = (ssize_t) text_len;
945 else
946 size = MIN (size, (ssize_t) text_len);
947
948 new_size = size + start - end;
949 if (new_size != 0)
950 {
951
952
953 size_t tail_len;
954
955 tail_len = in->buffer->len - end;
956 if (tail_len != 0)
957 {
958 char *tail;
959 size_t hole_end;
960
961 tail = g_strndup (in->buffer->str + end, tail_len);
962
963 hole_end = end + new_size;
964 if (in->buffer->len < hole_end)
965 g_string_set_size (in->buffer, hole_end + tail_len);
966
967 g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
968
969 g_free (tail);
970 }
971 }
972
973 g_string_overwrite_len (in->buffer, start, text, size);
974
975 in->point += str_length (in->buffer->str) - buff_len;
976 input_update (in, TRUE);
977 end += new_size;
978
979 return new_size != 0;
980 }
981
982
983
984 static cb_ret_t
985 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
986 {
987 static int bl = 0;
988
989 WGroup *g = GROUP (w);
990 WDialog *h = DIALOG (w);
991
992 switch (msg)
993 {
994 case MSG_KEY:
995 switch (parm)
996 {
997 case KEY_LEFT:
998 case KEY_RIGHT:
999 bl = 0;
1000 h->ret_value = 0;
1001 dlg_close (h);
1002 return MSG_HANDLED;
1003
1004 case KEY_BACKSPACE:
1005 bl = 0;
1006
1007 if (end == 0)
1008 {
1009 h->ret_value = 0;
1010 dlg_close (h);
1011 }
1012
1013 else if (end == min_end)
1014 {
1015 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1016 input_handle_char (input, parm);
1017 h->ret_value = B_USER;
1018 dlg_close (h);
1019 }
1020 else
1021 {
1022 int new_end;
1023 int i;
1024 GList *e;
1025
1026 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1027
1028 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1029 i++, e = g_list_next (e))
1030 {
1031 WLEntry *le = LENTRY (e->data);
1032
1033 if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1034 {
1035 listbox_set_current (LISTBOX (g->current->data), i);
1036 end = new_end;
1037 input_handle_char (input, parm);
1038 widget_draw (WIDGET (g->current->data));
1039 break;
1040 }
1041 }
1042 }
1043 return MSG_HANDLED;
1044
1045 default:
1046 if (parm < 32 || parm > 255)
1047 {
1048 bl = 0;
1049 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1050 return MSG_NOT_HANDLED;
1051
1052 if (end == min_end)
1053 return MSG_HANDLED;
1054
1055
1056 h->ret_value = B_USER;
1057 dlg_close (h);
1058 }
1059 else
1060 {
1061 static char buff[MB_LEN_MAX] = "";
1062 GList *e;
1063 int i;
1064 int need_redraw = 0;
1065 int low = 4096;
1066 char *last_text = NULL;
1067
1068 buff[bl++] = (char) parm;
1069 buff[bl] = '\0';
1070
1071 switch (str_is_valid_char (buff, bl))
1072 {
1073 case -1:
1074 bl = 0;
1075 MC_FALLTHROUGH;
1076 case -2:
1077 return MSG_HANDLED;
1078 default:
1079 break;
1080 }
1081
1082 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1083 i++, e = g_list_next (e))
1084 {
1085 WLEntry *le = LENTRY (e->data);
1086
1087 if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1088 && strncmp (le->text + end - start, buff, bl) == 0)
1089 {
1090 if (need_redraw == 0)
1091 {
1092 need_redraw = 1;
1093 listbox_set_current (LISTBOX (g->current->data), i);
1094 last_text = le->text;
1095 }
1096 else
1097 {
1098 char *si, *sl;
1099 int si_num = 0;
1100 int sl_num = 0;
1101
1102
1103 for (si = le->text + start; si < le->text + end;
1104 str_next_char (&si), si_num++)
1105 ;
1106 for (sl = last_text + start; sl < last_text + end;
1107 str_next_char (&sl), sl_num++)
1108 ;
1109
1110
1111 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1112 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1113
1114 while (si[0] != '\0' && sl[0] != '\0')
1115 {
1116 char *nexti, *nextl;
1117
1118 nexti = str_get_next_char (si);
1119 nextl = str_get_next_char (sl);
1120
1121 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1122 break;
1123
1124 si = nexti;
1125 sl = nextl;
1126
1127 si_num++;
1128 }
1129
1130 last_text = le->text;
1131
1132 si = &last_text[str_offset_to_pos (last_text, si_num)];
1133 if (low > si - last_text)
1134 low = si - last_text;
1135
1136 need_redraw = 2;
1137 }
1138 }
1139 }
1140
1141 if (need_redraw == 2)
1142 {
1143 insert_text (input, last_text, low);
1144 widget_draw (WIDGET (g->current->data));
1145 }
1146 else if (need_redraw == 1)
1147 {
1148 h->ret_value = B_ENTER;
1149 dlg_close (h);
1150 }
1151 bl = 0;
1152 }
1153 }
1154 return MSG_HANDLED;
1155
1156 default:
1157 return dlg_default_callback (w, sender, msg, parm, data);
1158 }
1159 }
1160
1161
1162
1163
1164 static gboolean
1165 complete_engine (WInput *in, int what_to_do)
1166 {
1167 if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1168 input_complete_free (in);
1169
1170 if (in->completions == NULL)
1171 complete_engine_fill_completions (in);
1172
1173 if (in->completions == NULL)
1174 tty_beep ();
1175 else
1176 {
1177 if ((what_to_do & DO_INSERTION) != 0
1178 || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1179 {
1180 const char *lc_complete;
1181
1182 lc_complete = g_ptr_array_index (in->completions, 0);
1183 if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1184 tty_beep ();
1185 else
1186 input_complete_free (in);
1187 }
1188
1189 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1190 {
1191 int maxlen = 0;
1192 int i;
1193 size_t k;
1194 int count;
1195 int x, y, w, h;
1196 int start_x, start_y;
1197 char *q;
1198 WDialog *complete_dlg;
1199 WListbox *complete_list;
1200
1201 for (k = 1; k < in->completions->len; k++)
1202 {
1203 q = g_ptr_array_index (in->completions, k);
1204 i = str_term_width1 (q);
1205 maxlen = MAX (maxlen, i);
1206 }
1207
1208 count = in->completions->len - 1;
1209 start_x = WIDGET (in)->rect.x;
1210 start_y = WIDGET (in)->rect.y;
1211 if (start_y - 2 >= count)
1212 {
1213 y = start_y - 2 - count;
1214 h = 2 + count;
1215 }
1216 else if (start_y >= LINES - start_y - 1)
1217 {
1218 y = 0;
1219 h = start_y;
1220 }
1221 else
1222 {
1223 y = start_y + 1;
1224 h = LINES - start_y - 1;
1225 }
1226 x = start - in->term_first_shown - 2 + start_x;
1227 w = maxlen + 4;
1228 if (x + w > COLS)
1229 x = COLS - w;
1230 if (x < 0)
1231 x = 0;
1232 if (x + w > COLS)
1233 w = COLS;
1234
1235 input = in;
1236 min_end = end;
1237
1238 complete_dlg = dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE, dialog_colors,
1239 complete_callback, NULL, "[Completion]", NULL);
1240 complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1241 group_add_widget (GROUP (complete_dlg), complete_list);
1242
1243 for (k = 1; k < in->completions->len; k++)
1244 {
1245 q = g_ptr_array_index (in->completions, k);
1246 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1247 }
1248
1249 i = dlg_run (complete_dlg);
1250 q = NULL;
1251 if (i == B_ENTER)
1252 {
1253 listbox_get_current (complete_list, &q, NULL);
1254 if (q != NULL)
1255 insert_text (in, q, -1);
1256 }
1257 if (q != NULL || end != min_end)
1258 input_complete_free (in);
1259 widget_destroy (WIDGET (complete_dlg));
1260
1261
1262 return (i == B_USER);
1263 }
1264 }
1265
1266 return FALSE;
1267 }
1268
1269
1270
1271
1272
1273
1274 GPtrArray *
1275 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1276 {
1277 try_complete_automation_state_t state;
1278 GPtrArray *matches = NULL;
1279
1280 memset (&state, 0, sizeof (state));
1281 state.flags = flags;
1282
1283 SHOW_C_CTX ("try_complete");
1284 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1285
1286 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1287
1288
1289
1290
1291
1292 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1293 try_complete_commands_prepare (&state, text, lc_start);
1294
1295 try_complete_find_start_sign (&state);
1296
1297
1298 if (state.p > state.q && state.p > state.r)
1299 {
1300 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1301 matches = completion_matches (str_cget_next_char (state.p), command_completion_function,
1302 state.flags & (~INPUT_COMPLETE_FILENAMES));
1303 if (matches != NULL)
1304 *lc_start += str_get_next_char (state.p) - state.word;
1305 }
1306
1307
1308 else if (state.q > state.p && state.q > state.r)
1309 {
1310 SHOW_C_CTX ("try_complete:var_subst");
1311 matches = completion_matches (state.q, variable_completion_function, state.flags);
1312 if (matches != NULL)
1313 *lc_start += state.q - state.word;
1314 }
1315
1316
1317
1318 else if (state.r > state.p && state.r > state.q)
1319 {
1320 SHOW_C_CTX ("try_complete:host_subst");
1321 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1322 if (matches != NULL)
1323 *lc_start += state.r - state.word;
1324 }
1325
1326
1327
1328 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1329 && strchr (state.word, PATH_SEP) == NULL)
1330 {
1331 SHOW_C_CTX ("try_complete:user_subst");
1332 matches = completion_matches (state.word, username_completion_function, state.flags);
1333 }
1334
1335
1336
1337
1338 if (matches == NULL)
1339 matches = try_complete_all_possible (&state, text, lc_start);
1340
1341
1342 if (matches == NULL)
1343 {
1344 state.in_command_position = 0;
1345 matches = try_complete_all_possible (&state, text, lc_start);
1346 }
1347
1348 g_free (state.word);
1349
1350 if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0
1351 && (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1352 {
1353
1354 size_t i;
1355
1356 for (i = 0; i < matches->len; i++)
1357 {
1358 char *p;
1359
1360 p = g_ptr_array_index (matches, i);
1361
1362
1363 g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1364 g_free (p);
1365 }
1366 }
1367
1368 return matches;
1369 }
1370
1371
1372
1373 void
1374 complete_engine_fill_completions (WInput *in)
1375 {
1376 char *s;
1377 const char *word_separators;
1378
1379 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1380
1381 end = str_offset_to_pos (in->buffer->str, in->point);
1382
1383 s = in->buffer->str;
1384 if (in->point != 0)
1385 {
1386
1387 size_t i;
1388
1389 for (i = in->point - 1; i > 0; i--)
1390 str_next_char (&s);
1391 }
1392
1393 for (; s >= in->buffer->str; str_prev_char (&s))
1394 {
1395 start = s - in->buffer->str;
1396 if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1397 break;
1398 }
1399
1400 if (start < end)
1401 {
1402 str_next_char (&s);
1403 start = s - in->buffer->str;
1404 }
1405
1406 in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1407 }
1408
1409
1410
1411
1412 void
1413 input_complete (WInput *in)
1414 {
1415 int engine_flags;
1416
1417 if (!str_is_valid_string (in->buffer->str))
1418 return;
1419
1420 if (in->completions != NULL)
1421 engine_flags = DO_QUERY;
1422 else
1423 {
1424 engine_flags = DO_INSERTION;
1425
1426 if (mc_global.widget.show_all_if_ambiguous)
1427 engine_flags |= DO_QUERY;
1428 }
1429
1430 while (complete_engine (in, engine_flags))
1431 ;
1432 }
1433
1434
1435
1436 void
1437 input_complete_free (WInput *in)
1438 {
1439 if (in->completions != NULL)
1440 {
1441 g_ptr_array_free (in->completions, TRUE);
1442 in->completions = NULL;
1443 }
1444 }
1445
1446