configure.c: fix AX_GCC_FUNC_ATTRIBUTE detection on custom CFLAGS
[midnight-commander.git] / lib / widget / input_complete.c
blob94d67a5832130345189becd3ee59a3953e4c2a38
1 /*
2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2019
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 WDialog *h = DIALOG (w);
1024 switch (msg)
1026 case MSG_KEY:
1027 switch (parm)
1029 case KEY_LEFT:
1030 case KEY_RIGHT:
1031 bl = 0;
1032 h->ret_value = 0;
1033 dlg_stop (h);
1034 return MSG_HANDLED;
1036 case KEY_BACKSPACE:
1037 bl = 0;
1038 /* exit from completion list if input line is empty */
1039 if (end == 0)
1041 h->ret_value = 0;
1042 dlg_stop (h);
1044 /* Refill the list box and start again */
1045 else if (end == min_end)
1047 end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1048 input_handle_char (input, parm);
1049 h->ret_value = B_USER;
1050 dlg_stop (h);
1052 else
1054 int new_end;
1055 int i;
1056 GList *e;
1058 new_end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1060 for (i = 0, e = listbox_get_first_link (LISTBOX (h->current->data));
1061 e != NULL; i++, e = g_list_next (e))
1063 WLEntry *le = LENTRY (e->data);
1065 if (strncmp (input->buffer + start, le->text, new_end - start) == 0)
1067 listbox_select_entry (LISTBOX (h->current->data), i);
1068 end = new_end;
1069 input_handle_char (input, parm);
1070 widget_redraw (WIDGET (h->current->data));
1071 break;
1075 return MSG_HANDLED;
1077 default:
1078 if (parm < 32 || parm > 255)
1080 bl = 0;
1081 if (input_key_is_in_map (input, parm) != 2)
1082 return MSG_NOT_HANDLED;
1084 if (end == min_end)
1085 return MSG_HANDLED;
1087 /* This means we want to refill the list box and start again */
1088 h->ret_value = B_USER;
1089 dlg_stop (h);
1091 else
1093 static char buff[MB_LEN_MAX] = "";
1094 GList *e;
1095 int i;
1096 int need_redraw = 0;
1097 int low = 4096;
1098 char *last_text = NULL;
1100 buff[bl++] = (char) parm;
1101 buff[bl] = '\0';
1103 switch (str_is_valid_char (buff, bl))
1105 case -1:
1106 bl = 0;
1107 MC_FALLTHROUGH;
1108 case -2:
1109 return MSG_HANDLED;
1110 default:
1111 break;
1114 for (i = 0, e = listbox_get_first_link (LISTBOX (h->current->data));
1115 e != NULL; i++, e = g_list_next (e))
1117 WLEntry *le = LENTRY (e->data);
1119 if (strncmp (input->buffer + start, le->text, end - start) == 0
1120 && strncmp (&le->text[end - start], buff, bl) == 0)
1122 if (need_redraw == 0)
1124 need_redraw = 1;
1125 listbox_select_entry (LISTBOX (h->current->data), i);
1126 last_text = le->text;
1128 else
1130 char *si, *sl;
1131 int si_num = 0;
1132 int sl_num = 0;
1134 /* count symbols between start and end */
1135 for (si = le->text + start; si < le->text + end;
1136 str_next_char (&si), si_num++)
1138 for (sl = last_text + start; sl < last_text + end;
1139 str_next_char (&sl), sl_num++)
1142 /* pointers to next symbols */
1143 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1144 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1146 while (si[0] != '\0' && sl[0] != '\0')
1148 char *nexti, *nextl;
1150 nexti = str_get_next_char (si);
1151 nextl = str_get_next_char (sl);
1153 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1154 break;
1156 si = nexti;
1157 sl = nextl;
1159 si_num++;
1162 last_text = le->text;
1164 si = &last_text[str_offset_to_pos (last_text, si_num)];
1165 if (low > si - last_text)
1166 low = si - last_text;
1168 need_redraw = 2;
1173 if (need_redraw == 2)
1175 insert_text (input, last_text, low);
1176 widget_redraw (WIDGET (h->current->data));
1178 else if (need_redraw == 1)
1180 h->ret_value = B_ENTER;
1181 dlg_stop (h);
1183 bl = 0;
1186 return MSG_HANDLED;
1188 default:
1189 return dlg_default_callback (w, sender, msg, parm, data);
1193 /* --------------------------------------------------------------------------------------------- */
1195 /** Returns TRUE if the user would like to see us again */
1196 static gboolean
1197 complete_engine (WInput * in, int what_to_do)
1199 if (in->completions != NULL && str_offset_to_pos (in->buffer, in->point) != end)
1200 input_free_completions (in);
1202 if (in->completions == NULL)
1203 complete_engine_fill_completions (in);
1205 if (in->completions == NULL)
1206 tty_beep ();
1207 else
1209 if ((what_to_do & DO_INSERTION) != 0
1210 || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
1212 char *lc_complete = in->completions[0];
1214 if (!insert_text (in, lc_complete, strlen (lc_complete)) || in->completions[1] != NULL)
1215 tty_beep ();
1216 else
1217 input_free_completions (in);
1220 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
1222 int maxlen = 0, count = 0, i;
1223 int x, y, w, h;
1224 int start_x, start_y;
1225 char **p, *q;
1226 WDialog *query_dlg;
1227 WListbox *query_list;
1229 for (p = in->completions + 1; *p != NULL; count++, p++)
1231 i = str_term_width1 (*p);
1232 if (i > maxlen)
1233 maxlen = i;
1236 start_x = WIDGET (in)->x;
1237 start_y = WIDGET (in)->y;
1238 if (start_y - 2 >= count)
1240 y = start_y - 2 - count;
1241 h = 2 + count;
1243 else if (start_y >= LINES - start_y - 1)
1245 y = 0;
1246 h = start_y;
1248 else
1250 y = start_y + 1;
1251 h = LINES - start_y - 1;
1253 x = start - in->term_first_shown - 2 + start_x;
1254 w = maxlen + 4;
1255 if (x + w > COLS)
1256 x = COLS - w;
1257 if (x < 0)
1258 x = 0;
1259 if (x + w > COLS)
1260 w = COLS;
1262 input = in;
1263 min_end = end;
1264 query_height = h;
1265 query_width = w;
1267 query_dlg = dlg_create (TRUE, y, x, query_height, query_width, WPOS_KEEP_DEFAULT, TRUE,
1268 dialog_colors, query_callback, NULL, "[Completion]", NULL);
1269 query_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1270 add_widget (query_dlg, query_list);
1272 for (p = in->completions + 1; *p != NULL; p++)
1273 listbox_add_item (query_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
1275 i = dlg_run (query_dlg);
1276 q = NULL;
1277 if (i == B_ENTER)
1279 listbox_get_current (query_list, &q, NULL);
1280 if (q != NULL)
1281 insert_text (in, q, strlen (q));
1283 if (q != NULL || end != min_end)
1284 input_free_completions (in);
1285 dlg_destroy (query_dlg);
1287 /* B_USER if user wants to start over again */
1288 return (i == B_USER);
1292 return FALSE;
1295 /* --------------------------------------------------------------------------------------------- */
1296 /*** public functions ****************************************************************************/
1297 /* --------------------------------------------------------------------------------------------- */
1299 /** Returns an array of matches, or NULL if none. */
1300 char **
1301 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1303 try_complete_automation_state_t state;
1304 char **matches = NULL;
1306 memset (&state, 0, sizeof (state));
1307 state.flags = flags;
1309 SHOW_C_CTX ("try_complete");
1310 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1312 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1314 /* Determine if this could be a command word. It is if it appears at
1315 the start of the line (ignoring preceding whitespace), or if it
1316 appears after a character that separates commands. And we have to
1317 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1318 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1319 try_complete_commands_prepare (&state, text, lc_start);
1321 try_complete_find_start_sign (&state);
1323 /* Command substitution? */
1324 if (state.p > state.q && state.p > state.r)
1326 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1327 matches = completion_matches (str_cget_next_char (state.p),
1328 command_completion_function,
1329 state.flags & (~INPUT_COMPLETE_FILENAMES));
1330 if (matches != NULL)
1331 *lc_start += str_get_next_char (state.p) - state.word;
1334 /* Variable name? */
1335 else if (state.q > state.p && state.q > state.r)
1337 SHOW_C_CTX ("try_complete:var_subst");
1338 matches = completion_matches (state.q, variable_completion_function, state.flags);
1339 if (matches != NULL)
1340 *lc_start += state.q - state.word;
1343 /* Starts with '@', then look through the known hostnames for
1344 completion first. */
1345 else if (state.r > state.p && state.r > state.q)
1347 SHOW_C_CTX ("try_complete:host_subst");
1348 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1349 if (matches != NULL)
1350 *lc_start += state.r - state.word;
1353 /* Starts with '~' and there is no slash in the word, then
1354 try completing this word as a username. */
1355 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1356 && strchr (state.word, PATH_SEP) == NULL)
1358 SHOW_C_CTX ("try_complete:user_subst");
1359 matches = completion_matches (state.word, username_completion_function, state.flags);
1362 /* If this word is in a command position, then
1363 complete over possible command names, including aliases, functions,
1364 and command names. */
1365 if (matches == NULL)
1366 matches = try_complete_all_possible (&state, text, lc_start);
1368 /* And finally if nothing found, try complete directory name */
1369 if (matches == NULL)
1371 state.in_command_position = 0;
1372 matches = try_complete_all_possible (&state, text, lc_start);
1375 g_free (state.word);
1377 if (matches != NULL &&
1378 (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
1379 (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
1381 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1382 char **m;
1384 for (m = matches; *m != NULL; m++)
1386 char *p;
1388 p = *m;
1389 *m = strutils_shell_escape (*m);
1390 g_free (p);
1394 return matches;
1397 /* --------------------------------------------------------------------------------------------- */
1399 void
1400 complete_engine_fill_completions (WInput * in)
1402 char *s;
1403 const char *word_separators;
1405 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1407 end = str_offset_to_pos (in->buffer, in->point);
1409 s = in->buffer;
1410 if (in->point != 0)
1412 /* get symbol before in->point */
1413 size_t i;
1415 for (i = in->point - 1; i > 0; i--)
1416 str_next_char (&s);
1419 for (; s >= in->buffer; str_prev_char (&s))
1421 start = s - in->buffer;
1422 if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer, s))
1423 break;
1426 if (start < end)
1428 str_next_char (&s);
1429 start = s - in->buffer;
1432 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
1435 /* --------------------------------------------------------------------------------------------- */
1437 /* declared in lib/widget/input.h */
1438 void
1439 complete (WInput * in)
1441 int engine_flags;
1443 if (!str_is_valid_string (in->buffer))
1444 return;
1446 if (in->completions != NULL)
1447 engine_flags = DO_QUERY;
1448 else
1450 engine_flags = DO_INSERTION;
1452 if (mc_global.widget.show_all_if_ambiguous)
1453 engine_flags |= DO_QUERY;
1456 while (complete_engine (in, engine_flags))
1460 /* --------------------------------------------------------------------------------------------- */