Merge branch '3578_mcext_fixes'
[midnight-commander.git] / lib / widget / input_complete.c
blobccd4b81f7ceba01dc4b7c07edb0f51ed8a8d3ad5
1 /*
2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2016
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 <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <dirent.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <pwd.h>
43 #include <unistd.h>
45 #include "lib/global.h"
47 #include "lib/tty/tty.h"
48 #include "lib/tty/key.h" /* XCTRL and ALT macros */
49 #include "lib/vfs/vfs.h"
50 #include "lib/strescape.h"
51 #include "lib/strutil.h"
52 #include "lib/util.h"
53 #include "lib/widget.h"
55 #include "input_complete.h"
57 /*** global variables ****************************************************************************/
59 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
60 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
61 extern char **environ;
62 #endif
64 /*** file scope macro definitions ****************************************************************/
66 /* #define DO_COMPLETION_DEBUG */
67 #ifdef DO_COMPLETION_DEBUG
68 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
69 #else
70 #define SHOW_C_CTX(func)
71 #endif /* DO_CMPLETION_DEBUG */
73 #define whitespace(c) ((c) == ' ' || (c) == '\t')
74 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
76 #define DO_INSERTION 1
77 #define DO_QUERY 2
79 /*** file scope type declarations ****************************************************************/
81 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
83 typedef struct
85 size_t in_command_position;
86 char *word;
87 char *p;
88 char *q;
89 char *r;
90 gboolean is_cd;
91 input_complete_t flags;
92 } try_complete_automation_state_t;
94 /*** file scope variables ************************************************************************/
96 static char **hosts = NULL;
97 static char **hosts_p = NULL;
98 static int hosts_alloclen = 0;
100 static int query_height, query_width;
101 static WInput *input;
102 static int min_end;
103 static int start = 0;
104 static int end = 0;
106 /*** file scope functions ************************************************************************/
107 /* --------------------------------------------------------------------------------------------- */
109 char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
110 void complete_engine_fill_completions (WInput * in);
112 #ifdef DO_COMPLETION_DEBUG
114 * Useful to print/debug completion flags
116 static const char *
117 show_c_flags (input_complete_t flags)
119 static char s_cf[] = "FHCVUDS";
121 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) ? 'F' : ' ';
122 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) ? 'H' : ' ';
123 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) ? 'C' : ' ';
124 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) ? 'V' : ' ';
125 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) ? 'U' : ' ';
126 s_cf[5] = (flags & INPUT_COMPLETE_CD) ? 'D' : ' ';
127 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) ? 'S' : ' ';
129 return s_cf;
131 #endif /* DO_CMPLETION_DEBUG */
133 /* --------------------------------------------------------------------------------------------- */
135 static char *
136 filename_completion_function (const char *text, int state, input_complete_t flags)
138 static DIR *directory = NULL;
139 static char *filename = NULL;
140 static char *dirname = NULL;
141 static char *users_dirname = NULL;
142 static size_t filename_len;
143 int isdir = 1, isexec = 0;
144 static vfs_path_t *dirname_vpath = NULL;
146 struct dirent *entry = NULL;
148 SHOW_C_CTX ("filename_completion_function");
150 if (text && (flags & INPUT_COMPLETE_SHELL_ESC))
152 char *u_text;
153 char *result;
154 char *e_result;
156 u_text = strutils_shell_unescape (text);
158 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
159 g_free (u_text);
161 e_result = strutils_shell_escape (result);
162 g_free (result);
164 return e_result;
167 /* If we're starting the match process, initialize us a bit. */
168 if (state == 0)
170 const char *temp;
172 g_free (dirname);
173 g_free (filename);
174 g_free (users_dirname);
175 vfs_path_free (dirname_vpath);
177 if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
179 filename = g_strdup (++temp);
180 dirname = g_strndup (text, temp - text);
182 else
184 dirname = g_strdup (".");
185 filename = g_strdup (text);
188 /* We aren't done yet. We also support the "~user" syntax. */
190 /* Save the version of the directory that the user typed. */
191 users_dirname = dirname;
192 dirname = tilde_expand (dirname);
193 canonicalize_pathname (dirname);
194 dirname_vpath = vfs_path_from_str (dirname);
196 /* Here we should do something with variable expansion
197 and `command`.
198 Maybe a dream - UNIMPLEMENTED yet. */
200 directory = mc_opendir (dirname_vpath);
201 filename_len = strlen (filename);
204 /* Now that we have some state, we can read the directory. */
206 while (directory && (entry = mc_readdir (directory)))
208 if (!str_is_valid_string (entry->d_name))
209 continue;
211 /* Special case for no filename.
212 All entries except "." and ".." match. */
213 if (filename_len == 0)
215 if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
216 continue;
218 else
220 /* Otherwise, if these match up to the length of filename, then
221 it may be a match. */
222 if ((entry->d_name[0] != filename[0]) ||
223 ((NLENGTH (entry)) < filename_len) ||
224 strncmp (filename, entry->d_name, filename_len))
225 continue;
227 isdir = 1;
228 isexec = 0;
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 = getuid ();
239 gid_t my_gid = getgid ();
241 if (!S_ISDIR (tempstat.st_mode))
243 isdir = 0;
244 if ((!my_uid && (tempstat.st_mode & 0111)) ||
245 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100)) ||
246 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010)) ||
247 (tempstat.st_mode & 0001))
248 isexec = 1;
251 else
253 /* stat failed, strange. not a dir in any case */
254 isdir = 0;
256 vfs_path_free (tmp_vpath);
258 if ((flags & INPUT_COMPLETE_COMMANDS) && (isexec || isdir))
259 break;
260 if ((flags & INPUT_COMPLETE_CD) && isdir)
261 break;
262 if (flags & (INPUT_COMPLETE_FILENAMES))
263 break;
266 if (entry == NULL)
268 if (directory)
270 mc_closedir (directory);
271 directory = NULL;
273 MC_PTR_FREE (dirname);
274 vfs_path_free (dirname_vpath);
275 dirname_vpath = NULL;
276 MC_PTR_FREE (filename);
277 MC_PTR_FREE (users_dirname);
278 return NULL;
282 GString *temp;
284 temp = g_string_sized_new (16);
286 if (users_dirname != NULL && (users_dirname[0] != '.' || users_dirname[1] != '\0'))
288 g_string_append (temp, users_dirname);
290 /* We need a '/' at the end. */
291 if (!IS_PATH_SEP (temp->str[temp->len - 1]))
292 g_string_append_c (temp, PATH_SEP);
294 g_string_append (temp, entry->d_name);
295 if (isdir)
296 g_string_append_c (temp, PATH_SEP);
298 return g_string_free (temp, FALSE);
302 /* --------------------------------------------------------------------------------------------- */
303 /** We assume here that text[0] == '~' , if you want to call it in another way,
304 you have to change the code */
306 static char *
307 username_completion_function (const char *text, int state, input_complete_t flags)
309 static struct passwd *entry;
310 static size_t userlen;
312 (void) flags;
313 SHOW_C_CTX ("username_completion_function");
315 if (text[0] == '\\' && text[1] == '~')
316 text++;
317 if (state == 0)
318 { /* Initialization stuff */
319 setpwent ();
320 userlen = strlen (text + 1);
322 while ((entry = getpwent ()) != NULL)
324 /* Null usernames should result in all users as possible completions. */
325 if (userlen == 0)
326 break;
327 if (text[1] == entry->pw_name[0] && !strncmp (text + 1, entry->pw_name, userlen))
328 break;
331 if (entry != NULL)
332 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
334 endpwent ();
335 return NULL;
338 /* --------------------------------------------------------------------------------------------- */
339 /** We assume text [0] == '$' and want to have a look at text [1], if it is
340 equal to '{', so that we should append '}' at the end */
342 static char *
343 variable_completion_function (const char *text, int state, input_complete_t flags)
345 static char **env_p;
346 static unsigned int isbrace;
347 static size_t varlen;
348 const char *p = NULL;
350 (void) flags;
351 SHOW_C_CTX ("variable_completion_function");
353 if (state == 0)
354 { /* Initialization stuff */
355 isbrace = (text[1] == '{') ? 1 : 0;
356 varlen = strlen (text + 1 + isbrace);
357 env_p = environ;
360 while (*env_p)
362 p = strchr (*env_p, '=');
363 if (p && ((size_t) (p - *env_p) >= varlen) && !strncmp (text + 1 + isbrace, *env_p, varlen))
364 break;
365 env_p++;
368 if (*env_p == NULL)
369 return NULL;
372 GString *temp;
374 temp = g_string_new_len (*env_p, p - *env_p);
376 if (isbrace != 0)
378 g_string_prepend_c (temp, '{');
379 g_string_append_c (temp, '}');
381 g_string_prepend_c (temp, '$');
383 env_p++;
385 return g_string_free (temp, FALSE);
389 /* --------------------------------------------------------------------------------------------- */
391 static void
392 fetch_hosts (const char *filename)
394 FILE *file = fopen (filename, "r");
395 char buffer[256], *name;
396 char *lc_start;
397 char *bi;
399 if (!file)
400 return;
402 while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
404 /* Skip to first character. */
405 for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi));
407 /* Ignore comments... */
408 if (bi[0] == '#')
409 continue;
410 /* Handle $include. */
411 if (!strncmp (bi, "$include ", 9))
413 char *includefile = bi + 9;
414 char *t;
416 /* Find start of filename. */
417 while (*includefile && whitespace (*includefile))
418 includefile++;
419 t = includefile;
421 /* Find end of filename. */
422 while (t[0] != '\0' && !str_isspace (t))
423 str_next_char (&t);
424 *t = '\0';
426 fetch_hosts (includefile);
427 continue;
430 /* Skip IP #s. */
431 while (bi[0] != '\0' && !str_isspace (bi))
432 str_next_char (&bi);
434 /* Get the host names separated by white space. */
435 while (bi[0] != '\0' && bi[0] != '#')
437 while (bi[0] != '\0' && str_isspace (bi))
438 str_next_char (&bi);
439 if (bi[0] == '#')
440 continue;
441 for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi));
443 if (bi - lc_start == 0)
444 continue;
446 name = g_strndup (lc_start, bi - lc_start);
448 char **host_p;
450 if (hosts_p - hosts >= hosts_alloclen)
452 int j;
454 j = hosts_p - hosts;
455 hosts_alloclen += 30;
456 hosts = g_renew (char *, hosts, hosts_alloclen + 1);
457 hosts_p = hosts + j;
459 for (host_p = hosts; host_p < hosts_p; host_p++)
460 if (!strcmp (name, *host_p))
461 break; /* We do not want any duplicates */
462 if (host_p == hosts_p)
464 *(hosts_p++) = name;
465 *hosts_p = NULL;
467 else
468 g_free (name);
472 fclose (file);
475 /* --------------------------------------------------------------------------------------------- */
477 static char *
478 hostname_completion_function (const char *text, int state, input_complete_t flags)
480 static char **host_p;
481 static unsigned int textstart;
482 static size_t textlen;
484 (void) flags;
485 SHOW_C_CTX ("hostname_completion_function");
487 if (state == 0)
488 { /* Initialization stuff */
489 const char *p;
491 g_strfreev (hosts);
492 hosts_alloclen = 30;
493 hosts = g_new (char *, hosts_alloclen + 1);
494 *hosts = NULL;
495 hosts_p = hosts;
496 p = getenv ("HOSTFILE");
497 fetch_hosts (p != NULL ? p : "/etc/hosts");
498 host_p = hosts;
499 textstart = (*text == '@') ? 1 : 0;
500 textlen = strlen (text + textstart);
503 for (; *host_p != NULL; host_p++)
505 if (textlen == 0)
506 break; /* Match all of them */
507 if (strncmp (text + textstart, *host_p, textlen) == 0)
508 break;
511 if (*host_p == NULL)
513 g_strfreev (hosts);
514 hosts = NULL;
515 return NULL;
519 GString *temp;
521 temp = g_string_sized_new (8);
523 if (textstart != 0)
524 g_string_append_c (temp, '@');
525 g_string_append (temp, *host_p);
526 host_p++;
528 return g_string_free (temp, FALSE);
532 /* --------------------------------------------------------------------------------------------- */
534 * This is the function to call when the word to complete is in a position
535 * where a command word can be found. It looks around $PATH, looking for
536 * commands that match. It also scans aliases, function names, and the
537 * table of shell built-ins.
540 static char *
541 command_completion_function (const char *_text, int state, input_complete_t flags)
543 char *text;
544 static const char *path_end;
545 static gboolean isabsolute;
546 static int phase;
547 static size_t text_len;
548 static const char *const *words;
549 static char *path;
550 static char *cur_path;
551 static char *cur_word;
552 static int init_state;
553 static const char *const bash_reserved[] = {
554 "if", "then", "else", "elif", "fi", "case", "esac", "for",
555 "select", "while", "until", "do", "done", "in", "function", 0
557 static const char *const bash_builtins[] = {
558 "alias", "bg", "bind", "break", "builtin", "cd", "command",
559 "continue", "declare", "dirs", "echo", "enable", "eval",
560 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
561 "help", "history", "jobs", "kill", "let", "local", "logout",
562 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
563 "shift", "source", "suspend", "test", "times", "trap", "type",
564 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
566 char *p, *found;
568 /* cppcheck-suppress uninitvar */
569 SHOW_C_CTX ("command_completion_function");
571 if (!(flags & INPUT_COMPLETE_COMMANDS))
572 return 0;
574 text = strutils_shell_unescape (_text);
575 flags &= ~INPUT_COMPLETE_SHELL_ESC;
577 if (state == 0)
578 { /* Initialize us a little bit */
579 isabsolute = strchr (text, PATH_SEP) != NULL;
580 if (!isabsolute)
582 words = bash_reserved;
583 phase = 0;
584 text_len = strlen (text);
586 if (path == NULL)
588 path = g_strdup (getenv ("PATH"));
589 if (path != NULL)
591 p = path;
592 path_end = strchr (p, '\0');
593 while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
595 *p++ = '\0';
602 if (isabsolute)
604 p = filename_completion_function (text, state, flags);
606 if (p != NULL)
608 char *temp_p = p;
610 p = strutils_shell_escape (p);
611 g_free (temp_p);
614 g_free (text);
615 return p;
618 found = NULL;
619 switch (phase)
621 case 0: /* Reserved words */
622 for (; *words != NULL; words++)
623 if (strncmp (*words, text, text_len) == 0)
625 g_free (text);
626 return g_strdup (*(words++));
628 phase++;
629 words = bash_builtins;
630 case 1: /* Builtin commands */
631 for (; *words != NULL; words++)
632 if (strncmp (*words, text, text_len) == 0)
634 g_free (text);
635 return g_strdup (*(words++));
637 phase++;
638 if (!path)
639 break;
640 cur_path = path;
641 cur_word = NULL;
642 case 2: /* And looking through the $PATH */
643 while (!found)
645 if (!cur_word)
647 char *expanded;
649 if (cur_path >= path_end)
650 break;
651 expanded = tilde_expand (*cur_path ? cur_path : ".");
652 cur_word = mc_build_filename (expanded, text, NULL);
653 g_free (expanded);
654 canonicalize_pathname (cur_word);
655 cur_path = strchr (cur_path, 0) + 1;
656 init_state = state;
658 found = filename_completion_function (cur_word, state - init_state, flags);
659 if (!found)
660 MC_PTR_FREE (cur_word);
662 default:
663 break;
666 if (found == NULL)
667 MC_PTR_FREE (path);
668 else
670 p = strrchr (found, PATH_SEP);
671 if (p != NULL)
673 char *tmp = found;
674 found = strutils_shell_escape (p + 1);
675 g_free (tmp);
679 g_free (text);
680 return found;
683 /* --------------------------------------------------------------------------------------------- */
685 static int
686 match_compare (const void *a, const void *b)
688 return strcmp (*(char **) a, *(char **) b);
691 /* --------------------------------------------------------------------------------------------- */
692 /** Returns an array of char * matches with the longest common denominator
693 in the 1st entry. Then a NULL terminated list of different possible
694 completions follows.
695 You have to supply your own CompletionFunction with the word you
696 want to complete as the first argument and an count of previous matches
697 as the second.
698 In case no matches were found we return NULL. */
700 static char **
701 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
703 /* Number of slots in match_list. */
704 size_t match_list_size = 30;
705 /* The list of matches. */
706 char **match_list;
707 /* Number of matches actually found. */
708 size_t matches = 0;
710 /* Temporary string binder. */
711 char *string;
713 match_list = g_new (char *, match_list_size + 1);
714 match_list[1] = NULL;
716 while ((string = (*entry_function) (text, matches, flags)) != NULL)
718 if (matches + 1 == match_list_size)
720 match_list_size += 30;
721 match_list = (char **) g_renew (char *, match_list, match_list_size + 1);
723 match_list[++matches] = string;
724 match_list[matches + 1] = NULL;
727 /* If there were any matches, then look through them finding out the
728 lowest common denominator. That then becomes match_list[0]. */
729 if (matches)
732 /* If only one match, just use that. */
733 if (matches == 1)
735 match_list[0] = match_list[1];
736 match_list[1] = NULL;
738 else
740 size_t i = 1;
741 int low = 4096; /* Count of max-matched characters. */
742 size_t j;
744 qsort (match_list + 1, matches, sizeof (char *), match_compare);
746 /* And compare each member of the list with
747 the next, finding out where they stop matching.
748 If we find two equal strings, we have to put one away... */
750 j = i + 1;
751 while (j < matches + 1)
753 char *si, *sj;
754 char *ni, *nj;
756 for (si = match_list[i], sj = match_list[j]; si[0] && sj[0];)
759 ni = str_get_next_char (si);
760 nj = str_get_next_char (sj);
762 if (ni - si != nj - sj)
763 break;
764 if (strncmp (si, sj, ni - si) != 0)
765 break;
767 si = ni;
768 sj = nj;
771 if (si[0] == '\0' && sj[0] == '\0')
772 { /* Two equal strings */
773 g_free (match_list[j]);
774 j++;
775 if (j > matches)
776 break;
777 continue; /* Look for a run of equal strings */
779 else if (low > si - match_list[i])
780 low = si - match_list[i];
781 if (i + 1 != j) /* So there's some gap */
782 match_list[i + 1] = match_list[j];
783 i++;
784 j++;
786 matches = i;
787 match_list[matches + 1] = NULL;
788 match_list[0] = g_strndup (match_list[1], low);
791 else /* There were no matches. */
792 MC_PTR_FREE (match_list);
794 return match_list;
797 /* --------------------------------------------------------------------------------------------- */
798 /** Check if directory completion is needed */
799 static gboolean
800 check_is_cd (const char *text, int lc_start, input_complete_t flags)
802 char *p, *q;
804 SHOW_C_CTX ("check_is_cd");
806 if ((flags & INPUT_COMPLETE_CD) == 0)
807 return FALSE;
809 /* Skip initial spaces */
810 p = (char *) text;
811 q = (char *) text + lc_start;
812 while (p < q && p[0] != '\0' && str_isspace (p))
813 str_next_char (&p);
815 /* Check if the command is "cd" and the cursor is after it */
816 return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
819 /* --------------------------------------------------------------------------------------------- */
821 static void
822 try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
824 const char *command_separator_chars = ";|&{(`";
825 char *ti;
827 if (*lc_start == 0)
828 ti = text;
829 else
831 ti = str_get_prev_char (&text[*lc_start]);
832 while (ti > text && (ti[0] == ' ' || ti[0] == '\t'))
833 str_prev_char (&ti);
836 if (ti == text)
837 state->in_command_position++;
838 else if (strchr (command_separator_chars, ti[0]) != NULL)
840 state->in_command_position++;
841 if (ti != text)
843 int this_char, prev_char;
845 /* Handle the two character tokens '>&', '<&', and '>|'.
846 We are not in a command position after one of these. */
847 this_char = ti[0];
848 prev_char = str_get_prev_char (ti)[0];
850 /* Quoted */
851 if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
852 || (this_char == '|' && prev_char == '>') || (ti != text
853 && str_get_prev_char (ti)[0] == '\\'))
854 state->in_command_position = 0;
859 /* --------------------------------------------------------------------------------------------- */
861 static void
862 try_complete_find_start_sign (try_complete_automation_state_t * state)
864 if (state->flags & INPUT_COMPLETE_COMMANDS)
865 state->p = strrchr (state->word, '`');
866 if (state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
868 state->q = strrchr (state->word, '$');
870 /* don't substitute variable in \$ case */
871 if (strutils_is_char_escaped (state->word, state->q))
873 size_t qlen;
875 qlen = strlen (state->q);
876 /* drop '\\' */
877 memmove (state->q - 1, state->q, qlen + 1);
878 /* adjust flags */
879 state->flags &= ~INPUT_COMPLETE_VARIABLES;
880 state->q = NULL;
883 if (state->flags & INPUT_COMPLETE_HOSTNAMES)
884 state->r = strrchr (state->word, '@');
885 if (state->q && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS))
887 if (state->q > state->p)
888 state->p = str_get_next_char (state->q);
889 state->q = NULL;
893 /* --------------------------------------------------------------------------------------------- */
895 static char **
896 try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
898 char **matches = NULL;
900 if (state->in_command_position != 0)
902 SHOW_C_CTX ("try_complete:cmd_subst");
903 matches =
904 completion_matches (state->word, command_completion_function,
905 state->flags & (~INPUT_COMPLETE_FILENAMES));
907 else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
909 if (state->is_cd)
910 state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
911 SHOW_C_CTX ("try_complete:filename_subst_1");
912 matches = completion_matches (state->word, filename_completion_function, state->flags);
914 if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
916 state->q = text + *lc_start;
917 for (state->p = text;
918 *state->p && state->p < state->q && (*state->p == ' ' || *state->p == '\t');
919 str_next_char (&state->p))
921 if (!strncmp (state->p, "cd", 2))
922 for (state->p += 2;
923 *state->p && state->p < state->q && (*state->p == ' ' || *state->p == '\t');
924 str_next_char (&state->p))
926 if (state->p == state->q)
928 char *const cdpath_ref = g_strdup (getenv ("CDPATH"));
929 char *cdpath = cdpath_ref;
930 char c;
932 c = (cdpath == NULL) ? '\0' : ':';
934 while (!matches && c == ':')
936 char *s;
938 s = strchr (cdpath, ':');
939 /* cppcheck-suppress nullPointer */
940 if (s == NULL)
941 s = strchr (cdpath, '\0');
942 c = *s;
943 *s = '\0';
944 if (*cdpath != '\0')
946 state->r = mc_build_filename (cdpath, state->word, NULL);
947 SHOW_C_CTX ("try_complete:filename_subst_2");
948 matches =
949 completion_matches (state->r, filename_completion_function,
950 state->flags);
951 g_free (state->r);
953 *s = c;
954 cdpath = str_get_next_char (s);
956 g_free (cdpath_ref);
960 return matches;
963 /* --------------------------------------------------------------------------------------------- */
965 static gboolean
966 insert_text (WInput * in, char *text, ssize_t size)
968 int buff_len;
970 buff_len = str_length (in->buffer);
971 size = min (size, (ssize_t) strlen (text)) + start - end;
972 if (strlen (in->buffer) + size >= (size_t) in->current_max_size)
974 /* Expand the buffer */
975 char *narea;
976 Widget *w = WIDGET (in);
978 narea = g_try_realloc (in->buffer, in->current_max_size + size + w->cols);
979 if (narea != NULL)
981 in->buffer = narea;
982 in->current_max_size += size + w->cols;
985 if (strlen (in->buffer) + 1 < (size_t) in->current_max_size)
987 if (size != 0)
988 memmove (in->buffer + end + size, in->buffer + end, strlen (&in->buffer[end]) + 1);
989 memmove (in->buffer + start, text, size - (start - end));
990 in->point += str_length (in->buffer) - buff_len;
991 input_update (in, TRUE);
992 end += size;
994 return size != 0;
997 /* --------------------------------------------------------------------------------------------- */
999 static cb_ret_t
1000 query_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1002 static int bl = 0;
1004 WDialog *h = DIALOG (w);
1006 switch (msg)
1008 case MSG_KEY:
1009 switch (parm)
1011 case KEY_LEFT:
1012 case KEY_RIGHT:
1013 bl = 0;
1014 h->ret_value = 0;
1015 dlg_stop (h);
1016 return MSG_HANDLED;
1018 case KEY_BACKSPACE:
1019 bl = 0;
1020 /* exit from completion list if input line is empty */
1021 if (end == 0)
1023 h->ret_value = 0;
1024 dlg_stop (h);
1026 /* Refill the list box and start again */
1027 else if (end == min_end)
1029 end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1030 input_handle_char (input, parm);
1031 h->ret_value = B_USER;
1032 dlg_stop (h);
1034 else
1036 int new_end;
1037 int i;
1038 GList *e;
1040 new_end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1042 for (i = 0, e = listbox_get_first_link (LISTBOX (h->current->data));
1043 e != NULL; i++, e = g_list_next (e))
1045 WLEntry *le = LENTRY (e->data);
1047 if (strncmp (input->buffer + start, le->text, new_end - start) == 0)
1049 listbox_select_entry (LISTBOX (h->current->data), i);
1050 end = new_end;
1051 input_handle_char (input, parm);
1052 widget_redraw (WIDGET (h->current->data));
1053 break;
1057 return MSG_HANDLED;
1059 default:
1060 if (parm < 32 || parm > 255)
1062 bl = 0;
1063 if (input_key_is_in_map (input, parm) != 2)
1064 return MSG_NOT_HANDLED;
1066 if (end == min_end)
1067 return MSG_HANDLED;
1069 /* This means we want to refill the list box and start again */
1070 h->ret_value = B_USER;
1071 dlg_stop (h);
1073 else
1075 static char buff[MB_LEN_MAX] = "";
1076 GList *e;
1077 int i;
1078 int need_redraw = 0;
1079 int low = 4096;
1080 char *last_text = NULL;
1082 buff[bl++] = (char) parm;
1083 buff[bl] = '\0';
1084 switch (str_is_valid_char (buff, bl))
1086 case -1:
1087 bl = 0;
1088 /* fallthrough */
1089 case -2:
1090 return MSG_HANDLED;
1091 default:
1092 break;
1095 for (i = 0, e = listbox_get_first_link (LISTBOX (h->current->data));
1096 e != NULL; i++, e = g_list_next (e))
1098 WLEntry *le = LENTRY (e->data);
1100 if (strncmp (input->buffer + start, le->text, end - start) == 0
1101 && strncmp (&le->text[end - start], buff, bl) == 0)
1103 if (need_redraw == 0)
1105 need_redraw = 1;
1106 listbox_select_entry (LISTBOX (h->current->data), i);
1107 last_text = le->text;
1109 else
1111 char *si, *sl;
1112 int si_num = 0;
1113 int sl_num = 0;
1115 /* count symbols between start and end */
1116 for (si = le->text + start; si < le->text + end;
1117 str_next_char (&si), si_num++)
1119 for (sl = last_text + start; sl < last_text + end;
1120 str_next_char (&sl), sl_num++)
1123 /* pointers to next symbols */
1124 si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1125 sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1127 while (si[0] != '\0' && sl[0] != '\0')
1129 char *nexti, *nextl;
1131 nexti = str_get_next_char (si);
1132 nextl = str_get_next_char (sl);
1134 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1135 break;
1137 si = nexti;
1138 sl = nextl;
1140 si_num++;
1143 last_text = le->text;
1145 si = &last_text[str_offset_to_pos (last_text, si_num)];
1146 if (low > si - last_text)
1147 low = si - last_text;
1149 need_redraw = 2;
1154 if (need_redraw == 2)
1156 insert_text (input, last_text, low);
1157 widget_redraw (WIDGET (h->current->data));
1159 else if (need_redraw == 1)
1161 h->ret_value = B_ENTER;
1162 dlg_stop (h);
1164 bl = 0;
1167 return MSG_HANDLED;
1169 default:
1170 return dlg_default_callback (w, sender, msg, parm, data);
1174 /* --------------------------------------------------------------------------------------------- */
1176 /** Returns 1 if the user would like to see us again */
1177 static int
1178 complete_engine (WInput * in, int what_to_do)
1180 if (in->completions != NULL && str_offset_to_pos (in->buffer, in->point) != end)
1181 input_free_completions (in);
1183 if (in->completions == NULL)
1184 complete_engine_fill_completions (in);
1186 if (in->completions != NULL)
1188 if (what_to_do & DO_INSERTION || ((what_to_do & DO_QUERY) && !in->completions[1]))
1190 char *lc_complete = in->completions[0];
1191 if (insert_text (in, lc_complete, strlen (lc_complete)))
1193 if (in->completions[1])
1194 tty_beep ();
1195 else
1196 input_free_completions (in);
1198 else
1199 tty_beep ();
1201 if ((what_to_do & DO_QUERY) && in->completions && in->completions[1])
1203 int maxlen = 0, i, count = 0;
1204 int x, y, w, h;
1205 int start_x, start_y;
1206 char **p, *q;
1207 WDialog *query_dlg;
1208 WListbox *query_list;
1210 for (p = in->completions + 1; *p != NULL; count++, p++)
1212 i = str_term_width1 (*p);
1213 if (i > maxlen)
1214 maxlen = i;
1216 start_x = WIDGET (in)->x;
1217 start_y = WIDGET (in)->y;
1218 if (start_y - 2 >= count)
1220 y = start_y - 2 - count;
1221 h = 2 + count;
1223 else
1225 if (start_y >= LINES - start_y - 1)
1227 y = 0;
1228 h = start_y;
1230 else
1232 y = start_y + 1;
1233 h = LINES - start_y - 1;
1236 x = start - in->term_first_shown - 2 + start_x;
1237 w = maxlen + 4;
1238 if (x + w > COLS)
1239 x = COLS - w;
1240 if (x < 0)
1241 x = 0;
1242 if (x + w > COLS)
1243 w = COLS;
1244 input = in;
1245 min_end = end;
1246 query_height = h;
1247 query_width = w;
1248 query_dlg = dlg_create (TRUE, y, x, query_height, query_width,
1249 dialog_colors, query_callback, NULL,
1250 "[Completion]", NULL, DLG_COMPACT);
1251 query_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1252 add_widget (query_dlg, query_list);
1253 for (p = in->completions + 1; *p; p++)
1254 listbox_add_item (query_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
1255 dlg_run (query_dlg);
1256 q = NULL;
1257 if (query_dlg->ret_value == B_ENTER)
1259 listbox_get_current (query_list, &q, NULL);
1260 if (q)
1261 insert_text (in, q, strlen (q));
1263 if (q || end != min_end)
1264 input_free_completions (in);
1265 i = query_dlg->ret_value; /* B_USER if user wants to start over again */
1266 dlg_destroy (query_dlg);
1267 if (i == B_USER)
1268 return 1;
1271 else
1272 tty_beep ();
1273 return 0;
1276 /* --------------------------------------------------------------------------------------------- */
1277 /*** public functions ****************************************************************************/
1278 /* --------------------------------------------------------------------------------------------- */
1280 /** Returns an array of matches, or NULL if none. */
1281 char **
1282 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1284 try_complete_automation_state_t state;
1285 char **matches = NULL;
1287 memset (&state, 0, sizeof (state));
1288 state.flags = flags;
1290 SHOW_C_CTX ("try_complete");
1291 state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1293 state.is_cd = check_is_cd (text, *lc_start, state.flags);
1295 /* Determine if this could be a command word. It is if it appears at
1296 the start of the line (ignoring preceding whitespace), or if it
1297 appears after a character that separates commands. And we have to
1298 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1299 if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS))
1300 try_complete_commands_prepare (&state, text, lc_start);
1302 try_complete_find_start_sign (&state);
1304 /* Command substitution? */
1305 if (state.p > state.q && state.p > state.r)
1307 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1308 matches = completion_matches (str_cget_next_char (state.p),
1309 command_completion_function,
1310 state.flags & (~INPUT_COMPLETE_FILENAMES));
1311 if (matches)
1312 *lc_start += str_get_next_char (state.p) - state.word;
1315 /* Variable name? */
1316 else if (state.q > state.p && state.q > state.r)
1318 SHOW_C_CTX ("try_complete:var_subst");
1319 matches = completion_matches (state.q, variable_completion_function, state.flags);
1320 if (matches)
1321 *lc_start += state.q - state.word;
1324 /* Starts with '@', then look through the known hostnames for
1325 completion first. */
1326 else if (state.r > state.p && state.r > state.q)
1328 SHOW_C_CTX ("try_complete:host_subst");
1329 matches = completion_matches (state.r, hostname_completion_function, state.flags);
1330 if (matches)
1331 *lc_start += state.r - state.word;
1334 /* Starts with '~' and there is no slash in the word, then
1335 try completing this word as a username. */
1336 if (!matches && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES)
1337 && !strchr (state.word, PATH_SEP))
1339 SHOW_C_CTX ("try_complete:user_subst");
1340 matches = completion_matches (state.word, username_completion_function, state.flags);
1343 /* If this word is in a command position, then
1344 complete over possible command names, including aliases, functions,
1345 and command names. */
1346 if (matches == NULL)
1347 matches = try_complete_all_possible (&state, text, lc_start);
1349 /* And finally if nothing found, try complete directory name */
1350 if (matches == NULL)
1352 state.in_command_position = 0;
1353 matches = try_complete_all_possible (&state, text, lc_start);
1356 g_free (state.word);
1358 if (matches != NULL &&
1359 (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
1360 (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
1362 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1363 char **m;
1365 for (m = matches; *m != NULL; m++)
1367 char *p;
1369 p = *m;
1370 *m = strutils_shell_escape (*m);
1371 g_free (p);
1375 return matches;
1378 /* --------------------------------------------------------------------------------------------- */
1380 void
1381 complete_engine_fill_completions (WInput * in)
1383 char *s;
1384 const char *word_separators;
1386 word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1388 end = str_offset_to_pos (in->buffer, in->point);
1390 s = in->buffer;
1391 if (in->point != 0)
1393 /* get symbol before in->point */
1394 size_t i;
1396 for (i = in->point - 1; i > 0; i--)
1397 str_next_char (&s);
1400 for (; s >= in->buffer; str_prev_char (&s))
1402 start = s - in->buffer;
1403 if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer, s))
1404 break;
1407 if (start < end)
1409 str_next_char (&s);
1410 start = s - in->buffer;
1413 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
1416 /* --------------------------------------------------------------------------------------------- */
1418 /* declared in lib/widget/input.h */
1419 void
1420 complete (WInput * in)
1422 int engine_flags;
1424 if (!str_is_valid_string (in->buffer))
1425 return;
1427 if (in->completions != NULL)
1428 engine_flags = DO_QUERY;
1429 else
1431 engine_flags = DO_INSERTION;
1433 if (mc_global.widget.show_all_if_ambiguous)
1434 engine_flags |= DO_QUERY;
1437 while (complete_engine (in, engine_flags))
1441 /* --------------------------------------------------------------------------------------------- */