2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2020
6 Free Software Foundation, Inc.
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
36 #include <limits.h> /* MB_LEN_MAX */
41 #include <sys/types.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"
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
;
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))
71 #define SHOW_C_CTX(func)
72 #endif /* DO_CMPLETION_DEBUG */
74 #define DO_INSERTION 1
77 /*** file scope type declarations ****************************************************************/
79 typedef char *CompletionFunction (const char *text
, int state
, input_complete_t flags
);
83 size_t in_command_position
;
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
;
101 static int start
= 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
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' : ' ';
129 #endif /* DO_CMPLETION_DEBUG */
131 /* --------------------------------------------------------------------------------------------- */
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)
154 u_text
= strutils_shell_unescape (text
);
156 result
= filename_completion_function (u_text
, state
, flags
& (~INPUT_COMPLETE_SHELL_ESC
));
159 e_result
= strutils_shell_escape (result
);
165 /* If we're starting the match process, initialize us a bit. */
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
);
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
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
))
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
))
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)
230 struct stat tempstat
;
231 vfs_path_t
*tmp_vpath
;
233 tmp_vpath
= vfs_path_build_filename (dirname
, entry
->d_name
, (char *) NULL
);
236 if (mc_stat (tmp_vpath
, &tempstat
) == 0)
244 if (!S_ISDIR (tempstat
.st_mode
))
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)
257 /* stat failed, strange. not a dir in any case */
260 vfs_path_free (tmp_vpath
);
263 if ((flags
& INPUT_COMPLETE_COMMANDS
) != 0 && (isexec
|| isdir
))
265 if ((flags
& INPUT_COMPLETE_CD
) != 0 && isdir
)
267 if ((flags
& INPUT_COMPLETE_FILENAMES
) != 0)
273 if (directory
!= NULL
)
275 mc_closedir (directory
);
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
);
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
);
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 */
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;
318 SHOW_C_CTX ("username_completion_function");
320 if (text
[0] == '\\' && text
[1] == '~')
323 { /* Initialization stuff */
325 userlen
= strlen (text
+ 1);
328 while ((entry
= getpwent ()) != NULL
)
330 /* Null usernames should result in all users as possible completions. */
333 if (text
[1] == entry
->pw_name
[0] && strncmp (text
+ 1, entry
->pw_name
, userlen
) == 0)
338 return g_strconcat ("~", entry
->pw_name
, PATH_SEP_STR
, (char *) 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 */
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
;
357 SHOW_C_CTX ("variable_completion_function");
360 { /* Initialization stuff */
361 isbrace
= (text
[1] == '{');
362 varlen
= strlen (text
+ 1 + isbrace
);
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)
381 temp
= g_string_new_len (*env_p
, p
- *env_p
);
385 g_string_prepend_c (temp
, '{');
386 g_string_append_c (temp
, '}');
388 g_string_prepend_c (temp
, '$');
392 return g_string_free (temp
, FALSE
);
396 /* --------------------------------------------------------------------------------------------- */
399 fetch_hosts (const char *filename
)
407 file
= fopen (filename
, "r");
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... */
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
))
432 /* Find end of filename. */
433 while (t
[0] != '\0' && !str_isspace (t
))
437 fetch_hosts (includefile
);
442 while (bi
[0] != '\0' && !str_isspace (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
))
452 for (lc_start
= bi
; bi
[0] != '\0' && !str_isspace (bi
); str_next_char (&bi
))
458 name
= g_strndup (lc_start
, bi
- lc_start
);
466 if (j
>= hosts_alloclen
)
468 hosts_alloclen
+= 30;
469 hosts
= g_renew (char *, hosts
, hosts_alloclen
+ 1);
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
)
491 /* --------------------------------------------------------------------------------------------- */
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;
501 SHOW_C_CTX ("hostname_completion_function");
504 { /* Initialization stuff */
509 hosts
= g_new (char *, hosts_alloclen
+ 1);
512 p
= getenv ("HOSTFILE");
513 fetch_hosts (p
!= NULL
? p
: "/etc/hosts");
515 textstart
= (*text
== '@') ? 1 : 0;
516 textlen
= strlen (text
+ textstart
);
519 for (; *host_p
!= NULL
; host_p
++)
522 break; /* Match all of them */
523 if (strncmp (text
+ textstart
, *host_p
, textlen
) == 0)
537 temp
= g_string_sized_new (8);
540 g_string_append_c (temp
, '@');
541 g_string_append (temp
, *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.
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
585 SHOW_C_CTX ("command_completion_function");
587 if ((flags
& INPUT_COMPLETE_COMMANDS
) == 0)
590 u_text
= strutils_shell_unescape (text
);
591 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
594 { /* Initialize us a little bit */
595 isabsolute
= strchr (u_text
, PATH_SEP
) != NULL
;
598 words
= bash_reserved
;
600 text_len
= strlen (u_text
);
604 path
= g_strdup (getenv ("PATH"));
608 path_end
= strchr (p
, '\0');
609 while ((p
= strchr (p
, PATH_ENV_SEP
)) != NULL
)
618 p
= filename_completion_function (u_text
, state
, flags
);
624 p
= strutils_shell_escape (p
);
635 case 0: /* Reserved words */
636 for (; *words
!= NULL
; words
++)
637 if (strncmp (*words
, u_text
, text_len
) == 0)
640 return g_strdup (*(words
++));
643 words
= bash_builtins
;
645 case 1: /* Builtin commands */
646 for (; *words
!= NULL
; words
++)
647 if (strncmp (*words
, u_text
, text_len
) == 0)
650 return g_strdup (*(words
++));
658 case 2: /* And looking through the $PATH */
659 while (found
== NULL
)
661 if (cur_word
== NULL
)
665 if (cur_path
>= path_end
)
667 expanded
= tilde_expand (*cur_path
!= '\0' ? cur_path
: ".");
668 cur_word
= mc_build_filename (expanded
, u_text
, (char *) NULL
);
670 canonicalize_pathname (cur_word
);
671 cur_path
= strchr (cur_path
, '\0') + 1;
674 found
= filename_completion_function (cur_word
, state
- init_state
, flags
);
676 MC_PTR_FREE (cur_word
);
687 p
= strrchr (found
, PATH_SEP
);
692 found
= strutils_shell_escape (p
+ 1);
701 /* --------------------------------------------------------------------------------------------- */
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
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
716 In case no matches were found we return NULL. */
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. */
725 /* Number of matches actually found. */
728 /* Temporary string binder. */
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]. */
748 MC_PTR_FREE (match_list
); /* There were no matches. */
751 /* If only one match, just use that. */
754 match_list
[0] = match_list
[1];
755 match_list
[1] = NULL
;
760 int low
= 4096; /* Count of max-matched characters. */
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... */
770 while (j
< matches
+ 1)
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
)
783 if (strncmp (si
, sj
, ni
- si
) != 0)
790 if (si
[0] == '\0' && sj
[0] == '\0')
791 { /* Two equal strings */
792 g_free (match_list
[j
]);
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
];
806 match_list
[matches
+ 1] = NULL
;
807 match_list
[0] = g_strndup (match_list
[1], low
);
814 /* --------------------------------------------------------------------------------------------- */
815 /** Check if directory completion is needed */
817 check_is_cd (const char *text
, int lc_start
, input_complete_t flags
)
821 SHOW_C_CTX ("check_is_cd");
823 if ((flags
& INPUT_COMPLETE_CD
) == 0)
826 /* Skip initial spaces */
829 while (p
< q
&& p
[0] != '\0' && str_isspace (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 /* --------------------------------------------------------------------------------------------- */
839 try_complete_commands_prepare (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
841 const char *command_separator_chars
= ";|&{(`";
848 ti
= str_get_prev_char (&text
[*lc_start
]);
849 while (ti
> text
&& whitespace (ti
[0]))
854 state
->in_command_position
++;
855 else if (strchr (command_separator_chars
, ti
[0]) != NULL
)
857 state
->in_command_position
++;
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. */
865 prev_char
= str_get_prev_char (ti
)[0];
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 /* --------------------------------------------------------------------------------------------- */
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
))
891 str_move (state
->q
- 1, state
->q
);
893 state
->flags
&= ~INPUT_COMPLETE_VARIABLES
;
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
);
907 /* --------------------------------------------------------------------------------------------- */
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");
918 completion_matches (state
->word
, command_completion_function
,
919 state
->flags
& (~INPUT_COMPLETE_FILENAMES
));
921 else if ((state
->flags
& INPUT_COMPLETE_FILENAMES
) != 0)
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)
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
;
945 cdpath_ref
= g_strdup (getenv ("CDPATH"));
947 c
= (cdpath
== NULL
) ? '\0' : ':';
949 while (matches
== NULL
&& c
== ':')
953 s
= strchr (cdpath
, ':');
954 /* cppcheck-suppress nullPointer */
956 s
= strchr (cdpath
, '\0');
961 state
->r
= mc_build_filename (cdpath
, state
->word
, (char *) NULL
);
962 SHOW_C_CTX ("try_complete:filename_subst_2");
964 completion_matches (state
->r
, filename_completion_function
,
969 cdpath
= str_get_next_char (s
);
978 /* --------------------------------------------------------------------------------------------- */
981 insert_text (WInput
* in
, char *text
, ssize_t size
)
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 */
993 Widget
*w
= WIDGET (in
);
995 narea
= g_try_realloc (in
->buffer
, in
->current_max_size
+ size
+ w
->cols
);
999 in
->current_max_size
+= size
+ w
->cols
;
1002 if (strlen (in
->buffer
) + 1 < (size_t) in
->current_max_size
)
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
);
1015 /* --------------------------------------------------------------------------------------------- */
1018 query_callback (Widget
* w
, Widget
* sender
, widget_msg_t msg
, int parm
, void *data
)
1022 WGroup
*g
= GROUP (w
);
1023 WDialog
*h
= DIALOG (w
);
1039 /* exit from completion list if input line is empty */
1045 /* Refill the list box and start again */
1046 else if (end
== min_end
)
1048 end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1049 input_handle_char (input
, parm
);
1050 h
->ret_value
= B_USER
;
1059 new_end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1061 for (i
= 0, e
= listbox_get_first_link (LISTBOX (g
->current
->data
));
1062 e
!= NULL
; i
++, e
= g_list_next (e
))
1064 WLEntry
*le
= LENTRY (e
->data
);
1066 if (strncmp (input
->buffer
+ start
, le
->text
, new_end
- start
) == 0)
1068 listbox_select_entry (LISTBOX (g
->current
->data
), i
);
1070 input_handle_char (input
, parm
);
1071 widget_draw (WIDGET (g
->current
->data
));
1079 if (parm
< 32 || parm
> 255)
1082 if (input_key_is_in_map (input
, parm
) != 2)
1083 return MSG_NOT_HANDLED
;
1088 /* This means we want to refill the list box and start again */
1089 h
->ret_value
= B_USER
;
1094 static char buff
[MB_LEN_MAX
] = "";
1097 int need_redraw
= 0;
1099 char *last_text
= NULL
;
1101 buff
[bl
++] = (char) parm
;
1104 switch (str_is_valid_char (buff
, bl
))
1115 for (i
= 0, e
= listbox_get_first_link (LISTBOX (g
->current
->data
));
1116 e
!= NULL
; i
++, e
= g_list_next (e
))
1118 WLEntry
*le
= LENTRY (e
->data
);
1120 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
) == 0
1121 && strncmp (&le
->text
[end
- start
], buff
, bl
) == 0)
1123 if (need_redraw
== 0)
1126 listbox_select_entry (LISTBOX (g
->current
->data
), i
);
1127 last_text
= le
->text
;
1135 /* count symbols between start and end */
1136 for (si
= le
->text
+ start
; si
< le
->text
+ end
;
1137 str_next_char (&si
), si_num
++)
1139 for (sl
= last_text
+ start
; sl
< last_text
+ end
;
1140 str_next_char (&sl
), sl_num
++)
1143 /* pointers to next symbols */
1144 si
= &le
->text
[str_offset_to_pos (le
->text
, ++si_num
)];
1145 sl
= &last_text
[str_offset_to_pos (last_text
, ++sl_num
)];
1147 while (si
[0] != '\0' && sl
[0] != '\0')
1149 char *nexti
, *nextl
;
1151 nexti
= str_get_next_char (si
);
1152 nextl
= str_get_next_char (sl
);
1154 if (nexti
- si
!= nextl
- sl
|| strncmp (si
, sl
, nexti
- si
) != 0)
1163 last_text
= le
->text
;
1165 si
= &last_text
[str_offset_to_pos (last_text
, si_num
)];
1166 if (low
> si
- last_text
)
1167 low
= si
- last_text
;
1174 if (need_redraw
== 2)
1176 insert_text (input
, last_text
, low
);
1177 widget_draw (WIDGET (g
->current
->data
));
1179 else if (need_redraw
== 1)
1181 h
->ret_value
= B_ENTER
;
1190 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
1194 /* --------------------------------------------------------------------------------------------- */
1196 /** Returns TRUE if the user would like to see us again */
1198 complete_engine (WInput
* in
, int what_to_do
)
1200 if (in
->completions
!= NULL
&& str_offset_to_pos (in
->buffer
, in
->point
) != end
)
1201 input_free_completions (in
);
1203 if (in
->completions
== NULL
)
1204 complete_engine_fill_completions (in
);
1206 if (in
->completions
== NULL
)
1210 if ((what_to_do
& DO_INSERTION
) != 0
1211 || ((what_to_do
& DO_QUERY
) != 0 && in
->completions
[1] == NULL
))
1213 char *lc_complete
= in
->completions
[0];
1215 if (!insert_text (in
, lc_complete
, strlen (lc_complete
)) || in
->completions
[1] != NULL
)
1218 input_free_completions (in
);
1221 if ((what_to_do
& DO_QUERY
) != 0 && in
->completions
!= NULL
&& in
->completions
[1] != NULL
)
1223 int maxlen
= 0, count
= 0, i
;
1225 int start_x
, start_y
;
1228 WListbox
*query_list
;
1230 for (p
= in
->completions
+ 1; *p
!= NULL
; count
++, p
++)
1232 i
= str_term_width1 (*p
);
1237 start_x
= WIDGET (in
)->x
;
1238 start_y
= WIDGET (in
)->y
;
1239 if (start_y
- 2 >= count
)
1241 y
= start_y
- 2 - count
;
1244 else if (start_y
>= LINES
- start_y
- 1)
1252 h
= LINES
- start_y
- 1;
1254 x
= start
- in
->term_first_shown
- 2 + start_x
;
1268 query_dlg
= dlg_create (TRUE
, y
, x
, query_height
, query_width
, WPOS_KEEP_DEFAULT
, TRUE
,
1269 dialog_colors
, query_callback
, NULL
, "[Completion]", NULL
);
1270 query_list
= listbox_new (1, 1, h
- 2, w
- 2, FALSE
, NULL
);
1271 group_add_widget (GROUP (query_dlg
), query_list
);
1273 for (p
= in
->completions
+ 1; *p
!= NULL
; p
++)
1274 listbox_add_item (query_list
, LISTBOX_APPEND_AT_END
, 0, *p
, NULL
, FALSE
);
1276 i
= dlg_run (query_dlg
);
1280 listbox_get_current (query_list
, &q
, NULL
);
1282 insert_text (in
, q
, strlen (q
));
1284 if (q
!= NULL
|| end
!= min_end
)
1285 input_free_completions (in
);
1286 dlg_destroy (query_dlg
);
1288 /* B_USER if user wants to start over again */
1289 return (i
== B_USER
);
1296 /* --------------------------------------------------------------------------------------------- */
1297 /*** public functions ****************************************************************************/
1298 /* --------------------------------------------------------------------------------------------- */
1300 /** Returns an array of matches, or NULL if none. */
1302 try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
)
1304 try_complete_automation_state_t state
;
1305 char **matches
= NULL
;
1307 memset (&state
, 0, sizeof (state
));
1308 state
.flags
= flags
;
1310 SHOW_C_CTX ("try_complete");
1311 state
.word
= g_strndup (text
+ *lc_start
, *lc_end
- *lc_start
);
1313 state
.is_cd
= check_is_cd (text
, *lc_start
, state
.flags
);
1315 /* Determine if this could be a command word. It is if it appears at
1316 the start of the line (ignoring preceding whitespace), or if it
1317 appears after a character that separates commands. And we have to
1318 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1319 if (!state
.is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
) != 0)
1320 try_complete_commands_prepare (&state
, text
, lc_start
);
1322 try_complete_find_start_sign (&state
);
1324 /* Command substitution? */
1325 if (state
.p
> state
.q
&& state
.p
> state
.r
)
1327 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1328 matches
= completion_matches (str_cget_next_char (state
.p
),
1329 command_completion_function
,
1330 state
.flags
& (~INPUT_COMPLETE_FILENAMES
));
1331 if (matches
!= NULL
)
1332 *lc_start
+= str_get_next_char (state
.p
) - state
.word
;
1335 /* Variable name? */
1336 else if (state
.q
> state
.p
&& state
.q
> state
.r
)
1338 SHOW_C_CTX ("try_complete:var_subst");
1339 matches
= completion_matches (state
.q
, variable_completion_function
, state
.flags
);
1340 if (matches
!= NULL
)
1341 *lc_start
+= state
.q
- state
.word
;
1344 /* Starts with '@', then look through the known hostnames for
1345 completion first. */
1346 else if (state
.r
> state
.p
&& state
.r
> state
.q
)
1348 SHOW_C_CTX ("try_complete:host_subst");
1349 matches
= completion_matches (state
.r
, hostname_completion_function
, state
.flags
);
1350 if (matches
!= NULL
)
1351 *lc_start
+= state
.r
- state
.word
;
1354 /* Starts with '~' and there is no slash in the word, then
1355 try completing this word as a username. */
1356 if (matches
== NULL
&& *state
.word
== '~' && (state
.flags
& INPUT_COMPLETE_USERNAMES
) != 0
1357 && strchr (state
.word
, PATH_SEP
) == NULL
)
1359 SHOW_C_CTX ("try_complete:user_subst");
1360 matches
= completion_matches (state
.word
, username_completion_function
, state
.flags
);
1363 /* If this word is in a command position, then
1364 complete over possible command names, including aliases, functions,
1365 and command names. */
1366 if (matches
== NULL
)
1367 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1369 /* And finally if nothing found, try complete directory name */
1370 if (matches
== NULL
)
1372 state
.in_command_position
= 0;
1373 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1376 g_free (state
.word
);
1378 if (matches
!= NULL
&&
1379 (flags
& (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
)) !=
1380 (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
))
1382 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1385 for (m
= matches
; *m
!= NULL
; m
++)
1390 *m
= strutils_shell_escape (*m
);
1398 /* --------------------------------------------------------------------------------------------- */
1401 complete_engine_fill_completions (WInput
* in
)
1404 const char *word_separators
;
1406 word_separators
= (in
->completion_flags
& INPUT_COMPLETE_SHELL_ESC
) ? " \t;|<>" : "\t;|<>";
1408 end
= str_offset_to_pos (in
->buffer
, in
->point
);
1413 /* get symbol before in->point */
1416 for (i
= in
->point
- 1; i
> 0; i
--)
1420 for (; s
>= in
->buffer
; str_prev_char (&s
))
1422 start
= s
- in
->buffer
;
1423 if (strchr (word_separators
, *s
) != NULL
&& !strutils_is_char_escaped (in
->buffer
, s
))
1430 start
= s
- in
->buffer
;
1433 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1436 /* --------------------------------------------------------------------------------------------- */
1438 /* declared in lib/widget/input.h */
1440 complete (WInput
* in
)
1444 if (!str_is_valid_string (in
->buffer
))
1447 if (in
->completions
!= NULL
)
1448 engine_flags
= DO_QUERY
;
1451 engine_flags
= DO_INSERTION
;
1453 if (mc_global
.widget
.show_all_if_ambiguous
)
1454 engine_flags
|= DO_QUERY
;
1457 while (complete_engine (in
, engine_flags
))
1461 /* --------------------------------------------------------------------------------------------- */