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 if (s == NULL)
912 s = strchr (cdpath, '\0');
913 c = *s;
914 *s = '\0';
915 if (*cdpath != '\0')
916 {
917 state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
918 SHOW_C_CTX ("try_complete:filename_subst_2");
919 matches = completion_matches (state->r, filename_completion_function,
920 state->flags);
921 g_free (state->r);
922 }
923 *s = c;
924 cdpath = str_get_next_char (s);
925 }
926 g_free (cdpath_ref);
927 }
928 }
929 }
930 return matches;
931 }
932
933
934
935 static gboolean
936 insert_text (WInput *in, const char *text, ssize_t size)
937 {
938 size_t text_len;
939 int buff_len;
940 ssize_t new_size;
941
942 text_len = strlen (text);
943 buff_len = str_length (in->buffer->str);
944 if (size < 0)
945 size = (ssize_t) text_len;
946 else
947 size = MIN (size, (ssize_t) text_len);
948
949 new_size = size + start - end;
950 if (new_size != 0)
951 {
952
953
954 size_t tail_len;
955
956 tail_len = in->buffer->len - end;
957 if (tail_len != 0)
958 {
959 char *tail;
960 size_t hole_end;
961
962 tail = g_strndup (in->buffer->str + end, tail_len);
963
964 hole_end = end + new_size;
965 if (in->buffer->len < hole_end)
966 g_string_set_size (in->buffer, hole_end + tail_len);
967
968 g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
969
970 g_free (tail);
971 }
972 }
973
974 g_string_overwrite_len (in->buffer, start, text, size);
975
976 in->point += str_length (in->buffer->str) - buff_len;
977 input_update (in, TRUE);
978 end += new_size;
979
980 return new_size != 0;
981 }
982
983
984
985 static cb_ret_t
986 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
987 {
988 static int bl = 0;
989
990 WGroup *g = GROUP (w);
991 WDialog *h = DIALOG (w);
992
993 switch (msg)
994 {
995 case MSG_KEY:
996 switch (parm)
997 {
998 case KEY_LEFT:
999 case KEY_RIGHT:
1000 bl = 0;
1001 h->ret_value = 0;
1002 dlg_close (h);
1003 return MSG_HANDLED;
1004
1005 case KEY_BACKSPACE:
1006 bl = 0;
1007
1008 if (end == 0)
1009 {
1010 h->ret_value = 0;
1011 dlg_close (h);
1012 }
1013
1014 else if (end == min_end)
1015 {
1016 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1017 input_handle_char (input, parm);
1018 h->ret_value = B_USER;
1019 dlg_close (h);
1020 }
1021 else
1022 {
1023 int new_end;
1024 int i;
1025 GList *e;
1026
1027 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1028
1029 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1030 i++, e = g_list_next (e))
1031 {
1032 WLEntry *le = LENTRY (e->data);
1033
1034 if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1035 {
1036 listbox_set_current (LISTBOX (g->current->data), i);
1037 end = new_end;
1038 input_handle_char (input, parm);
1039 widget_draw (WIDGET (g->current->data));
1040 break;
1041 }
1042 }
1043 }
1044 return MSG_HANDLED;
1045
1046 default:
1047 if (parm < 32 || parm > 255)
1048 {
1049 bl = 0;
1050 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1051 return MSG_NOT_HANDLED;
1052
1053 if (end == min_end)
1054 return MSG_HANDLED;
1055
1056
1057 h->ret_value = B_USER;
1058 dlg_close (h);
1059 }
1060 else
1061 {
1062 static char buff[MB_LEN_MAX] = "";
1063 GList *e;
1064 int i;
1065 int need_redraw = 0;
1066 int low = 4096;
1067 char *last_text = NULL;
1068
1069 buff[bl++] = (char) parm;
1070 buff[bl] = '\0';
1071
1072 switch (str_is_valid_char (buff, bl))
1073 {
1074 case -1:
1075 bl = 0;
1076 MC_FALLTHROUGH;
1077 case -2:
1078 return MSG_HANDLED;
1079 default:
1080 break;
1081 }
1082
1083 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1084 i++, e = g_list_next (e))
1085 {
1086 WLEntry *le = LENTRY (e->data);
1087
1088 if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1089 && strncmp (le->text + end - start, buff, bl) == 0)
1090 {
1091 if (need_redraw == 0)
1092 {
1093 need_redraw = 1;
1094 listbox_set_current (LISTBOX (g->current->data), i);
1095 last_text = le->text;
1096 }
1097 else
1098 {
1099 char *si, *sl;
1100 int si_num = 0;
1101 int sl_num = 0;
1102
1103
1104 for (si = le->text + start; si < le->text + end;
1105 str_next_char (&si), si_num++)
1106 ;
1107 for (sl = last_text + start; sl < last_text + end;
1108 str_next_char (&sl), sl_num++)
1109 ;
1110
1111
1112 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1113 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1114
1115 while (si[0] != '\0' && sl[0] != '\0')
1116 {
1117 char *nexti, *nextl;
1118
1119 nexti = str_get_next_char (si);
1120 nextl = str_get_next_char (sl);
1121
1122 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1123 break;
1124
1125 si = nexti;
1126 sl = nextl;
1127
1128 si_num++;
1129 }
1130
1131 last_text = le->text;
1132
1133 si = &last_text[str_offset_to_pos (last_text, si_num)];
1134 if (low > si - last_text)
1135 low = si - last_text;
1136
1137 need_redraw = 2;
1138 }
1139 }
1140 }
1141
1142 if (need_redraw == 2)
1143 {
1144 insert_text (input, last_text, low);
1145 widget_draw (WIDGET (g->current->data));
1146 }
1147 else if (need_redraw == 1)
1148 {
1149 h->ret_value = B_ENTER;
1150 dlg_close (h);
1151 }
1152 bl = 0;
1153 }
1154 }
1155 return MSG_HANDLED;
1156
1157 default:
1158 return dlg_default_callback (w, sender, msg, parm, data);
1159 }
1160 }
1161
1162
1163
1164
1165 static gboolean
1166 complete_engine (WInput *in, int what_to_do)
1167 {
1168 if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1169 input_complete_free (in);
1170
1171 if (in->completions == NULL)
1172 complete_engine_fill_completions (in);
1173
1174 if (in->completions == NULL)
1175 tty_beep ();
1176 else
1177 {
1178 if ((what_to_do & DO_INSERTION) != 0
1179 || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1180 {
1181 const char *lc_complete;
1182
1183 lc_complete = g_ptr_array_index (in->completions, 0);
1184 if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1185 tty_beep ();
1186 else
1187 input_complete_free (in);
1188 }
1189
1190 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1191 {
1192 int maxlen = 0;
1193 int i;
1194 size_t k;
1195 int count;
1196 int x, y, w, h;
1197 int start_x, start_y;
1198 char *q;
1199 WDialog *complete_dlg;
1200 WListbox *complete_list;
1201
1202 for (k = 1; k < in->completions->len; k++)
1203 {
1204 q = g_ptr_array_index (in->completions, k);
1205 i = str_term_width1 (q);
1206 maxlen = MAX (maxlen, i);
1207 }
1208
1209 count = in->completions->len - 1;
1210 start_x = WIDGET (in)->rect.x;
1211 start_y = WIDGET (in)->rect.y;
1212 if (start_y - 2 >= count)
1213 {
1214 y = start_y - 2 - count;
1215 h = 2 + count;
1216 }
1217 else if (start_y >= LINES - start_y - 1)
1218 {
1219 y = 0;
1220 h = start_y;
1221 }
1222 else
1223 {
1224 y = start_y + 1;
1225 h = LINES - start_y - 1;
1226 }
1227 x = start - in->term_first_shown - 2 + start_x;
1228 w = maxlen + 4;
1229 if (x + w > COLS)
1230 x = COLS - w;
1231 if (x < 0)
1232 x = 0;
1233 if (x + w > COLS)
1234 w = COLS;
1235
1236 input = in;
1237 min_end = end;
1238
1239 complete_dlg = dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE, dialog_colors,
1240 complete_callback, NULL, "[Completion]", NULL);
1241 complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1242 group_add_widget (GROUP (complete_dlg), complete_list);
1243
1244 for (k = 1; k < in->completions->len; k++)
1245 {
1246 q = g_ptr_array_index (in->completions, k);
1247 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1248 }
1249
1250 i = dlg_run (complete_dlg);
1251 q = NULL;
1252 if (i == B_ENTER)
1253 {
1254 listbox_get_current (complete_list, &q, NULL);
1255 if (q != NULL)
1256 insert_text (in, q, -1);
1257 }
1258 if (q != NULL || end != min_end)
1259 input_complete_free (in);
1260 widget_destroy (WIDGET (complete_dlg));
1261
1262
1263 return (i == B_USER);
1264 }
1265 }
1266
1267 return FALSE;
1268 }
1269
1270
1271
1272
1273
1274
1275 GPtrArray *
1276 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1277 {
1278 try_complete_automation_state_t state;
1279 GPtrArray *matches = NULL;
1280
1281 memset (&state, 0, sizeof (state));
1282 state.flags = flags;
1283
1284 SHOW_C_CTX ("try_complete");
1285 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1286
1287 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1288
1289
1290
1291
1292
1293 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1294 try_complete_commands_prepare (&state, text, lc_start);
1295
1296 try_complete_find_start_sign (&state);
1297
1298
1299 if (state.p > state.q && state.p > state.r)
1300 {
1301 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1302 matches = completion_matches (str_cget_next_char (state.p), command_completion_function,
1303 state.flags & (~INPUT_COMPLETE_FILENAMES));
1304 if (matches != NULL)
1305 *lc_start += str_get_next_char (state.p) - state.word;
1306 }
1307
1308
1309 else if (state.q > state.p && state.q > state.r)
1310 {
1311 SHOW_C_CTX ("try_complete:var_subst");
1312 matches = completion_matches (state.q, variable_completion_function, state.flags);
1313 if (matches != NULL)
1314 *lc_start += state.q - state.word;
1315 }
1316
1317
1318
1319 else if (state.r > state.p && state.r > state.q)
1320 {
1321 SHOW_C_CTX ("try_complete:host_subst");
1322 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1323 if (matches != NULL)
1324 *lc_start += state.r - state.word;
1325 }
1326
1327
1328
1329 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1330 && strchr (state.word, PATH_SEP) == NULL)
1331 {
1332 SHOW_C_CTX ("try_complete:user_subst");
1333 matches = completion_matches (state.word, username_completion_function, state.flags);
1334 }
1335
1336
1337
1338
1339 if (matches == NULL)
1340 matches = try_complete_all_possible (&state, text, lc_start);
1341
1342
1343 if (matches == NULL)
1344 {
1345 state.in_command_position = 0;
1346 matches = try_complete_all_possible (&state, text, lc_start);
1347 }
1348
1349 g_free (state.word);
1350
1351 if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0
1352 && (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1353 {
1354
1355 size_t i;
1356
1357 for (i = 0; i < matches->len; i++)
1358 {
1359 char *p;
1360
1361 p = g_ptr_array_index (matches, i);
1362
1363
1364 g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1365 g_free (p);
1366 }
1367 }
1368
1369 return matches;
1370 }
1371
1372
1373
1374 void
1375 complete_engine_fill_completions (WInput *in)
1376 {
1377 char *s;
1378 const char *word_separators;
1379
1380 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1381
1382 end = str_offset_to_pos (in->buffer->str, in->point);
1383
1384 s = in->buffer->str;
1385 if (in->point != 0)
1386 {
1387
1388 size_t i;
1389
1390 for (i = in->point - 1; i > 0; i--)
1391 str_next_char (&s);
1392 }
1393
1394 for (; s >= in->buffer->str; str_prev_char (&s))
1395 {
1396 start = s - in->buffer->str;
1397 if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1398 break;
1399 }
1400
1401 if (start < end)
1402 {
1403 str_next_char (&s);
1404 start = s - in->buffer->str;
1405 }
1406
1407 in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1408 }
1409
1410
1411
1412
1413 void
1414 input_complete (WInput *in)
1415 {
1416 int engine_flags;
1417
1418 if (!str_is_valid_string (in->buffer->str))
1419 return;
1420
1421 if (in->completions != NULL)
1422 engine_flags = DO_QUERY;
1423 else
1424 {
1425 engine_flags = DO_INSERTION;
1426
1427 if (mc_global.widget.show_all_if_ambiguous)
1428 engine_flags |= DO_QUERY;
1429 }
1430
1431 while (complete_engine (in, engine_flags))
1432 ;
1433 }
1434
1435
1436
1437 void
1438 input_complete_free (WInput *in)
1439 {
1440 if (in->completions != NULL)
1441 {
1442 g_ptr_array_free (in->completions, TRUE);
1443 in->completions = NULL;
1444 }
1445 }
1446
1447