2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2019
6 Free Software Foundation, Inc.
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 WDialog
*h
= DIALOG (w
);
1038 /* exit from completion list if input line is empty */
1044 /* Refill the list box and start again */
1045 else if (end
== min_end
)
1047 end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1048 input_handle_char (input
, parm
);
1049 h
->ret_value
= B_USER
;
1058 new_end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1060 for (i
= 0, e
= listbox_get_first_link (LISTBOX (h
->current
->data
));
1061 e
!= NULL
; i
++, e
= g_list_next (e
))
1063 WLEntry
*le
= LENTRY (e
->data
);
1065 if (strncmp (input
->buffer
+ start
, le
->text
, new_end
- start
) == 0)
1067 listbox_select_entry (LISTBOX (h
->current
->data
), i
);
1069 input_handle_char (input
, parm
);
1070 widget_redraw (WIDGET (h
->current
->data
));
1078 if (parm
< 32 || parm
> 255)
1081 if (input_key_is_in_map (input
, parm
) != 2)
1082 return MSG_NOT_HANDLED
;
1087 /* This means we want to refill the list box and start again */
1088 h
->ret_value
= B_USER
;
1093 static char buff
[MB_LEN_MAX
] = "";
1096 int need_redraw
= 0;
1098 char *last_text
= NULL
;
1100 buff
[bl
++] = (char) parm
;
1103 switch (str_is_valid_char (buff
, bl
))
1114 for (i
= 0, e
= listbox_get_first_link (LISTBOX (h
->current
->data
));
1115 e
!= NULL
; i
++, e
= g_list_next (e
))
1117 WLEntry
*le
= LENTRY (e
->data
);
1119 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
) == 0
1120 && strncmp (&le
->text
[end
- start
], buff
, bl
) == 0)
1122 if (need_redraw
== 0)
1125 listbox_select_entry (LISTBOX (h
->current
->data
), i
);
1126 last_text
= le
->text
;
1134 /* count symbols between start and end */
1135 for (si
= le
->text
+ start
; si
< le
->text
+ end
;
1136 str_next_char (&si
), si_num
++)
1138 for (sl
= last_text
+ start
; sl
< last_text
+ end
;
1139 str_next_char (&sl
), sl_num
++)
1142 /* pointers to next symbols */
1143 si
= &le
->text
[str_offset_to_pos (le
->text
, ++si_num
)];
1144 sl
= &last_text
[str_offset_to_pos (last_text
, ++sl_num
)];
1146 while (si
[0] != '\0' && sl
[0] != '\0')
1148 char *nexti
, *nextl
;
1150 nexti
= str_get_next_char (si
);
1151 nextl
= str_get_next_char (sl
);
1153 if (nexti
- si
!= nextl
- sl
|| strncmp (si
, sl
, nexti
- si
) != 0)
1162 last_text
= le
->text
;
1164 si
= &last_text
[str_offset_to_pos (last_text
, si_num
)];
1165 if (low
> si
- last_text
)
1166 low
= si
- last_text
;
1173 if (need_redraw
== 2)
1175 insert_text (input
, last_text
, low
);
1176 widget_redraw (WIDGET (h
->current
->data
));
1178 else if (need_redraw
== 1)
1180 h
->ret_value
= B_ENTER
;
1189 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
1193 /* --------------------------------------------------------------------------------------------- */
1195 /** Returns TRUE if the user would like to see us again */
1197 complete_engine (WInput
* in
, int what_to_do
)
1199 if (in
->completions
!= NULL
&& str_offset_to_pos (in
->buffer
, in
->point
) != end
)
1200 input_free_completions (in
);
1202 if (in
->completions
== NULL
)
1203 complete_engine_fill_completions (in
);
1205 if (in
->completions
== NULL
)
1209 if ((what_to_do
& DO_INSERTION
) != 0
1210 || ((what_to_do
& DO_QUERY
) != 0 && in
->completions
[1] == NULL
))
1212 char *lc_complete
= in
->completions
[0];
1214 if (!insert_text (in
, lc_complete
, strlen (lc_complete
)) || in
->completions
[1] != NULL
)
1217 input_free_completions (in
);
1220 if ((what_to_do
& DO_QUERY
) != 0 && in
->completions
!= NULL
&& in
->completions
[1] != NULL
)
1222 int maxlen
= 0, count
= 0, i
;
1224 int start_x
, start_y
;
1227 WListbox
*query_list
;
1229 for (p
= in
->completions
+ 1; *p
!= NULL
; count
++, p
++)
1231 i
= str_term_width1 (*p
);
1236 start_x
= WIDGET (in
)->x
;
1237 start_y
= WIDGET (in
)->y
;
1238 if (start_y
- 2 >= count
)
1240 y
= start_y
- 2 - count
;
1243 else if (start_y
>= LINES
- start_y
- 1)
1251 h
= LINES
- start_y
- 1;
1253 x
= start
- in
->term_first_shown
- 2 + start_x
;
1267 query_dlg
= dlg_create (TRUE
, y
, x
, query_height
, query_width
, WPOS_KEEP_DEFAULT
, TRUE
,
1268 dialog_colors
, query_callback
, NULL
, "[Completion]", NULL
);
1269 query_list
= listbox_new (1, 1, h
- 2, w
- 2, FALSE
, NULL
);
1270 add_widget (query_dlg
, query_list
);
1272 for (p
= in
->completions
+ 1; *p
!= NULL
; p
++)
1273 listbox_add_item (query_list
, LISTBOX_APPEND_AT_END
, 0, *p
, NULL
, FALSE
);
1275 i
= dlg_run (query_dlg
);
1279 listbox_get_current (query_list
, &q
, NULL
);
1281 insert_text (in
, q
, strlen (q
));
1283 if (q
!= NULL
|| end
!= min_end
)
1284 input_free_completions (in
);
1285 dlg_destroy (query_dlg
);
1287 /* B_USER if user wants to start over again */
1288 return (i
== B_USER
);
1295 /* --------------------------------------------------------------------------------------------- */
1296 /*** public functions ****************************************************************************/
1297 /* --------------------------------------------------------------------------------------------- */
1299 /** Returns an array of matches, or NULL if none. */
1301 try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
)
1303 try_complete_automation_state_t state
;
1304 char **matches
= NULL
;
1306 memset (&state
, 0, sizeof (state
));
1307 state
.flags
= flags
;
1309 SHOW_C_CTX ("try_complete");
1310 state
.word
= g_strndup (text
+ *lc_start
, *lc_end
- *lc_start
);
1312 state
.is_cd
= check_is_cd (text
, *lc_start
, state
.flags
);
1314 /* Determine if this could be a command word. It is if it appears at
1315 the start of the line (ignoring preceding whitespace), or if it
1316 appears after a character that separates commands. And we have to
1317 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1318 if (!state
.is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
) != 0)
1319 try_complete_commands_prepare (&state
, text
, lc_start
);
1321 try_complete_find_start_sign (&state
);
1323 /* Command substitution? */
1324 if (state
.p
> state
.q
&& state
.p
> state
.r
)
1326 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1327 matches
= completion_matches (str_cget_next_char (state
.p
),
1328 command_completion_function
,
1329 state
.flags
& (~INPUT_COMPLETE_FILENAMES
));
1330 if (matches
!= NULL
)
1331 *lc_start
+= str_get_next_char (state
.p
) - state
.word
;
1334 /* Variable name? */
1335 else if (state
.q
> state
.p
&& state
.q
> state
.r
)
1337 SHOW_C_CTX ("try_complete:var_subst");
1338 matches
= completion_matches (state
.q
, variable_completion_function
, state
.flags
);
1339 if (matches
!= NULL
)
1340 *lc_start
+= state
.q
- state
.word
;
1343 /* Starts with '@', then look through the known hostnames for
1344 completion first. */
1345 else if (state
.r
> state
.p
&& state
.r
> state
.q
)
1347 SHOW_C_CTX ("try_complete:host_subst");
1348 matches
= completion_matches (state
.r
, hostname_completion_function
, state
.flags
);
1349 if (matches
!= NULL
)
1350 *lc_start
+= state
.r
- state
.word
;
1353 /* Starts with '~' and there is no slash in the word, then
1354 try completing this word as a username. */
1355 if (matches
== NULL
&& *state
.word
== '~' && (state
.flags
& INPUT_COMPLETE_USERNAMES
) != 0
1356 && strchr (state
.word
, PATH_SEP
) == NULL
)
1358 SHOW_C_CTX ("try_complete:user_subst");
1359 matches
= completion_matches (state
.word
, username_completion_function
, state
.flags
);
1362 /* If this word is in a command position, then
1363 complete over possible command names, including aliases, functions,
1364 and command names. */
1365 if (matches
== NULL
)
1366 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1368 /* And finally if nothing found, try complete directory name */
1369 if (matches
== NULL
)
1371 state
.in_command_position
= 0;
1372 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1375 g_free (state
.word
);
1377 if (matches
!= NULL
&&
1378 (flags
& (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
)) !=
1379 (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
))
1381 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1384 for (m
= matches
; *m
!= NULL
; m
++)
1389 *m
= strutils_shell_escape (*m
);
1397 /* --------------------------------------------------------------------------------------------- */
1400 complete_engine_fill_completions (WInput
* in
)
1403 const char *word_separators
;
1405 word_separators
= (in
->completion_flags
& INPUT_COMPLETE_SHELL_ESC
) ? " \t;|<>" : "\t;|<>";
1407 end
= str_offset_to_pos (in
->buffer
, in
->point
);
1412 /* get symbol before in->point */
1415 for (i
= in
->point
- 1; i
> 0; i
--)
1419 for (; s
>= in
->buffer
; str_prev_char (&s
))
1421 start
= s
- in
->buffer
;
1422 if (strchr (word_separators
, *s
) != NULL
&& !strutils_is_char_escaped (in
->buffer
, s
))
1429 start
= s
- in
->buffer
;
1432 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1435 /* --------------------------------------------------------------------------------------------- */
1437 /* declared in lib/widget/input.h */
1439 complete (WInput
* in
)
1443 if (!str_is_valid_string (in
->buffer
))
1446 if (in
->completions
!= NULL
)
1447 engine_flags
= DO_QUERY
;
1450 engine_flags
= DO_INSERTION
;
1452 if (mc_global
.widget
.show_all_if_ambiguous
)
1453 engine_flags
|= DO_QUERY
;
1456 while (complete_engine (in
, engine_flags
))
1460 /* --------------------------------------------------------------------------------------------- */