Move widget add/del API from WDialog to WGroup.
[midnight-commander.git] / lib / widget / input_complete.c
blobbfb2449b977fb3e5c79f209adc6a4d8e16f7b58b
1 /*
2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2020
6 Free Software Foundation, Inc.
8 Written by:
9 Jakub Jelinek, 1995
10 Slava Zanko <slavazanko@gmail.com>, 2013
11 Andrew Borodin <aborodin@vmail.ru>, 2013
13 This file is part of the Midnight Commander.
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 /** \file lib/widget/input_complete.c
30 * \brief Source: Input line filename/username/hostname/variable/command completion
33 #include <config.h>
35 #include <ctype.h>
36 #include <limits.h> /* MB_LEN_MAX */
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>
46 #include "lib/global.h"
48 #include "lib/tty/tty.h"
49 #include "lib/tty/key.h" /* XCTRL and ALT macros */
50 #include "lib/vfs/vfs.h"
51 #include "lib/strescape.h"
52 #include "lib/strutil.h"
53 #include "lib/util.h"
54 #include "lib/widget.h"
56 #include "input_complete.h"
58 /*** global variables ****************************************************************************/
60 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
61 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
62 extern char **environ;
63 #endif
65 /*** file scope macro definitions ****************************************************************/
67 /* #define DO_COMPLETION_DEBUG */
68 #ifdef DO_COMPLETION_DEBUG
69 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
70 #else
71 #define SHOW_C_CTX(func)
72 #endif /* DO_CMPLETION_DEBUG */
74 #define DO_INSERTION 1
75 #define DO_QUERY 2
77 /*** file scope type declarations ****************************************************************/
79 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
81 typedef struct
83 size_t in_command_position;
84 char *word;
85 char *p;
86 char *q;
87 char *r;
88 gboolean is_cd;
89 input_complete_t flags;
90 } try_complete_automation_state_t;
92 /*** file scope variables ************************************************************************/
94 static char **hosts = NULL;
95 static char **hosts_p = NULL;
96 static int hosts_alloclen = 0;
98 static int query_height, query_width;
99 static WInput *input;
100 static int min_end;
101 static int start = 0;
102 static int end = 0;
104 /*** file scope functions ************************************************************************/
105 /* --------------------------------------------------------------------------------------------- */
107 char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
108 void complete_engine_fill_completions (WInput * in);
110 #ifdef DO_COMPLETION_DEBUG
112 * Useful to print/debug completion flags
114 static const char *
115 show_c_flags (input_complete_t flags)
117 static char s_cf[] = "FHCVUDS";
119 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
120 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
121 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
122 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
123 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
124 s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
125 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
127 return s_cf;
129 #endif /* DO_CMPLETION_DEBUG */
131 /* --------------------------------------------------------------------------------------------- */
133 static char *
134 filename_completion_function (const char *text, int state, input_complete_t flags)
136 static DIR *directory = NULL;
137 static char *filename = NULL;
138 static char *dirname = NULL;
139 static char *users_dirname = NULL;
140 static size_t filename_len = 0;
141 static vfs_path_t *dirname_vpath = NULL;
143 gboolean isdir = TRUE, isexec = FALSE;
144 struct dirent *entry = NULL;
146 SHOW_C_CTX ("filename_completion_function");
148 if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
150 char *u_text;
151 char *result;
152 char *e_result;
154 u_text = strutils_shell_unescape (text);
156 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
157 g_free (u_text);
159 e_result = strutils_shell_escape (result);
160 g_free (result);
162 return e_result;
165 /* If we're starting the match process, initialize us a bit. */
166 if (state == 0)
168 const char *temp;
170 g_free (dirname);
171 g_free (filename);
172 g_free (users_dirname);
173 vfs_path_free (dirname_vpath);
175 if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
177 filename = g_strdup (++temp);
178 dirname = g_strndup (text, temp - text);
180 else
182 dirname = g_strdup (".");
183 filename = g_strdup (text);
186 /* We aren't done yet. We also support the "~user" syntax. */
188 /* Save the version of the directory that the user typed. */
189 users_dirname = dirname;
190 dirname = tilde_expand (dirname);
191 canonicalize_pathname (dirname);
192 dirname_vpath = vfs_path_from_str (dirname);
194 /* Here we should do something with variable expansion
195 and `command`.
196 Maybe a dream - UNIMPLEMENTED yet. */
198 directory = mc_opendir (dirname_vpath);
199 filename_len = strlen (filename);
202 /* Now that we have some state, we can read the directory. */
204 while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
206 if (!str_is_valid_string (entry->d_name))
207 continue;
209 /* Special case for no filename.
210 All entries except "." and ".." match. */
211 if (filename_len == 0)
213 if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
214 continue;
216 else
218 /* Otherwise, if these match up to the length of filename, then
219 it may be a match. */
220 if ((entry->d_name[0] != filename[0]) ||
221 ((NLENGTH (entry)) < filename_len) ||
222 strncmp (filename, entry->d_name, filename_len) != 0)
223 continue;
226 isdir = TRUE;
227 isexec = FALSE;
230 struct stat tempstat;
231 vfs_path_t *tmp_vpath;
233 tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
235 /* Unix version */
236 if (mc_stat (tmp_vpath, &tempstat) == 0)
238 uid_t my_uid;
239 gid_t my_gid;
241 my_uid = getuid ();
242 my_gid = getgid ();
244 if (!S_ISDIR (tempstat.st_mode))
246 isdir = FALSE;
248 if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
249 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
250 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
251 (tempstat.st_mode & 0001) != 0)
252 isexec = TRUE;
255 else
257 /* stat failed, strange. not a dir in any case */
258 isdir = FALSE;
260 vfs_path_free (tmp_vpath);
263 if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
264 break;
265 if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
266 break;
267 if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
268 break;
271 if (entry == NULL)
273 if (directory != NULL)
275 mc_closedir (directory);
276 directory = NULL;
278 MC_PTR_FREE (dirname);
279 vfs_path_free (dirname_vpath);
280 dirname_vpath = NULL;
281 MC_PTR_FREE (filename);
282 MC_PTR_FREE (users_dirname);
283 return NULL;
287 GString *temp;
289 temp = g_string_sized_new (16);
291 if (users_dirname != NULL && (users_dirname[0] != '.' || users_dirname[1] != '\0'))
293 g_string_append (temp, users_dirname);
295 /* We need a '/' at the end. */
296 if (!IS_PATH_SEP (temp->str[temp->len - 1]))
297 g_string_append_c (temp, PATH_SEP);
299 g_string_append (temp, entry->d_name);
300 if (isdir)
301 g_string_append_c (temp, PATH_SEP);
303 return g_string_free (temp, FALSE);
307 /* --------------------------------------------------------------------------------------------- */
308 /** We assume here that text[0] == '~' , if you want to call it in another way,
309 you have to change the code */
311 static char *
312 username_completion_function (const char *text, int state, input_complete_t flags)
314 static struct passwd *entry = NULL;
315 static size_t userlen = 0;
317 (void) flags;
318 SHOW_C_CTX ("username_completion_function");
320 if (text[0] == '\\' && text[1] == '~')
321 text++;
322 if (state == 0)
323 { /* Initialization stuff */
324 setpwent ();
325 userlen = strlen (text + 1);
328 while ((entry = getpwent ()) != NULL)
330 /* Null usernames should result in all users as possible completions. */
331 if (userlen == 0)
332 break;
333 if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
334 break;
337 if (entry != NULL)
338 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
340 endpwent ();
341 return NULL;
344 /* --------------------------------------------------------------------------------------------- */
345 /** We assume text [0] == '$' and want to have a look at text [1], if it is
346 equal to '{', so that we should append '}' at the end */
348 static char *
349 variable_completion_function (const char *text, int state, input_complete_t flags)
351 static char **env_p = NULL;
352 static gboolean isbrace = FALSE;
353 static size_t varlen = 0;
354 const char *p = NULL;
356 (void) flags;
357 SHOW_C_CTX ("variable_completion_function");
359 if (state == 0)
360 { /* Initialization stuff */
361 isbrace = (text[1] == '{');
362 varlen = strlen (text + 1 + isbrace);
363 env_p = environ;
366 while (*env_p != NULL)
368 p = strchr (*env_p, '=');
369 if (p != NULL && ((size_t) (p - *env_p) >= varlen)
370 && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
371 break;
372 env_p++;
375 if (*env_p == NULL)
376 return NULL;
379 GString *temp;
381 temp = g_string_new_len (*env_p, p - *env_p);
383 if (isbrace)
385 g_string_prepend_c (temp, '{');
386 g_string_append_c (temp, '}');
388 g_string_prepend_c (temp, '$');
390 env_p++;
392 return g_string_free (temp, FALSE);
396 /* --------------------------------------------------------------------------------------------- */
398 static void
399 fetch_hosts (const char *filename)
401 FILE *file;
402 char buffer[256];
403 char *name;
404 char *lc_start;
405 char *bi;
407 file = fopen (filename, "r");
408 if (file == NULL)
409 return;
411 while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
413 /* Skip to first character. */
414 for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
417 /* Ignore comments... */
418 if (bi[0] == '#')
419 continue;
421 /* Handle $include. */
422 if (strncmp (bi, "$include ", 9) == 0)
424 char *includefile, *t;
426 /* Find start of filename. */
427 includefile = bi + 9;
428 while (*includefile != '\0' && whitespace (*includefile))
429 includefile++;
430 t = includefile;
432 /* Find end of filename. */
433 while (t[0] != '\0' && !str_isspace (t))
434 str_next_char (&t);
435 *t = '\0';
437 fetch_hosts (includefile);
438 continue;
441 /* Skip IP #s. */
442 while (bi[0] != '\0' && !str_isspace (bi))
443 str_next_char (&bi);
445 /* Get the host names separated by white space. */
446 while (bi[0] != '\0' && bi[0] != '#')
448 while (bi[0] != '\0' && str_isspace (bi))
449 str_next_char (&bi);
450 if (bi[0] == '#')
451 continue;
452 for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
455 if (bi == lc_start)
456 continue;
458 name = g_strndup (lc_start, bi - lc_start);
461 char **host_p;
462 int j;
464 j = hosts_p - hosts;
466 if (j >= hosts_alloclen)
468 hosts_alloclen += 30;
469 hosts = g_renew (char *, hosts, hosts_alloclen + 1);
470 hosts_p = hosts + j;
473 for (host_p = hosts; host_p < hosts_p; host_p++)
474 if (strcmp (name, *host_p) == 0)
475 break; /* We do not want any duplicates */
477 if (host_p == hosts_p)
479 *(hosts_p++) = name;
480 *hosts_p = NULL;
482 else
483 g_free (name);
488 fclose (file);
491 /* --------------------------------------------------------------------------------------------- */
493 static char *
494 hostname_completion_function (const char *text, int state, input_complete_t flags)
496 static char **host_p = NULL;
497 static size_t textstart = 0;
498 static size_t textlen = 0;
500 (void) flags;
501 SHOW_C_CTX ("hostname_completion_function");
503 if (state == 0)
504 { /* Initialization stuff */
505 const char *p;
507 g_strfreev (hosts);
508 hosts_alloclen = 30;
509 hosts = g_new (char *, hosts_alloclen + 1);
510 *hosts = NULL;
511 hosts_p = hosts;
512 p = getenv ("HOSTFILE");
513 fetch_hosts (p != NULL ? p : "/etc/hosts");
514 host_p = hosts;
515 textstart = (*text == '@') ? 1 : 0;
516 textlen = strlen (text + textstart);
519 for (; *host_p != NULL; host_p++)
521 if (textlen == 0)
522 break; /* Match all of them */
523 if (strncmp (text + textstart, *host_p, textlen) == 0)
524 break;
527 if (*host_p == NULL)
529 g_strfreev (hosts);
530 hosts = NULL;
531 return NULL;
535 GString *temp;
537 temp = g_string_sized_new (8);
539 if (textstart != 0)
540 g_string_append_c (temp, '@');
541 g_string_append (temp, *host_p);
542 host_p++;
544 return g_string_free (temp, FALSE);
548 /* --------------------------------------------------------------------------------------------- */
550 * This is the function to call when the word to complete is in a position
551 * where a command word can be found. It looks around $PATH, looking for
552 * commands that match. It also scans aliases, function names, and the
553 * table of shell built-ins.
556 static char *
557 command_completion_function (const char *text, int state, input_complete_t flags)
559 static const char *path_end = NULL;
560 static gboolean isabsolute = FALSE;
561 static int phase = 0;
562 static size_t text_len = 0;
563 static const char *const *words = NULL;
564 static char *path = NULL;
565 static char *cur_path = NULL;
566 static char *cur_word = NULL;
567 static int init_state = 0;
568 static const char *const bash_reserved[] = {
569 "if", "then", "else", "elif", "fi", "case", "esac", "for",
570 "select", "while", "until", "do", "done", "in", "function", 0
572 static const char *const bash_builtins[] = {
573 "alias", "bg", "bind", "break", "builtin", "cd", "command",
574 "continue", "declare", "dirs", "echo", "enable", "eval",
575 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
576 "help", "history", "jobs", "kill", "let", "local", "logout",
577 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
578 "shift", "source", "suspend", "test", "times", "trap", "type",
579 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
582 char *u_text;
583 char *p, *found;
585 SHOW_C_CTX ("command_completion_function");
587 if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
588 return NULL;
590 u_text = strutils_shell_unescape (text);
591 flags &= ~INPUT_COMPLETE_SHELL_ESC;
593 if (state == 0)
594 { /* Initialize us a little bit */
595 isabsolute = strchr (u_text, PATH_SEP) != NULL;
596 if (!isabsolute)
598 words = bash_reserved;
599 phase = 0;
600 text_len = strlen (u_text);
602 if (path == NULL)
604 path = g_strdup (getenv ("PATH"));
605 if (path != NULL)
607 p = path;
608 path_end = strchr (p, '\0');
609 while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
610 *p++ = '\0';
616 if (isabsolute)
618 p = filename_completion_function (u_text, state, flags);
620 if (p != NULL)
622 char *temp_p = p;
624 p = strutils_shell_escape (p);
625 g_free (temp_p);
628 g_free (u_text);
629 return p;
632 found = NULL;
633 switch (phase)
635 case 0: /* Reserved words */
636 for (; *words != NULL; words++)
637 if (strncmp (*words, u_text, text_len) == 0)
639 g_free (u_text);
640 return g_strdup (*(words++));
642 phase++;
643 words = bash_builtins;
644 MC_FALLTHROUGH;
645 case 1: /* Builtin commands */
646 for (; *words != NULL; words++)
647 if (strncmp (*words, u_text, text_len) == 0)
649 g_free (u_text);
650 return g_strdup (*(words++));
652 phase++;
653 if (path == NULL)
654 break;
655 cur_path = path;
656 cur_word = NULL;
657 MC_FALLTHROUGH;
658 case 2: /* And looking through the $PATH */
659 while (found == NULL)
661 if (cur_word == NULL)
663 char *expanded;
665 if (cur_path >= path_end)
666 break;
667 expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
668 cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
669 g_free (expanded);
670 canonicalize_pathname (cur_word);
671 cur_path = strchr (cur_path, '\0') + 1;
672 init_state = state;
674 found = filename_completion_function (cur_word, state - init_state, flags);
675 if (found == NULL)
676 MC_PTR_FREE (cur_word);
678 MC_FALLTHROUGH;
679 default:
680 break;
683 if (found == NULL)
684 MC_PTR_FREE (path);
685 else
687 p = strrchr (found, PATH_SEP);
688 if (p != NULL)
690 char *tmp = found;
692 found = strutils_shell_escape (p + 1);
693 g_free (tmp);
697 g_free (u_text);
698 return found;
701 /* --------------------------------------------------------------------------------------------- */
703 static int
704 match_compare (const void *a, const void *b)
706 return strcmp (*(char *const *) a, *(char *const *) b);
709 /* --------------------------------------------------------------------------------------------- */
710 /** Returns an array of char * matches with the longest common denominator
711 in the 1st entry. Then a NULL terminated list of different possible
712 completions follows.
713 You have to supply your own CompletionFunction with the word you
714 want to complete as the first argument and an count of previous matches
715 as the second.
716 In case no matches were found we return NULL. */
718 static char **
719 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
721 /* Number of slots in match_list. */
722 size_t match_list_size = 30;
723 /* The list of matches. */
724 char **match_list;
725 /* Number of matches actually found. */
726 size_t matches = 0;
728 /* Temporary string binder. */
729 char *string;
731 match_list = g_new (char *, match_list_size + 1);
732 match_list[1] = NULL;
734 while ((string = (*entry_function) (text, matches, flags)) != NULL)
736 if (matches + 1 == match_list_size)
738 match_list_size += 30;
739 match_list = (char **) g_renew (char *, match_list, match_list_size + 1);
741 match_list[++matches] = string;
742 match_list[matches + 1] = NULL;
745 /* If there were any matches, then look through them finding out the
746 lowest common denominator. That then becomes match_list[0]. */
747 if (matches == 0)
748 MC_PTR_FREE (match_list); /* There were no matches. */
749 else
751 /* If only one match, just use that. */
752 if (matches == 1)
754 match_list[0] = match_list[1];
755 match_list[1] = NULL;
757 else
759 size_t i = 1;
760 int low = 4096; /* Count of max-matched characters. */
761 size_t j;
763 qsort (match_list + 1, matches, sizeof (char *), match_compare);
765 /* And compare each member of the list with
766 the next, finding out where they stop matching.
767 If we find two equal strings, we have to put one away... */
769 j = i + 1;
770 while (j < matches + 1)
772 char *si, *sj;
773 char *ni, *nj;
775 for (si = match_list[i], sj = match_list[j]; si[0] != '\0' && sj[0] != '\0';)
778 ni = str_get_next_char (si);
779 nj = str_get_next_char (sj);
781 if (ni - si != nj - sj)
782 break;
783 if (strncmp (si, sj, ni - si) != 0)
784 break;
786 si = ni;
787 sj = nj;
790 if (si[0] == '\0' && sj[0] == '\0')
791 { /* Two equal strings */
792 g_free (match_list[j]);
793 j++;
794 if (j > matches)
795 break;
796 continue; /* Look for a run of equal strings */
798 else if (low > si - match_list[i])
799 low = si - match_list[i];
800 if (i + 1 != j) /* So there's some gap */
801 match_list[i + 1] = match_list[j];
802 i++;
803 j++;
805 matches = i;
806 match_list[matches + 1] = NULL;
807 match_list[0] = g_strndup (match_list[1], low);
811 return match_list;
814 /* --------------------------------------------------------------------------------------------- */
815 /** Check if directory completion is needed */
816 static gboolean
817 check_is_cd (const char *text, int lc_start, input_complete_t flags)
819 const char *p, *q;
821 SHOW_C_CTX ("check_is_cd");
823 if ((flags & INPUT_COMPLETE_CD) == 0)
824 return FALSE;
826 /* Skip initial spaces */
827 p = text;
828 q = text + lc_start;
829 while (p < q && p[0] != '\0' && str_isspace (p))
830 str_cnext_char (&p);
832 /* Check if the command is "cd" and the cursor is after it */
833 return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
836 /* --------------------------------------------------------------------------------------------- */
838 static void
839 try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
841 const char *command_separator_chars = ";|&{(`";
842 char *ti;
844 if (*lc_start == 0)
845 ti = text;
846 else
848 ti = str_get_prev_char (&text[*lc_start]);
849 while (ti > text && whitespace (ti[0]))
850 str_prev_char (&ti);
853 if (ti == text)
854 state->in_command_position++;
855 else if (strchr (command_separator_chars, ti[0]) != NULL)
857 state->in_command_position++;
858 if (ti != text)
860 int this_char, prev_char;
862 /* Handle the two character tokens '>&', '<&', and '>|'.
863 We are not in a command position after one of these. */
864 this_char = ti[0];
865 prev_char = str_get_prev_char (ti)[0];
867 /* Quoted */
868 if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
869 || (this_char == '|' && prev_char == '>') || (ti != text
870 && str_get_prev_char (ti)[0] == '\\'))
871 state->in_command_position = 0;
876 /* --------------------------------------------------------------------------------------------- */
878 static void
879 try_complete_find_start_sign (try_complete_automation_state_t * state)
881 if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
882 state->p = strrchr (state->word, '`');
883 if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
885 state->q = strrchr (state->word, '$');
887 /* don't substitute variable in \$ case */
888 if (strutils_is_char_escaped (state->word, state->q))
890 /* drop '\\' */
891 str_move (state->q - 1, state->q);
892 /* adjust flags */
893 state->flags &= ~INPUT_COMPLETE_VARIABLES;
894 state->q = NULL;
897 if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
898 state->r = strrchr (state->word, '@');
899 if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
901 if (state->q > state->p)
902 state->p = str_get_next_char (state->q);
903 state->q = NULL;
907 /* --------------------------------------------------------------------------------------------- */
909 static char **
910 try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
912 char **matches = NULL;
914 if (state->in_command_position != 0)
916 SHOW_C_CTX ("try_complete:cmd_subst");
917 matches =
918 completion_matches (state->word, command_completion_function,
919 state->flags & (~INPUT_COMPLETE_FILENAMES));
921 else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
923 if (state->is_cd)
924 state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
925 SHOW_C_CTX ("try_complete:filename_subst_1");
926 matches = completion_matches (state->word, filename_completion_function, state->flags);
928 if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
930 state->q = text + *lc_start;
931 for (state->p = text;
932 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
933 str_next_char (&state->p))
935 if (strncmp (state->p, "cd", 2) == 0)
936 for (state->p += 2;
937 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
938 str_next_char (&state->p))
940 if (state->p == state->q)
942 char *cdpath_ref, *cdpath;
943 char c;
945 cdpath_ref = g_strdup (getenv ("CDPATH"));
946 cdpath = cdpath_ref;
947 c = (cdpath == NULL) ? '\0' : ':';
949 while (matches == NULL && c == ':')
951 char *s;
953 s = strchr (cdpath, ':');
954 /* cppcheck-suppress nullPointer */
955 if (s == NULL)
956 s = strchr (cdpath, '\0');
957 c = *s;
958 *s = '\0';
959 if (*cdpath != '\0')
961 state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
962 SHOW_C_CTX ("try_complete:filename_subst_2");
963 matches =
964 completion_matches (state->r, filename_completion_function,
965 state->flags);
966 g_free (state->r);
968 *s = c;
969 cdpath = str_get_next_char (s);
971 g_free (cdpath_ref);
975 return matches;
978 /* --------------------------------------------------------------------------------------------- */
980 static gboolean
981 insert_text (WInput * in, char *text, ssize_t size)
983 size_t text_len;
984 int buff_len;
986 text_len = strlen (text);
987 buff_len = str_length (in->buffer);
988 size = MIN (size, (ssize_t) text_len) + start - end;
989 if (strlen (in->buffer) + size >= (size_t) in->current_max_size)
991 /* Expand the buffer */
992 char *narea;
993 Widget *w = WIDGET (in);
995 narea = g_try_realloc (in->buffer, in->current_max_size + size + w->cols);
996 if (narea != NULL)
998 in->buffer = narea;
999 in->current_max_size += size + w->cols;
1002 if (strlen (in->buffer) + 1 < (size_t) in->current_max_size)
1004 if (size != 0)
1005 memmove (in->buffer + end + size, in->buffer + end, strlen (&in->buffer[end]) + 1);
1006 memmove (in->buffer + start, text, size - (start - end));
1007 in->point += str_length (in->buffer) - buff_len;
1008 input_update (in, TRUE);
1009 end += size;
1012 return size != 0;
1015 /* --------------------------------------------------------------------------------------------- */
1017 static cb_ret_t
1018 query_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1020 static int bl = 0;
1022 WGroup *g = GROUP (w);
1023 WDialog *h = DIALOG (w);
1025 switch (msg)
1027 case MSG_KEY:
1028 switch (parm)
1030 case KEY_LEFT:
1031 case KEY_RIGHT:
1032 bl = 0;
1033 h->ret_value = 0;
1034 dlg_stop (h);
1035 return MSG_HANDLED;
1037 case KEY_BACKSPACE:
1038 bl = 0;
1039 /* exit from completion list if input line is empty */
1040 if (end == 0)
1042 h->ret_value = 0;
1043 dlg_stop (h);
1045 /* Refill the list box and start again */
1046 else if (end == min_end)
1048 end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1049 input_handle_char (input, parm);
1050 h->ret_value = B_USER;
1051 dlg_stop (h);
1053 else
1055 int new_end;
1056 int i;
1057 GList *e;
1059 new_end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1061 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1062 e != NULL; i++, e = g_list_next (e))
1064 WLEntry *le = LENTRY (e->data);
1066 if (strncmp (input->buffer + start, le->text, new_end - start) == 0)
1068 listbox_select_entry (LISTBOX (g->current->data), i);
1069 end = new_end;
1070 input_handle_char (input, parm);
1071 widget_draw (WIDGET (g->current->data));
1072 break;
1076 return MSG_HANDLED;
1078 default:
1079 if (parm < 32 || parm > 255)
1081 bl = 0;
1082 if (input_key_is_in_map (input, parm) != 2)
1083 return MSG_NOT_HANDLED;
1085 if (end == min_end)
1086 return MSG_HANDLED;
1088 /* This means we want to refill the list box and start again */
1089 h->ret_value = B_USER;
1090 dlg_stop (h);
1092 else
1094 static char buff[MB_LEN_MAX] = "";
1095 GList *e;
1096 int i;
1097 int need_redraw = 0;
1098 int low = 4096;
1099 char *last_text = NULL;
1101 buff[bl++] = (char) parm;
1102 buff[bl] = '\0';
1104 switch (str_is_valid_char (buff, bl))
1106 case -1:
1107 bl = 0;
1108 MC_FALLTHROUGH;
1109 case -2:
1110 return MSG_HANDLED;
1111 default:
1112 break;
1115 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1116 e != NULL; i++, e = g_list_next (e))
1118 WLEntry *le = LENTRY (e->data);
1120 if (strncmp (input->buffer + start, le->text, end - start) == 0
1121 && strncmp (&le->text[end - start], buff, bl) == 0)
1123 if (need_redraw == 0)
1125 need_redraw = 1;
1126 listbox_select_entry (LISTBOX (g->current->data), i);
1127 last_text = le->text;
1129 else
1131 char *si, *sl;
1132 int si_num = 0;
1133 int sl_num = 0;
1135 /* count symbols between start and end */
1136 for (si = le->text + start; si < le->text + end;
1137 str_next_char (&si), si_num++)
1139 for (sl = last_text + start; sl < last_text + end;
1140 str_next_char (&sl), sl_num++)
1143 /* pointers to next symbols */
1144 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1145 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1147 while (si[0] != '\0' && sl[0] != '\0')
1149 char *nexti, *nextl;
1151 nexti = str_get_next_char (si);
1152 nextl = str_get_next_char (sl);
1154 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1155 break;
1157 si = nexti;
1158 sl = nextl;
1160 si_num++;
1163 last_text = le->text;
1165 si = &last_text[str_offset_to_pos (last_text, si_num)];
1166 if (low > si - last_text)
1167 low = si - last_text;
1169 need_redraw = 2;
1174 if (need_redraw == 2)
1176 insert_text (input, last_text, low);
1177 widget_draw (WIDGET (g->current->data));
1179 else if (need_redraw == 1)
1181 h->ret_value = B_ENTER;
1182 dlg_stop (h);
1184 bl = 0;
1187 return MSG_HANDLED;
1189 default:
1190 return dlg_default_callback (w, sender, msg, parm, data);
1194 /* --------------------------------------------------------------------------------------------- */
1196 /** Returns TRUE if the user would like to see us again */
1197 static gboolean
1198 complete_engine (WInput * in, int what_to_do)
1200 if (in->completions != NULL && str_offset_to_pos (in->buffer, in->point) != end)
1201 input_free_completions (in);
1203 if (in->completions == NULL)
1204 complete_engine_fill_completions (in);
1206 if (in->completions == NULL)
1207 tty_beep ();
1208 else
1210 if ((what_to_do & DO_INSERTION) != 0
1211 || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
1213 char *lc_complete = in->completions[0];
1215 if (!insert_text (in, lc_complete, strlen (lc_complete)) || in->completions[1] != NULL)
1216 tty_beep ();
1217 else
1218 input_free_completions (in);
1221 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
1223 int maxlen = 0, count = 0, i;
1224 int x, y, w, h;
1225 int start_x, start_y;
1226 char **p, *q;
1227 WDialog *query_dlg;
1228 WListbox *query_list;
1230 for (p = in->completions + 1; *p != NULL; count++, p++)
1232 i = str_term_width1 (*p);
1233 if (i > maxlen)
1234 maxlen = i;
1237 start_x = WIDGET (in)->x;
1238 start_y = WIDGET (in)->y;
1239 if (start_y - 2 >= count)
1241 y = start_y - 2 - count;
1242 h = 2 + count;
1244 else if (start_y >= LINES - start_y - 1)
1246 y = 0;
1247 h = start_y;
1249 else
1251 y = start_y + 1;
1252 h = LINES - start_y - 1;
1254 x = start - in->term_first_shown - 2 + start_x;
1255 w = maxlen + 4;
1256 if (x + w > COLS)
1257 x = COLS - w;
1258 if (x < 0)
1259 x = 0;
1260 if (x + w > COLS)
1261 w = COLS;
1263 input = in;
1264 min_end = end;
1265 query_height = h;
1266 query_width = w;
1268 query_dlg = dlg_create (TRUE, y, x, query_height, query_width, WPOS_KEEP_DEFAULT, TRUE,
1269 dialog_colors, query_callback, NULL, "[Completion]", NULL);
1270 query_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1271 group_add_widget (GROUP (query_dlg), query_list);
1273 for (p = in->completions + 1; *p != NULL; p++)
1274 listbox_add_item (query_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
1276 i = dlg_run (query_dlg);
1277 q = NULL;
1278 if (i == B_ENTER)
1280 listbox_get_current (query_list, &q, NULL);
1281 if (q != NULL)
1282 insert_text (in, q, strlen (q));
1284 if (q != NULL || end != min_end)
1285 input_free_completions (in);
1286 dlg_destroy (query_dlg);
1288 /* B_USER if user wants to start over again */
1289 return (i == B_USER);
1293 return FALSE;
1296 /* --------------------------------------------------------------------------------------------- */
1297 /*** public functions ****************************************************************************/
1298 /* --------------------------------------------------------------------------------------------- */
1300 /** Returns an array of matches, or NULL if none. */
1301 char **
1302 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1304 try_complete_automation_state_t state;
1305 char **matches = NULL;
1307 memset (&state, 0, sizeof (state));
1308 state.flags = flags;
1310 SHOW_C_CTX ("try_complete");
1311 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1313 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1315 /* Determine if this could be a command word. It is if it appears at
1316 the start of the line (ignoring preceding whitespace), or if it
1317 appears after a character that separates commands. And we have to
1318 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1319 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1320 try_complete_commands_prepare (&state, text, lc_start);
1322 try_complete_find_start_sign (&state);
1324 /* Command substitution? */
1325 if (state.p > state.q && state.p > state.r)
1327 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1328 matches = completion_matches (str_cget_next_char (state.p),
1329 command_completion_function,
1330 state.flags & (~INPUT_COMPLETE_FILENAMES));
1331 if (matches != NULL)
1332 *lc_start += str_get_next_char (state.p) - state.word;
1335 /* Variable name? */
1336 else if (state.q > state.p && state.q > state.r)
1338 SHOW_C_CTX ("try_complete:var_subst");
1339 matches = completion_matches (state.q, variable_completion_function, state.flags);
1340 if (matches != NULL)
1341 *lc_start += state.q - state.word;
1344 /* Starts with '@', then look through the known hostnames for
1345 completion first. */
1346 else if (state.r > state.p && state.r > state.q)
1348 SHOW_C_CTX ("try_complete:host_subst");
1349 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1350 if (matches != NULL)
1351 *lc_start += state.r - state.word;
1354 /* Starts with '~' and there is no slash in the word, then
1355 try completing this word as a username. */
1356 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1357 && strchr (state.word, PATH_SEP) == NULL)
1359 SHOW_C_CTX ("try_complete:user_subst");
1360 matches = completion_matches (state.word, username_completion_function, state.flags);
1363 /* If this word is in a command position, then
1364 complete over possible command names, including aliases, functions,
1365 and command names. */
1366 if (matches == NULL)
1367 matches = try_complete_all_possible (&state, text, lc_start);
1369 /* And finally if nothing found, try complete directory name */
1370 if (matches == NULL)
1372 state.in_command_position = 0;
1373 matches = try_complete_all_possible (&state, text, lc_start);
1376 g_free (state.word);
1378 if (matches != NULL &&
1379 (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
1380 (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
1382 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1383 char **m;
1385 for (m = matches; *m != NULL; m++)
1387 char *p;
1389 p = *m;
1390 *m = strutils_shell_escape (*m);
1391 g_free (p);
1395 return matches;
1398 /* --------------------------------------------------------------------------------------------- */
1400 void
1401 complete_engine_fill_completions (WInput * in)
1403 char *s;
1404 const char *word_separators;
1406 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1408 end = str_offset_to_pos (in->buffer, in->point);
1410 s = in->buffer;
1411 if (in->point != 0)
1413 /* get symbol before in->point */
1414 size_t i;
1416 for (i = in->point - 1; i > 0; i--)
1417 str_next_char (&s);
1420 for (; s >= in->buffer; str_prev_char (&s))
1422 start = s - in->buffer;
1423 if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer, s))
1424 break;
1427 if (start < end)
1429 str_next_char (&s);
1430 start = s - in->buffer;
1433 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
1436 /* --------------------------------------------------------------------------------------------- */
1438 /* declared in lib/widget/input.h */
1439 void
1440 complete (WInput * in)
1442 int engine_flags;
1444 if (!str_is_valid_string (in->buffer))
1445 return;
1447 if (in->completions != NULL)
1448 engine_flags = DO_QUERY;
1449 else
1451 engine_flags = DO_INSERTION;
1453 if (mc_global.widget.show_all_if_ambiguous)
1454 engine_flags |= DO_QUERY;
1457 while (complete_engine (in, engine_flags))
1461 /* --------------------------------------------------------------------------------------------- */