Merge lib/strescape.h into lib/strutil.h. Rename functions.
[midnight-commander.git] / lib / widget / input_complete.c
blob724321f99ad4cd7ec18c03bebd493ab91d8224c8
1 /*
2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2024
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-2022
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/strutil.h"
52 #include "lib/util.h"
53 #include "lib/widget.h"
55 /*** global variables ****************************************************************************/
57 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
58 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
59 extern char **environ;
60 #endif
62 /*** file scope macro definitions ****************************************************************/
64 /* #define DO_COMPLETION_DEBUG */
65 #ifdef DO_COMPLETION_DEBUG
66 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
67 #else
68 #define SHOW_C_CTX(func)
69 #endif /* DO_CMPLETION_DEBUG */
71 #define DO_INSERTION 1
72 #define DO_QUERY 2
74 /*** file scope type declarations ****************************************************************/
76 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
78 typedef struct
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;
89 /*** forward declarations (file scope functions) *************************************************/
91 char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
92 void complete_engine_fill_completions (WInput * in);
94 /*** file scope variables ************************************************************************/
96 static char **hosts = NULL;
97 static char **hosts_p = NULL;
98 static int hosts_alloclen = 0;
100 static WInput *input;
101 static int min_end;
102 static int start = 0;
103 static int end = 0;
105 /* --------------------------------------------------------------------------------------------- */
106 /*** file scope functions ************************************************************************/
107 /* --------------------------------------------------------------------------------------------- */
109 #ifdef DO_COMPLETION_DEBUG
111 * Useful to print/debug completion flags
113 static const char *
114 show_c_flags (input_complete_t flags)
116 static char s_cf[] = "FHCVUDS";
118 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
119 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
120 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
121 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
122 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
123 s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
124 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
126 return s_cf;
128 #endif /* DO_CMPLETION_DEBUG */
130 /* --------------------------------------------------------------------------------------------- */
132 static char *
133 filename_completion_function (const char *text, int state, input_complete_t flags)
135 static DIR *directory = NULL;
136 static char *filename = NULL;
137 static char *dirname = NULL;
138 static char *users_dirname = NULL;
139 static size_t filename_len = 0;
140 static vfs_path_t *dirname_vpath = NULL;
142 gboolean isdir = TRUE, isexec = FALSE;
143 struct vfs_dirent *entry = NULL;
145 SHOW_C_CTX ("filename_completion_function");
147 if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
149 char *u_text;
150 char *result;
151 char *e_result;
153 u_text = str_shell_unescape (text);
155 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
156 g_free (u_text);
158 e_result = str_shell_escape (result);
159 g_free (result);
161 return e_result;
164 /* If we're starting the match process, initialize us a bit. */
165 if (state == 0)
167 const char *temp;
169 g_free (dirname);
170 g_free (filename);
171 g_free (users_dirname);
172 vfs_path_free (dirname_vpath, TRUE);
174 if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
176 filename = g_strdup (++temp);
177 dirname = g_strndup (text, temp - text);
179 else
181 dirname = g_strdup (".");
182 filename = g_strdup (text);
185 /* We aren't done yet. We also support the "~user" syntax. */
187 /* Save the version of the directory that the user typed. */
188 users_dirname = dirname;
189 dirname = tilde_expand (dirname);
190 canonicalize_pathname (dirname);
191 dirname_vpath = vfs_path_from_str (dirname);
193 /* Here we should do something with variable expansion
194 and `command`.
195 Maybe a dream - UNIMPLEMENTED yet. */
197 directory = mc_opendir (dirname_vpath);
198 filename_len = strlen (filename);
201 /* Now that we have some state, we can read the directory. */
203 while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
205 if (!str_is_valid_string (entry->d_name))
206 continue;
208 /* Special case for no filename.
209 All entries except "." and ".." match. */
210 if (filename_len == 0)
212 if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
213 continue;
215 else
217 /* Otherwise, if these match up to the length of filename, then
218 it may be a match. */
219 if ((entry->d_name[0] != filename[0]) ||
220 ((NLENGTH (entry)) < filename_len) ||
221 strncmp (filename, entry->d_name, filename_len) != 0)
222 continue;
225 isdir = TRUE;
226 isexec = FALSE;
229 struct stat tempstat;
230 vfs_path_t *tmp_vpath;
232 tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
234 /* Unix version */
235 if (mc_stat (tmp_vpath, &tempstat) == 0)
237 uid_t my_uid;
238 gid_t my_gid;
240 my_uid = getuid ();
241 my_gid = getgid ();
243 if (!S_ISDIR (tempstat.st_mode))
245 isdir = FALSE;
247 if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
248 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
249 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
250 (tempstat.st_mode & 0001) != 0)
251 isexec = TRUE;
254 else
256 /* stat failed, strange. not a dir in any case */
257 isdir = FALSE;
259 vfs_path_free (tmp_vpath, TRUE);
262 if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
263 break;
264 if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
265 break;
266 if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
267 break;
270 if (entry == NULL)
272 if (directory != NULL)
274 mc_closedir (directory);
275 directory = NULL;
277 MC_PTR_FREE (dirname);
278 vfs_path_free (dirname_vpath, TRUE);
279 dirname_vpath = NULL;
280 MC_PTR_FREE (filename);
281 MC_PTR_FREE (users_dirname);
282 return NULL;
286 GString *temp;
288 temp = g_string_sized_new (16);
290 if (users_dirname != NULL && (users_dirname[0] != '.' || users_dirname[1] != '\0'))
292 g_string_append (temp, users_dirname);
294 /* We need a '/' at the end. */
295 if (!IS_PATH_SEP (temp->str[temp->len - 1]))
296 g_string_append_c (temp, PATH_SEP);
298 g_string_append (temp, entry->d_name);
299 if (isdir)
300 g_string_append_c (temp, PATH_SEP);
302 return g_string_free (temp, FALSE);
306 /* --------------------------------------------------------------------------------------------- */
307 /** We assume here that text[0] == '~' , if you want to call it in another way,
308 you have to change the code */
310 static char *
311 username_completion_function (const char *text, int state, input_complete_t flags)
313 static struct passwd *entry = NULL;
314 static size_t userlen = 0;
316 (void) flags;
317 SHOW_C_CTX ("username_completion_function");
319 if (text[0] == '\\' && text[1] == '~')
320 text++;
321 if (state == 0)
322 { /* Initialization stuff */
323 setpwent ();
324 userlen = strlen (text + 1);
327 while ((entry = getpwent ()) != NULL)
329 /* Null usernames should result in all users as possible completions. */
330 if (userlen == 0)
331 break;
332 if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
333 break;
336 if (entry != NULL)
337 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
339 endpwent ();
340 return NULL;
343 /* --------------------------------------------------------------------------------------------- */
344 /** We assume text [0] == '$' and want to have a look at text [1], if it is
345 equal to '{', so that we should append '}' at the end */
347 static char *
348 variable_completion_function (const char *text, int state, input_complete_t flags)
350 static char **env_p = NULL;
351 static gboolean isbrace = FALSE;
352 static size_t varlen = 0;
353 const char *p = NULL;
355 (void) flags;
356 SHOW_C_CTX ("variable_completion_function");
358 if (state == 0)
359 { /* Initialization stuff */
360 isbrace = (text[1] == '{');
361 varlen = strlen (text + 1 + isbrace);
362 env_p = environ;
365 while (*env_p != NULL)
367 p = strchr (*env_p, '=');
368 if (p != NULL && ((size_t) (p - *env_p) >= varlen)
369 && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
370 break;
371 env_p++;
374 if (*env_p == NULL)
375 return NULL;
378 GString *temp;
380 temp = g_string_new_len (*env_p, p - *env_p);
382 if (isbrace)
384 g_string_prepend_c (temp, '{');
385 g_string_append_c (temp, '}');
387 g_string_prepend_c (temp, '$');
389 env_p++;
391 return g_string_free (temp, FALSE);
395 /* --------------------------------------------------------------------------------------------- */
397 static void
398 fetch_hosts (const char *filename)
400 FILE *file;
401 char buffer[256];
402 char *name;
403 char *lc_start;
404 char *bi;
406 file = fopen (filename, "r");
407 if (file == NULL)
408 return;
410 while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
412 /* Skip to first character. */
413 for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
416 /* Ignore comments... */
417 if (bi[0] == '#')
418 continue;
420 /* Handle $include. */
421 if (strncmp (bi, "$include ", 9) == 0)
423 char *includefile, *t;
425 /* Find start of filename. */
426 includefile = bi + 9;
427 while (*includefile != '\0' && whitespace (*includefile))
428 includefile++;
429 t = includefile;
431 /* Find end of filename. */
432 while (t[0] != '\0' && !str_isspace (t))
433 str_next_char (&t);
434 *t = '\0';
436 fetch_hosts (includefile);
437 continue;
440 /* Skip IP #s. */
441 while (bi[0] != '\0' && !str_isspace (bi))
442 str_next_char (&bi);
444 /* Get the host names separated by white space. */
445 while (bi[0] != '\0' && bi[0] != '#')
447 while (bi[0] != '\0' && str_isspace (bi))
448 str_next_char (&bi);
449 if (bi[0] == '#')
450 continue;
451 for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
454 if (bi == lc_start)
455 continue;
457 name = g_strndup (lc_start, bi - lc_start);
460 char **host_p;
461 int j;
463 j = hosts_p - hosts;
465 if (j >= hosts_alloclen)
467 hosts_alloclen += 30;
468 hosts = g_renew (char *, hosts, hosts_alloclen + 1);
469 hosts_p = hosts + j;
472 for (host_p = hosts; host_p < hosts_p; host_p++)
473 if (strcmp (name, *host_p) == 0)
474 break; /* We do not want any duplicates */
476 if (host_p == hosts_p)
478 *(hosts_p++) = name;
479 *hosts_p = NULL;
481 else
482 g_free (name);
487 fclose (file);
490 /* --------------------------------------------------------------------------------------------- */
492 static char *
493 hostname_completion_function (const char *text, int state, input_complete_t flags)
495 static char **host_p = NULL;
496 static size_t textstart = 0;
497 static size_t textlen = 0;
499 (void) flags;
500 SHOW_C_CTX ("hostname_completion_function");
502 if (state == 0)
503 { /* Initialization stuff */
504 const char *p;
506 g_strfreev (hosts);
507 hosts_alloclen = 30;
508 hosts = g_new (char *, hosts_alloclen + 1);
509 *hosts = NULL;
510 hosts_p = hosts;
511 p = getenv ("HOSTFILE");
512 fetch_hosts (p != NULL ? p : "/etc/hosts");
513 host_p = hosts;
514 textstart = (*text == '@') ? 1 : 0;
515 textlen = strlen (text + textstart);
518 for (; *host_p != NULL; host_p++)
520 if (textlen == 0)
521 break; /* Match all of them */
522 if (strncmp (text + textstart, *host_p, textlen) == 0)
523 break;
526 if (*host_p == NULL)
528 g_strfreev (hosts);
529 hosts = NULL;
530 return NULL;
534 GString *temp;
536 temp = g_string_sized_new (8);
538 if (textstart != 0)
539 g_string_append_c (temp, '@');
540 g_string_append (temp, *host_p);
541 host_p++;
543 return g_string_free (temp, FALSE);
547 /* --------------------------------------------------------------------------------------------- */
549 * This is the function to call when the word to complete is in a position
550 * where a command word can be found. It looks around $PATH, looking for
551 * commands that match. It also scans aliases, function names, and the
552 * table of shell built-ins.
555 static char *
556 command_completion_function (const char *text, int state, input_complete_t flags)
558 static const char *path_end = NULL;
559 static gboolean isabsolute = FALSE;
560 static int phase = 0;
561 static size_t text_len = 0;
562 static const char *const *words = NULL;
563 static char *path = NULL;
564 static char *cur_path = NULL;
565 static char *cur_word = NULL;
566 static int init_state = 0;
567 static const char *const bash_reserved[] = {
568 "if", "then", "else", "elif", "fi", "case", "esac", "for",
569 "select", "while", "until", "do", "done", "in", "function", 0
571 static const char *const bash_builtins[] = {
572 "alias", "bg", "bind", "break", "builtin", "cd", "command",
573 "continue", "declare", "dirs", "echo", "enable", "eval",
574 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
575 "help", "history", "jobs", "kill", "let", "local", "logout",
576 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
577 "shift", "source", "suspend", "test", "times", "trap", "type",
578 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
581 char *u_text;
582 char *p, *found;
584 SHOW_C_CTX ("command_completion_function");
586 if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
587 return NULL;
589 u_text = str_shell_unescape (text);
590 flags &= ~INPUT_COMPLETE_SHELL_ESC;
592 if (state == 0)
593 { /* Initialize us a little bit */
594 isabsolute = strchr (u_text, PATH_SEP) != NULL;
595 if (!isabsolute)
597 words = bash_reserved;
598 phase = 0;
599 text_len = strlen (u_text);
601 if (path == NULL)
603 path = g_strdup (getenv ("PATH"));
604 if (path != NULL)
606 p = path;
607 path_end = strchr (p, '\0');
608 while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
609 *p++ = '\0';
615 if (isabsolute)
617 p = filename_completion_function (u_text, state, flags);
619 if (p != NULL)
621 char *temp_p = p;
623 p = str_shell_escape (p);
624 g_free (temp_p);
627 g_free (u_text);
628 return p;
631 found = NULL;
632 switch (phase)
634 case 0: /* Reserved words */
635 for (; *words != NULL; words++)
636 if (strncmp (*words, u_text, text_len) == 0)
638 g_free (u_text);
639 return g_strdup (*(words++));
641 phase++;
642 words = bash_builtins;
643 MC_FALLTHROUGH;
644 case 1: /* Builtin commands */
645 for (; *words != NULL; words++)
646 if (strncmp (*words, u_text, text_len) == 0)
648 g_free (u_text);
649 return g_strdup (*(words++));
651 phase++;
652 if (path == NULL)
653 break;
654 cur_path = path;
655 cur_word = NULL;
656 MC_FALLTHROUGH;
657 case 2: /* And looking through the $PATH */
658 while (found == NULL)
660 if (cur_word == NULL)
662 char *expanded;
664 if (cur_path >= path_end)
665 break;
666 expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
667 cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
668 g_free (expanded);
669 cur_path = strchr (cur_path, '\0') + 1;
670 init_state = state;
672 found = filename_completion_function (cur_word, state - init_state, flags);
673 if (found == NULL)
674 MC_PTR_FREE (cur_word);
676 MC_FALLTHROUGH;
677 default:
678 break;
681 if (found == NULL)
682 MC_PTR_FREE (path);
683 else
685 p = strrchr (found, PATH_SEP);
686 if (p != NULL)
688 char *tmp = found;
690 found = str_shell_escape (p + 1);
691 g_free (tmp);
695 g_free (u_text);
696 return found;
699 /* --------------------------------------------------------------------------------------------- */
701 static int
702 match_compare (const void *a, const void *b)
704 return strcmp (*(char *const *) a, *(char *const *) b);
707 /* --------------------------------------------------------------------------------------------- */
708 /** Returns an array of char * matches with the longest common denominator
709 in the 1st entry. Then a NULL terminated list of different possible
710 completions follows.
711 You have to supply your own CompletionFunction with the word you
712 want to complete as the first argument and an count of previous matches
713 as the second.
714 In case no matches were found we return NULL. */
716 static char **
717 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
719 /* Number of slots in match_list. */
720 size_t match_list_size = 30;
721 /* The list of matches. */
722 char **match_list;
723 /* Number of matches actually found. */
724 size_t matches = 0;
726 /* Temporary string binder. */
727 char *string;
729 match_list = g_new (char *, match_list_size + 1);
730 match_list[1] = NULL;
732 while ((string = (*entry_function) (text, matches, flags)) != NULL)
734 if (matches + 1 == match_list_size)
736 match_list_size += 30;
737 match_list = (char **) g_renew (char *, match_list, match_list_size + 1);
739 match_list[++matches] = string;
740 match_list[matches + 1] = NULL;
743 /* If there were any matches, then look through them finding out the
744 lowest common denominator. That then becomes match_list[0]. */
745 if (matches == 0)
746 MC_PTR_FREE (match_list); /* There were no matches. */
747 else
749 /* If only one match, just use that. */
750 if (matches == 1)
752 match_list[0] = match_list[1];
753 match_list[1] = NULL;
755 else
757 size_t i = 1;
758 int low = 4096; /* Count of max-matched characters. */
759 size_t j;
761 qsort (match_list + 1, matches, sizeof (char *), match_compare);
763 /* And compare each member of the list with
764 the next, finding out where they stop matching.
765 If we find two equal strings, we have to put one away... */
767 j = i + 1;
768 while (j < matches + 1)
770 char *si, *sj;
771 char *ni, *nj;
773 for (si = match_list[i], sj = match_list[j]; si[0] != '\0' && sj[0] != '\0';)
776 ni = str_get_next_char (si);
777 nj = str_get_next_char (sj);
779 if (ni - si != nj - sj)
780 break;
781 if (strncmp (si, sj, ni - si) != 0)
782 break;
784 si = ni;
785 sj = nj;
788 if (si[0] == '\0' && sj[0] == '\0')
789 { /* Two equal strings */
790 g_free (match_list[j]);
791 j++;
792 if (j > matches)
793 break;
794 continue; /* Look for a run of equal strings */
796 else if (low > si - match_list[i])
797 low = si - match_list[i];
798 if (i + 1 != j) /* So there's some gap */
799 match_list[i + 1] = match_list[j];
800 i++;
801 j++;
803 matches = i;
804 match_list[matches + 1] = NULL;
805 match_list[0] = g_strndup (match_list[1], low);
809 return match_list;
812 /* --------------------------------------------------------------------------------------------- */
813 /** Check if directory completion is needed */
814 static gboolean
815 check_is_cd (const char *text, int lc_start, input_complete_t flags)
817 const char *p, *q;
819 SHOW_C_CTX ("check_is_cd");
821 if ((flags & INPUT_COMPLETE_CD) == 0)
822 return FALSE;
824 /* Skip initial spaces */
825 p = text;
826 q = text + lc_start;
827 while (p < q && p[0] != '\0' && str_isspace (p))
828 str_cnext_char (&p);
830 /* Check if the command is "cd" and the cursor is after it */
831 return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
834 /* --------------------------------------------------------------------------------------------- */
836 static void
837 try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
839 const char *command_separator_chars = ";|&{(`";
840 char *ti;
842 if (*lc_start == 0)
843 ti = text;
844 else
846 ti = str_get_prev_char (&text[*lc_start]);
847 while (ti > text && whitespace (ti[0]))
848 str_prev_char (&ti);
851 if (ti == text)
852 state->in_command_position++;
853 else if (strchr (command_separator_chars, ti[0]) != NULL)
855 state->in_command_position++;
856 if (ti != text)
858 int this_char, prev_char;
860 /* Handle the two character tokens '>&', '<&', and '>|'.
861 We are not in a command position after one of these. */
862 this_char = ti[0];
863 prev_char = str_get_prev_char (ti)[0];
865 /* Quoted */
866 if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
867 || (this_char == '|' && prev_char == '>') || (ti != text
868 && str_get_prev_char (ti)[0] == '\\'))
869 state->in_command_position = 0;
874 /* --------------------------------------------------------------------------------------------- */
876 static void
877 try_complete_find_start_sign (try_complete_automation_state_t * state)
879 if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
880 state->p = strrchr (state->word, '`');
881 if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
883 state->q = strrchr (state->word, '$');
885 /* don't substitute variable in \$ case */
886 if (str_is_char_escaped (state->word, state->q))
888 /* drop '\\' */
889 str_move (state->q - 1, state->q);
890 /* adjust flags */
891 state->flags &= ~INPUT_COMPLETE_VARIABLES;
892 state->q = NULL;
895 if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
896 state->r = strrchr (state->word, '@');
897 if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
899 if (state->q > state->p)
900 state->p = str_get_next_char (state->q);
901 state->q = NULL;
905 /* --------------------------------------------------------------------------------------------- */
907 static char **
908 try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
910 char **matches = NULL;
912 if (state->in_command_position != 0)
914 SHOW_C_CTX ("try_complete:cmd_subst");
915 matches =
916 completion_matches (state->word, command_completion_function,
917 state->flags & (~INPUT_COMPLETE_FILENAMES));
919 else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
921 if (state->is_cd)
922 state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
923 SHOW_C_CTX ("try_complete:filename_subst_1");
924 matches = completion_matches (state->word, filename_completion_function, state->flags);
926 if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
928 state->q = text + *lc_start;
929 for (state->p = text;
930 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
931 str_next_char (&state->p))
933 if (strncmp (state->p, "cd", 2) == 0)
934 for (state->p += 2;
935 *state->p != '\0' && state->p < state->q && whitespace (*state->p);
936 str_next_char (&state->p))
938 if (state->p == state->q)
940 char *cdpath_ref, *cdpath;
941 char c;
943 cdpath_ref = g_strdup (getenv ("CDPATH"));
944 cdpath = cdpath_ref;
945 c = (cdpath == NULL) ? '\0' : ':';
947 while (matches == NULL && c == ':')
949 char *s;
951 s = strchr (cdpath, ':');
952 /* cppcheck-suppress nullPointer */
953 if (s == NULL)
954 s = strchr (cdpath, '\0');
955 c = *s;
956 *s = '\0';
957 if (*cdpath != '\0')
959 state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
960 SHOW_C_CTX ("try_complete:filename_subst_2");
961 matches =
962 completion_matches (state->r, filename_completion_function,
963 state->flags);
964 g_free (state->r);
966 *s = c;
967 cdpath = str_get_next_char (s);
969 g_free (cdpath_ref);
973 return matches;
976 /* --------------------------------------------------------------------------------------------- */
978 static gboolean
979 insert_text (WInput * in, char *text, ssize_t size)
981 size_t text_len;
982 int buff_len;
983 ssize_t new_size;
985 text_len = strlen (text);
986 buff_len = str_length (in->buffer->str);
987 if (size < 0)
988 size = (ssize_t) text_len;
989 else
990 size = MIN (size, (ssize_t) text_len);
992 new_size = size + start - end;
993 if (new_size != 0)
995 /* make a hole within buffer */
997 size_t tail_len;
999 tail_len = in->buffer->len - end;
1000 if (tail_len != 0)
1002 char *tail;
1003 size_t hole_end;
1005 tail = g_strndup (in->buffer->str + end, tail_len);
1007 hole_end = end + new_size;
1008 if (in->buffer->len < hole_end)
1009 g_string_set_size (in->buffer, hole_end + tail_len);
1011 g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
1013 g_free (tail);
1017 g_string_overwrite_len (in->buffer, start, text, size);
1019 in->point += str_length (in->buffer->str) - buff_len;
1020 input_update (in, TRUE);
1021 end += new_size;
1023 return new_size != 0;
1026 /* --------------------------------------------------------------------------------------------- */
1028 static cb_ret_t
1029 complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1031 static int bl = 0;
1033 WGroup *g = GROUP (w);
1034 WDialog *h = DIALOG (w);
1036 switch (msg)
1038 case MSG_KEY:
1039 switch (parm)
1041 case KEY_LEFT:
1042 case KEY_RIGHT:
1043 bl = 0;
1044 h->ret_value = 0;
1045 dlg_close (h);
1046 return MSG_HANDLED;
1048 case KEY_BACKSPACE:
1049 bl = 0;
1050 /* exit from completion list if input line is empty */
1051 if (end == 0)
1053 h->ret_value = 0;
1054 dlg_close (h);
1056 /* Refill the list box and start again */
1057 else if (end == min_end)
1059 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1060 input_handle_char (input, parm);
1061 h->ret_value = B_USER;
1062 dlg_close (h);
1064 else
1066 int new_end;
1067 int i;
1068 GList *e;
1070 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1072 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1073 e != NULL; i++, e = g_list_next (e))
1075 WLEntry *le = LENTRY (e->data);
1077 if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1079 listbox_set_current (LISTBOX (g->current->data), i);
1080 end = new_end;
1081 input_handle_char (input, parm);
1082 widget_draw (WIDGET (g->current->data));
1083 break;
1087 return MSG_HANDLED;
1089 default:
1090 if (parm < 32 || parm > 255)
1092 bl = 0;
1093 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1094 return MSG_NOT_HANDLED;
1096 if (end == min_end)
1097 return MSG_HANDLED;
1099 /* This means we want to refill the list box and start again */
1100 h->ret_value = B_USER;
1101 dlg_close (h);
1103 else
1105 static char buff[MB_LEN_MAX] = "";
1106 GList *e;
1107 int i;
1108 int need_redraw = 0;
1109 int low = 4096;
1110 char *last_text = NULL;
1112 buff[bl++] = (char) parm;
1113 buff[bl] = '\0';
1115 switch (str_is_valid_char (buff, bl))
1117 case -1:
1118 bl = 0;
1119 MC_FALLTHROUGH;
1120 case -2:
1121 return MSG_HANDLED;
1122 default:
1123 break;
1126 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1127 e != NULL; i++, e = g_list_next (e))
1129 WLEntry *le = LENTRY (e->data);
1131 if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1132 && strncmp (le->text + end - start, buff, bl) == 0)
1134 if (need_redraw == 0)
1136 need_redraw = 1;
1137 listbox_set_current (LISTBOX (g->current->data), i);
1138 last_text = le->text;
1140 else
1142 char *si, *sl;
1143 int si_num = 0;
1144 int sl_num = 0;
1146 /* count symbols between start and end */
1147 for (si = le->text + start; si < le->text + end;
1148 str_next_char (&si), si_num++)
1150 for (sl = last_text + start; sl < last_text + end;
1151 str_next_char (&sl), sl_num++)
1154 /* pointers to next symbols */
1155 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1156 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1158 while (si[0] != '\0' && sl[0] != '\0')
1160 char *nexti, *nextl;
1162 nexti = str_get_next_char (si);
1163 nextl = str_get_next_char (sl);
1165 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1166 break;
1168 si = nexti;
1169 sl = nextl;
1171 si_num++;
1174 last_text = le->text;
1176 si = &last_text[str_offset_to_pos (last_text, si_num)];
1177 if (low > si - last_text)
1178 low = si - last_text;
1180 need_redraw = 2;
1185 if (need_redraw == 2)
1187 insert_text (input, last_text, low);
1188 widget_draw (WIDGET (g->current->data));
1190 else if (need_redraw == 1)
1192 h->ret_value = B_ENTER;
1193 dlg_close (h);
1195 bl = 0;
1198 return MSG_HANDLED;
1200 default:
1201 return dlg_default_callback (w, sender, msg, parm, data);
1205 /* --------------------------------------------------------------------------------------------- */
1207 /** Returns TRUE if the user would like to see us again */
1208 static gboolean
1209 complete_engine (WInput * in, int what_to_do)
1211 if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1212 input_complete_free (in);
1214 if (in->completions == NULL)
1215 complete_engine_fill_completions (in);
1217 if (in->completions == NULL)
1218 tty_beep ();
1219 else
1221 if ((what_to_do & DO_INSERTION) != 0
1222 || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
1224 char *lc_complete = in->completions[0];
1226 if (!insert_text (in, lc_complete, -1) || in->completions[1] != NULL)
1227 tty_beep ();
1228 else
1229 input_complete_free (in);
1232 if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
1234 int maxlen = 0, count = 0, i;
1235 int x, y, w, h;
1236 int start_x, start_y;
1237 char **p, *q;
1238 WDialog *complete_dlg;
1239 WListbox *complete_list;
1241 for (p = in->completions + 1; *p != NULL; count++, p++)
1243 i = str_term_width1 (*p);
1244 if (i > maxlen)
1245 maxlen = i;
1248 start_x = WIDGET (in)->rect.x;
1249 start_y = WIDGET (in)->rect.y;
1250 if (start_y - 2 >= count)
1252 y = start_y - 2 - count;
1253 h = 2 + count;
1255 else if (start_y >= LINES - start_y - 1)
1257 y = 0;
1258 h = start_y;
1260 else
1262 y = start_y + 1;
1263 h = LINES - start_y - 1;
1265 x = start - in->term_first_shown - 2 + start_x;
1266 w = maxlen + 4;
1267 if (x + w > COLS)
1268 x = COLS - w;
1269 if (x < 0)
1270 x = 0;
1271 if (x + w > COLS)
1272 w = COLS;
1274 input = in;
1275 min_end = end;
1277 complete_dlg =
1278 dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE,
1279 dialog_colors, complete_callback, NULL, "[Completion]", NULL);
1280 complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1281 group_add_widget (GROUP (complete_dlg), complete_list);
1283 for (p = in->completions + 1; *p != NULL; p++)
1284 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
1286 i = dlg_run (complete_dlg);
1287 q = NULL;
1288 if (i == B_ENTER)
1290 listbox_get_current (complete_list, &q, NULL);
1291 if (q != NULL)
1292 insert_text (in, q, -1);
1294 if (q != NULL || end != min_end)
1295 input_complete_free (in);
1296 widget_destroy (WIDGET (complete_dlg));
1298 /* B_USER if user wants to start over again */
1299 return (i == B_USER);
1303 return FALSE;
1306 /* --------------------------------------------------------------------------------------------- */
1307 /*** public functions ****************************************************************************/
1308 /* --------------------------------------------------------------------------------------------- */
1310 /** Returns an array of matches, or NULL if none. */
1311 char **
1312 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1314 try_complete_automation_state_t state;
1315 char **matches = NULL;
1317 memset (&state, 0, sizeof (state));
1318 state.flags = flags;
1320 SHOW_C_CTX ("try_complete");
1321 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1323 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1325 /* Determine if this could be a command word. It is if it appears at
1326 the start of the line (ignoring preceding whitespace), or if it
1327 appears after a character that separates commands. And we have to
1328 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1329 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1330 try_complete_commands_prepare (&state, text, lc_start);
1332 try_complete_find_start_sign (&state);
1334 /* Command substitution? */
1335 if (state.p > state.q && state.p > state.r)
1337 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1338 matches = completion_matches (str_cget_next_char (state.p),
1339 command_completion_function,
1340 state.flags & (~INPUT_COMPLETE_FILENAMES));
1341 if (matches != NULL)
1342 *lc_start += str_get_next_char (state.p) - state.word;
1345 /* Variable name? */
1346 else if (state.q > state.p && state.q > state.r)
1348 SHOW_C_CTX ("try_complete:var_subst");
1349 matches = completion_matches (state.q, variable_completion_function, state.flags);
1350 if (matches != NULL)
1351 *lc_start += state.q - state.word;
1354 /* Starts with '@', then look through the known hostnames for
1355 completion first. */
1356 else if (state.r > state.p && state.r > state.q)
1358 SHOW_C_CTX ("try_complete:host_subst");
1359 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1360 if (matches != NULL)
1361 *lc_start += state.r - state.word;
1364 /* Starts with '~' and there is no slash in the word, then
1365 try completing this word as a username. */
1366 if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1367 && strchr (state.word, PATH_SEP) == NULL)
1369 SHOW_C_CTX ("try_complete:user_subst");
1370 matches = completion_matches (state.word, username_completion_function, state.flags);
1373 /* If this word is in a command position, then
1374 complete over possible command names, including aliases, functions,
1375 and command names. */
1376 if (matches == NULL)
1377 matches = try_complete_all_possible (&state, text, lc_start);
1379 /* And finally if nothing found, try complete directory name */
1380 if (matches == NULL)
1382 state.in_command_position = 0;
1383 matches = try_complete_all_possible (&state, text, lc_start);
1386 g_free (state.word);
1388 if (matches != NULL &&
1389 (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
1390 (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
1392 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1393 char **m;
1395 for (m = matches; *m != NULL; m++)
1397 char *p;
1399 p = *m;
1400 *m = str_shell_escape (*m);
1401 g_free (p);
1405 return matches;
1408 /* --------------------------------------------------------------------------------------------- */
1410 void
1411 complete_engine_fill_completions (WInput * in)
1413 char *s;
1414 const char *word_separators;
1416 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1418 end = str_offset_to_pos (in->buffer->str, in->point);
1420 s = in->buffer->str;
1421 if (in->point != 0)
1423 /* get symbol before in->point */
1424 size_t i;
1426 for (i = in->point - 1; i > 0; i--)
1427 str_next_char (&s);
1430 for (; s >= in->buffer->str; str_prev_char (&s))
1432 start = s - in->buffer->str;
1433 if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1434 break;
1437 if (start < end)
1439 str_next_char (&s);
1440 start = s - in->buffer->str;
1443 in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1446 /* --------------------------------------------------------------------------------------------- */
1448 /* declared in lib/widget/input.h */
1449 void
1450 input_complete (WInput * in)
1452 int engine_flags;
1454 if (!str_is_valid_string (in->buffer->str))
1455 return;
1457 if (in->completions != NULL)
1458 engine_flags = DO_QUERY;
1459 else
1461 engine_flags = DO_INSERTION;
1463 if (mc_global.widget.show_all_if_ambiguous)
1464 engine_flags |= DO_QUERY;
1467 while (complete_engine (in, engine_flags))
1471 /* --------------------------------------------------------------------------------------------- */
1473 void
1474 input_complete_free (WInput * in)
1476 g_strfreev (in->completions);
1477 in->completions = NULL;
1480 /* --------------------------------------------------------------------------------------------- */