2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2021
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 /*** global variables ****************************************************************************/
58 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
59 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
60 extern char **environ
;
63 /*** file scope macro definitions ****************************************************************/
65 /* #define DO_COMPLETION_DEBUG */
66 #ifdef DO_COMPLETION_DEBUG
67 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
69 #define SHOW_C_CTX(func)
70 #endif /* DO_CMPLETION_DEBUG */
72 #define DO_INSERTION 1
75 /*** file scope type declarations ****************************************************************/
77 typedef char *CompletionFunction (const char *text
, int state
, input_complete_t flags
);
81 size_t in_command_position
;
87 input_complete_t flags
;
88 } try_complete_automation_state_t
;
90 /*** file scope variables ************************************************************************/
92 static char **hosts
= NULL
;
93 static char **hosts_p
= NULL
;
94 static int hosts_alloclen
= 0;
96 static int complete_height
, complete_width
;
102 /*** file scope functions ************************************************************************/
103 /* --------------------------------------------------------------------------------------------- */
105 char **try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
);
106 void complete_engine_fill_completions (WInput
* in
);
108 #ifdef DO_COMPLETION_DEBUG
110 * Useful to print/debug completion flags
113 show_c_flags (input_complete_t flags
)
115 static char s_cf
[] = "FHCVUDS";
117 s_cf
[0] = (flags
& INPUT_COMPLETE_FILENAMES
) != 0 ? 'F' : ' ';
118 s_cf
[1] = (flags
& INPUT_COMPLETE_HOSTNAMES
) != 0 ? 'H' : ' ';
119 s_cf
[2] = (flags
& INPUT_COMPLETE_COMMANDS
) != 0 ? 'C' : ' ';
120 s_cf
[3] = (flags
& INPUT_COMPLETE_VARIABLES
) != 0 ? 'V' : ' ';
121 s_cf
[4] = (flags
& INPUT_COMPLETE_USERNAMES
) != 0 ? 'U' : ' ';
122 s_cf
[5] = (flags
& INPUT_COMPLETE_CD
) != 0 ? 'D' : ' ';
123 s_cf
[6] = (flags
& INPUT_COMPLETE_SHELL_ESC
) != 0 ? 'S' : ' ';
127 #endif /* DO_CMPLETION_DEBUG */
129 /* --------------------------------------------------------------------------------------------- */
132 filename_completion_function (const char *text
, int state
, input_complete_t flags
)
134 static DIR *directory
= NULL
;
135 static char *filename
= NULL
;
136 static char *dirname
= NULL
;
137 static char *users_dirname
= NULL
;
138 static size_t filename_len
= 0;
139 static vfs_path_t
*dirname_vpath
= NULL
;
141 gboolean isdir
= TRUE
, isexec
= FALSE
;
142 struct vfs_dirent
*entry
= NULL
;
144 SHOW_C_CTX ("filename_completion_function");
146 if (text
!= NULL
&& (flags
& INPUT_COMPLETE_SHELL_ESC
) != 0)
152 u_text
= strutils_shell_unescape (text
);
154 result
= filename_completion_function (u_text
, state
, flags
& (~INPUT_COMPLETE_SHELL_ESC
));
157 e_result
= strutils_shell_escape (result
);
163 /* If we're starting the match process, initialize us a bit. */
170 g_free (users_dirname
);
171 vfs_path_free (dirname_vpath
, TRUE
);
173 if ((*text
!= '\0') && (temp
= strrchr (text
, PATH_SEP
)) != NULL
)
175 filename
= g_strdup (++temp
);
176 dirname
= g_strndup (text
, temp
- text
);
180 dirname
= g_strdup (".");
181 filename
= g_strdup (text
);
184 /* We aren't done yet. We also support the "~user" syntax. */
186 /* Save the version of the directory that the user typed. */
187 users_dirname
= dirname
;
188 dirname
= tilde_expand (dirname
);
189 canonicalize_pathname (dirname
);
190 dirname_vpath
= vfs_path_from_str (dirname
);
192 /* Here we should do something with variable expansion
194 Maybe a dream - UNIMPLEMENTED yet. */
196 directory
= mc_opendir (dirname_vpath
);
197 filename_len
= strlen (filename
);
200 /* Now that we have some state, we can read the directory. */
202 while (directory
!= NULL
&& (entry
= mc_readdir (directory
)) != NULL
)
204 if (!str_is_valid_string (entry
->d_name
))
207 /* Special case for no filename.
208 All entries except "." and ".." match. */
209 if (filename_len
== 0)
211 if (DIR_IS_DOT (entry
->d_name
) || DIR_IS_DOTDOT (entry
->d_name
))
216 /* Otherwise, if these match up to the length of filename, then
217 it may be a match. */
218 if ((entry
->d_name
[0] != filename
[0]) ||
219 ((NLENGTH (entry
)) < filename_len
) ||
220 strncmp (filename
, entry
->d_name
, filename_len
) != 0)
228 struct stat tempstat
;
229 vfs_path_t
*tmp_vpath
;
231 tmp_vpath
= vfs_path_build_filename (dirname
, entry
->d_name
, (char *) NULL
);
234 if (mc_stat (tmp_vpath
, &tempstat
) == 0)
242 if (!S_ISDIR (tempstat
.st_mode
))
246 if ((my_uid
== 0 && (tempstat
.st_mode
& 0111) != 0) ||
247 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100) != 0) ||
248 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010) != 0) ||
249 (tempstat
.st_mode
& 0001) != 0)
255 /* stat failed, strange. not a dir in any case */
258 vfs_path_free (tmp_vpath
, TRUE
);
261 if ((flags
& INPUT_COMPLETE_COMMANDS
) != 0 && (isexec
|| isdir
))
263 if ((flags
& INPUT_COMPLETE_CD
) != 0 && isdir
)
265 if ((flags
& INPUT_COMPLETE_FILENAMES
) != 0)
271 if (directory
!= NULL
)
273 mc_closedir (directory
);
276 MC_PTR_FREE (dirname
);
277 vfs_path_free (dirname_vpath
, TRUE
);
278 dirname_vpath
= NULL
;
279 MC_PTR_FREE (filename
);
280 MC_PTR_FREE (users_dirname
);
287 temp
= g_string_sized_new (16);
289 if (users_dirname
!= NULL
&& (users_dirname
[0] != '.' || users_dirname
[1] != '\0'))
291 g_string_append (temp
, users_dirname
);
293 /* We need a '/' at the end. */
294 if (!IS_PATH_SEP (temp
->str
[temp
->len
- 1]))
295 g_string_append_c (temp
, PATH_SEP
);
297 g_string_append (temp
, entry
->d_name
);
299 g_string_append_c (temp
, PATH_SEP
);
301 return g_string_free (temp
, FALSE
);
305 /* --------------------------------------------------------------------------------------------- */
306 /** We assume here that text[0] == '~' , if you want to call it in another way,
307 you have to change the code */
310 username_completion_function (const char *text
, int state
, input_complete_t flags
)
312 static struct passwd
*entry
= NULL
;
313 static size_t userlen
= 0;
316 SHOW_C_CTX ("username_completion_function");
318 if (text
[0] == '\\' && text
[1] == '~')
321 { /* Initialization stuff */
323 userlen
= strlen (text
+ 1);
326 while ((entry
= getpwent ()) != NULL
)
328 /* Null usernames should result in all users as possible completions. */
331 if (text
[1] == entry
->pw_name
[0] && strncmp (text
+ 1, entry
->pw_name
, userlen
) == 0)
336 return g_strconcat ("~", entry
->pw_name
, PATH_SEP_STR
, (char *) NULL
);
342 /* --------------------------------------------------------------------------------------------- */
343 /** We assume text [0] == '$' and want to have a look at text [1], if it is
344 equal to '{', so that we should append '}' at the end */
347 variable_completion_function (const char *text
, int state
, input_complete_t flags
)
349 static char **env_p
= NULL
;
350 static gboolean isbrace
= FALSE
;
351 static size_t varlen
= 0;
352 const char *p
= NULL
;
355 SHOW_C_CTX ("variable_completion_function");
358 { /* Initialization stuff */
359 isbrace
= (text
[1] == '{');
360 varlen
= strlen (text
+ 1 + isbrace
);
364 while (*env_p
!= NULL
)
366 p
= strchr (*env_p
, '=');
367 if (p
!= NULL
&& ((size_t) (p
- *env_p
) >= varlen
)
368 && strncmp (text
+ 1 + isbrace
, *env_p
, varlen
) == 0)
379 temp
= g_string_new_len (*env_p
, p
- *env_p
);
383 g_string_prepend_c (temp
, '{');
384 g_string_append_c (temp
, '}');
386 g_string_prepend_c (temp
, '$');
390 return g_string_free (temp
, FALSE
);
394 /* --------------------------------------------------------------------------------------------- */
397 fetch_hosts (const char *filename
)
405 file
= fopen (filename
, "r");
409 while (fgets (buffer
, sizeof (buffer
) - 1, file
) != NULL
)
411 /* Skip to first character. */
412 for (bi
= buffer
; bi
[0] != '\0' && str_isspace (bi
); str_next_char (&bi
))
415 /* Ignore comments... */
419 /* Handle $include. */
420 if (strncmp (bi
, "$include ", 9) == 0)
422 char *includefile
, *t
;
424 /* Find start of filename. */
425 includefile
= bi
+ 9;
426 while (*includefile
!= '\0' && whitespace (*includefile
))
430 /* Find end of filename. */
431 while (t
[0] != '\0' && !str_isspace (t
))
435 fetch_hosts (includefile
);
440 while (bi
[0] != '\0' && !str_isspace (bi
))
443 /* Get the host names separated by white space. */
444 while (bi
[0] != '\0' && bi
[0] != '#')
446 while (bi
[0] != '\0' && str_isspace (bi
))
450 for (lc_start
= bi
; bi
[0] != '\0' && !str_isspace (bi
); str_next_char (&bi
))
456 name
= g_strndup (lc_start
, bi
- lc_start
);
464 if (j
>= hosts_alloclen
)
466 hosts_alloclen
+= 30;
467 hosts
= g_renew (char *, hosts
, hosts_alloclen
+ 1);
471 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
472 if (strcmp (name
, *host_p
) == 0)
473 break; /* We do not want any duplicates */
475 if (host_p
== hosts_p
)
489 /* --------------------------------------------------------------------------------------------- */
492 hostname_completion_function (const char *text
, int state
, input_complete_t flags
)
494 static char **host_p
= NULL
;
495 static size_t textstart
= 0;
496 static size_t textlen
= 0;
499 SHOW_C_CTX ("hostname_completion_function");
502 { /* Initialization stuff */
507 hosts
= g_new (char *, hosts_alloclen
+ 1);
510 p
= getenv ("HOSTFILE");
511 fetch_hosts (p
!= NULL
? p
: "/etc/hosts");
513 textstart
= (*text
== '@') ? 1 : 0;
514 textlen
= strlen (text
+ textstart
);
517 for (; *host_p
!= NULL
; host_p
++)
520 break; /* Match all of them */
521 if (strncmp (text
+ textstart
, *host_p
, textlen
) == 0)
535 temp
= g_string_sized_new (8);
538 g_string_append_c (temp
, '@');
539 g_string_append (temp
, *host_p
);
542 return g_string_free (temp
, FALSE
);
546 /* --------------------------------------------------------------------------------------------- */
548 * This is the function to call when the word to complete is in a position
549 * where a command word can be found. It looks around $PATH, looking for
550 * commands that match. It also scans aliases, function names, and the
551 * table of shell built-ins.
555 command_completion_function (const char *text
, int state
, input_complete_t flags
)
557 static const char *path_end
= NULL
;
558 static gboolean isabsolute
= FALSE
;
559 static int phase
= 0;
560 static size_t text_len
= 0;
561 static const char *const *words
= NULL
;
562 static char *path
= NULL
;
563 static char *cur_path
= NULL
;
564 static char *cur_word
= NULL
;
565 static int init_state
= 0;
566 static const char *const bash_reserved
[] = {
567 "if", "then", "else", "elif", "fi", "case", "esac", "for",
568 "select", "while", "until", "do", "done", "in", "function", 0
570 static const char *const bash_builtins
[] = {
571 "alias", "bg", "bind", "break", "builtin", "cd", "command",
572 "continue", "declare", "dirs", "echo", "enable", "eval",
573 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
574 "help", "history", "jobs", "kill", "let", "local", "logout",
575 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
576 "shift", "source", "suspend", "test", "times", "trap", "type",
577 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
583 SHOW_C_CTX ("command_completion_function");
585 if ((flags
& INPUT_COMPLETE_COMMANDS
) == 0)
588 u_text
= strutils_shell_unescape (text
);
589 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
592 { /* Initialize us a little bit */
593 isabsolute
= strchr (u_text
, PATH_SEP
) != NULL
;
596 words
= bash_reserved
;
598 text_len
= strlen (u_text
);
602 path
= g_strdup (getenv ("PATH"));
606 path_end
= strchr (p
, '\0');
607 while ((p
= strchr (p
, PATH_ENV_SEP
)) != NULL
)
616 p
= filename_completion_function (u_text
, state
, flags
);
622 p
= strutils_shell_escape (p
);
633 case 0: /* Reserved words */
634 for (; *words
!= NULL
; words
++)
635 if (strncmp (*words
, u_text
, text_len
) == 0)
638 return g_strdup (*(words
++));
641 words
= bash_builtins
;
643 case 1: /* Builtin commands */
644 for (; *words
!= NULL
; words
++)
645 if (strncmp (*words
, u_text
, text_len
) == 0)
648 return g_strdup (*(words
++));
656 case 2: /* And looking through the $PATH */
657 while (found
== NULL
)
659 if (cur_word
== NULL
)
663 if (cur_path
>= path_end
)
665 expanded
= tilde_expand (*cur_path
!= '\0' ? cur_path
: ".");
666 cur_word
= mc_build_filename (expanded
, u_text
, (char *) NULL
);
668 canonicalize_pathname (cur_word
);
669 cur_path
= strchr (cur_path
, '\0') + 1;
672 found
= filename_completion_function (cur_word
, state
- init_state
, flags
);
674 MC_PTR_FREE (cur_word
);
685 p
= strrchr (found
, PATH_SEP
);
690 found
= strutils_shell_escape (p
+ 1);
699 /* --------------------------------------------------------------------------------------------- */
702 match_compare (const void *a
, const void *b
)
704 return strcmp (*(char *const *) a
, *(char *const *) b
);
707 /* --------------------------------------------------------------------------------------------- */
708 /** Returns an array of char * matches with the longest common denominator
709 in the 1st entry. Then a NULL terminated list of different possible
711 You have to supply your own CompletionFunction with the word you
712 want to complete as the first argument and an count of previous matches
714 In case no matches were found we return NULL. */
717 completion_matches (const char *text
, CompletionFunction entry_function
, input_complete_t flags
)
719 /* Number of slots in match_list. */
720 size_t match_list_size
= 30;
721 /* The list of matches. */
723 /* Number of matches actually found. */
726 /* Temporary string binder. */
729 match_list
= g_new (char *, match_list_size
+ 1);
730 match_list
[1] = NULL
;
732 while ((string
= (*entry_function
) (text
, matches
, flags
)) != NULL
)
734 if (matches
+ 1 == match_list_size
)
736 match_list_size
+= 30;
737 match_list
= (char **) g_renew (char *, match_list
, match_list_size
+ 1);
739 match_list
[++matches
] = string
;
740 match_list
[matches
+ 1] = NULL
;
743 /* If there were any matches, then look through them finding out the
744 lowest common denominator. That then becomes match_list[0]. */
746 MC_PTR_FREE (match_list
); /* There were no matches. */
749 /* If only one match, just use that. */
752 match_list
[0] = match_list
[1];
753 match_list
[1] = NULL
;
758 int low
= 4096; /* Count of max-matched characters. */
761 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
763 /* And compare each member of the list with
764 the next, finding out where they stop matching.
765 If we find two equal strings, we have to put one away... */
768 while (j
< matches
+ 1)
773 for (si
= match_list
[i
], sj
= match_list
[j
]; si
[0] != '\0' && sj
[0] != '\0';)
776 ni
= str_get_next_char (si
);
777 nj
= str_get_next_char (sj
);
779 if (ni
- si
!= nj
- sj
)
781 if (strncmp (si
, sj
, ni
- si
) != 0)
788 if (si
[0] == '\0' && sj
[0] == '\0')
789 { /* Two equal strings */
790 g_free (match_list
[j
]);
794 continue; /* Look for a run of equal strings */
796 else if (low
> si
- match_list
[i
])
797 low
= si
- match_list
[i
];
798 if (i
+ 1 != j
) /* So there's some gap */
799 match_list
[i
+ 1] = match_list
[j
];
804 match_list
[matches
+ 1] = NULL
;
805 match_list
[0] = g_strndup (match_list
[1], low
);
812 /* --------------------------------------------------------------------------------------------- */
813 /** Check if directory completion is needed */
815 check_is_cd (const char *text
, int lc_start
, input_complete_t flags
)
819 SHOW_C_CTX ("check_is_cd");
821 if ((flags
& INPUT_COMPLETE_CD
) == 0)
824 /* Skip initial spaces */
827 while (p
< q
&& p
[0] != '\0' && str_isspace (p
))
830 /* Check if the command is "cd" and the cursor is after it */
831 return (p
[0] == 'c' && p
[1] == 'd' && str_isspace (p
+ 2) && p
+ 2 < q
);
834 /* --------------------------------------------------------------------------------------------- */
837 try_complete_commands_prepare (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
839 const char *command_separator_chars
= ";|&{(`";
846 ti
= str_get_prev_char (&text
[*lc_start
]);
847 while (ti
> text
&& whitespace (ti
[0]))
852 state
->in_command_position
++;
853 else if (strchr (command_separator_chars
, ti
[0]) != NULL
)
855 state
->in_command_position
++;
858 int this_char
, prev_char
;
860 /* Handle the two character tokens '>&', '<&', and '>|'.
861 We are not in a command position after one of these. */
863 prev_char
= str_get_prev_char (ti
)[0];
866 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>'))
867 || (this_char
== '|' && prev_char
== '>') || (ti
!= text
868 && str_get_prev_char (ti
)[0] == '\\'))
869 state
->in_command_position
= 0;
874 /* --------------------------------------------------------------------------------------------- */
877 try_complete_find_start_sign (try_complete_automation_state_t
* state
)
879 if ((state
->flags
& INPUT_COMPLETE_COMMANDS
) != 0)
880 state
->p
= strrchr (state
->word
, '`');
881 if ((state
->flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
)) != 0)
883 state
->q
= strrchr (state
->word
, '$');
885 /* don't substitute variable in \$ case */
886 if (strutils_is_char_escaped (state
->word
, state
->q
))
889 str_move (state
->q
- 1, state
->q
);
891 state
->flags
&= ~INPUT_COMPLETE_VARIABLES
;
895 if ((state
->flags
& INPUT_COMPLETE_HOSTNAMES
) != 0)
896 state
->r
= strrchr (state
->word
, '@');
897 if (state
->q
!= NULL
&& state
->q
[1] == '(' && (state
->flags
& INPUT_COMPLETE_COMMANDS
) != 0)
899 if (state
->q
> state
->p
)
900 state
->p
= str_get_next_char (state
->q
);
905 /* --------------------------------------------------------------------------------------------- */
908 try_complete_all_possible (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
910 char **matches
= NULL
;
912 if (state
->in_command_position
!= 0)
914 SHOW_C_CTX ("try_complete:cmd_subst");
916 completion_matches (state
->word
, command_completion_function
,
917 state
->flags
& (~INPUT_COMPLETE_FILENAMES
));
919 else if ((state
->flags
& INPUT_COMPLETE_FILENAMES
) != 0)
922 state
->flags
&= ~(INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_COMMANDS
);
923 SHOW_C_CTX ("try_complete:filename_subst_1");
924 matches
= completion_matches (state
->word
, filename_completion_function
, state
->flags
);
926 if (matches
== NULL
&& state
->is_cd
&& !IS_PATH_SEP (*state
->word
) && *state
->word
!= '~')
928 state
->q
= text
+ *lc_start
;
929 for (state
->p
= text
;
930 *state
->p
!= '\0' && state
->p
< state
->q
&& whitespace (*state
->p
);
931 str_next_char (&state
->p
))
933 if (strncmp (state
->p
, "cd", 2) == 0)
935 *state
->p
!= '\0' && state
->p
< state
->q
&& whitespace (*state
->p
);
936 str_next_char (&state
->p
))
938 if (state
->p
== state
->q
)
940 char *cdpath_ref
, *cdpath
;
943 cdpath_ref
= g_strdup (getenv ("CDPATH"));
945 c
= (cdpath
== NULL
) ? '\0' : ':';
947 while (matches
== NULL
&& c
== ':')
951 s
= strchr (cdpath
, ':');
952 /* cppcheck-suppress nullPointer */
954 s
= strchr (cdpath
, '\0');
959 state
->r
= mc_build_filename (cdpath
, state
->word
, (char *) NULL
);
960 SHOW_C_CTX ("try_complete:filename_subst_2");
962 completion_matches (state
->r
, filename_completion_function
,
967 cdpath
= str_get_next_char (s
);
976 /* --------------------------------------------------------------------------------------------- */
979 insert_text (WInput
* in
, char *text
, ssize_t size
)
984 text_len
= strlen (text
);
985 buff_len
= str_length (in
->buffer
);
987 size
= (ssize_t
) text_len
;
989 size
= MIN (size
, (ssize_t
) text_len
);
991 if (strlen (in
->buffer
) + size
>= (size_t) in
->current_max_size
)
993 /* Expand the buffer */
995 Widget
*w
= WIDGET (in
);
997 narea
= g_try_realloc (in
->buffer
, in
->current_max_size
+ size
+ w
->cols
);
1001 in
->current_max_size
+= size
+ w
->cols
;
1004 if (strlen (in
->buffer
) + 1 < (size_t) in
->current_max_size
)
1007 memmove (in
->buffer
+ end
+ size
, in
->buffer
+ end
, strlen (&in
->buffer
[end
]) + 1);
1008 memmove (in
->buffer
+ start
, text
, size
- (start
- end
));
1009 in
->point
+= str_length (in
->buffer
) - buff_len
;
1010 input_update (in
, TRUE
);
1017 /* --------------------------------------------------------------------------------------------- */
1020 complete_callback (Widget
* w
, Widget
* sender
, widget_msg_t msg
, int parm
, void *data
)
1024 WGroup
*g
= GROUP (w
);
1025 WDialog
*h
= DIALOG (w
);
1041 /* exit from completion list if input line is empty */
1047 /* Refill the list box and start again */
1048 else if (end
== min_end
)
1050 end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1051 input_handle_char (input
, parm
);
1052 h
->ret_value
= B_USER
;
1061 new_end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1063 for (i
= 0, e
= listbox_get_first_link (LISTBOX (g
->current
->data
));
1064 e
!= NULL
; i
++, e
= g_list_next (e
))
1066 WLEntry
*le
= LENTRY (e
->data
);
1068 if (strncmp (input
->buffer
+ start
, le
->text
, new_end
- start
) == 0)
1070 listbox_select_entry (LISTBOX (g
->current
->data
), i
);
1072 input_handle_char (input
, parm
);
1073 widget_draw (WIDGET (g
->current
->data
));
1081 if (parm
< 32 || parm
> 255)
1084 if (widget_lookup_key (WIDGET (input
), parm
) != CK_Complete
)
1085 return MSG_NOT_HANDLED
;
1090 /* This means we want to refill the list box and start again */
1091 h
->ret_value
= B_USER
;
1096 static char buff
[MB_LEN_MAX
] = "";
1099 int need_redraw
= 0;
1101 char *last_text
= NULL
;
1103 buff
[bl
++] = (char) parm
;
1106 switch (str_is_valid_char (buff
, bl
))
1117 for (i
= 0, e
= listbox_get_first_link (LISTBOX (g
->current
->data
));
1118 e
!= NULL
; i
++, e
= g_list_next (e
))
1120 WLEntry
*le
= LENTRY (e
->data
);
1122 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
) == 0
1123 && strncmp (&le
->text
[end
- start
], buff
, bl
) == 0)
1125 if (need_redraw
== 0)
1128 listbox_select_entry (LISTBOX (g
->current
->data
), i
);
1129 last_text
= le
->text
;
1137 /* count symbols between start and end */
1138 for (si
= le
->text
+ start
; si
< le
->text
+ end
;
1139 str_next_char (&si
), si_num
++)
1141 for (sl
= last_text
+ start
; sl
< last_text
+ end
;
1142 str_next_char (&sl
), sl_num
++)
1145 /* pointers to next symbols */
1146 si
= &le
->text
[str_offset_to_pos (le
->text
, ++si_num
)];
1147 sl
= &last_text
[str_offset_to_pos (last_text
, ++sl_num
)];
1149 while (si
[0] != '\0' && sl
[0] != '\0')
1151 char *nexti
, *nextl
;
1153 nexti
= str_get_next_char (si
);
1154 nextl
= str_get_next_char (sl
);
1156 if (nexti
- si
!= nextl
- sl
|| strncmp (si
, sl
, nexti
- si
) != 0)
1165 last_text
= le
->text
;
1167 si
= &last_text
[str_offset_to_pos (last_text
, si_num
)];
1168 if (low
> si
- last_text
)
1169 low
= si
- last_text
;
1176 if (need_redraw
== 2)
1178 insert_text (input
, last_text
, low
);
1179 widget_draw (WIDGET (g
->current
->data
));
1181 else if (need_redraw
== 1)
1183 h
->ret_value
= B_ENTER
;
1192 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
1196 /* --------------------------------------------------------------------------------------------- */
1198 /** Returns TRUE if the user would like to see us again */
1200 complete_engine (WInput
* in
, int what_to_do
)
1202 if (in
->completions
!= NULL
&& str_offset_to_pos (in
->buffer
, in
->point
) != end
)
1203 input_complete_free (in
);
1205 if (in
->completions
== NULL
)
1206 complete_engine_fill_completions (in
);
1208 if (in
->completions
== NULL
)
1212 if ((what_to_do
& DO_INSERTION
) != 0
1213 || ((what_to_do
& DO_QUERY
) != 0 && in
->completions
[1] == NULL
))
1215 char *lc_complete
= in
->completions
[0];
1217 if (!insert_text (in
, lc_complete
, -1) || in
->completions
[1] != NULL
)
1220 input_complete_free (in
);
1223 if ((what_to_do
& DO_QUERY
) != 0 && in
->completions
!= NULL
&& in
->completions
[1] != NULL
)
1225 int maxlen
= 0, count
= 0, i
;
1227 int start_x
, start_y
;
1229 WDialog
*complete_dlg
;
1230 WListbox
*complete_list
;
1232 for (p
= in
->completions
+ 1; *p
!= NULL
; count
++, p
++)
1234 i
= str_term_width1 (*p
);
1239 start_x
= WIDGET (in
)->x
;
1240 start_y
= WIDGET (in
)->y
;
1241 if (start_y
- 2 >= count
)
1243 y
= start_y
- 2 - count
;
1246 else if (start_y
>= LINES
- start_y
- 1)
1254 h
= LINES
- start_y
- 1;
1256 x
= start
- in
->term_first_shown
- 2 + start_x
;
1267 complete_height
= h
;
1271 dlg_create (TRUE
, y
, x
, complete_height
, complete_width
, WPOS_KEEP_DEFAULT
, TRUE
,
1272 dialog_colors
, complete_callback
, NULL
, "[Completion]", NULL
);
1273 complete_list
= listbox_new (1, 1, h
- 2, w
- 2, FALSE
, NULL
);
1274 group_add_widget (GROUP (complete_dlg
), complete_list
);
1276 for (p
= in
->completions
+ 1; *p
!= NULL
; p
++)
1277 listbox_add_item (complete_list
, LISTBOX_APPEND_AT_END
, 0, *p
, NULL
, FALSE
);
1279 i
= dlg_run (complete_dlg
);
1283 listbox_get_current (complete_list
, &q
, NULL
);
1285 insert_text (in
, q
, -1);
1287 if (q
!= NULL
|| end
!= min_end
)
1288 input_complete_free (in
);
1289 widget_destroy (WIDGET (complete_dlg
));
1291 /* B_USER if user wants to start over again */
1292 return (i
== B_USER
);
1299 /* --------------------------------------------------------------------------------------------- */
1300 /*** public functions ****************************************************************************/
1301 /* --------------------------------------------------------------------------------------------- */
1303 /** Returns an array of matches, or NULL if none. */
1305 try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
)
1307 try_complete_automation_state_t state
;
1308 char **matches
= NULL
;
1310 memset (&state
, 0, sizeof (state
));
1311 state
.flags
= flags
;
1313 SHOW_C_CTX ("try_complete");
1314 state
.word
= g_strndup (text
+ *lc_start
, *lc_end
- *lc_start
);
1316 state
.is_cd
= check_is_cd (text
, *lc_start
, state
.flags
);
1318 /* Determine if this could be a command word. It is if it appears at
1319 the start of the line (ignoring preceding whitespace), or if it
1320 appears after a character that separates commands. And we have to
1321 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1322 if (!state
.is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
) != 0)
1323 try_complete_commands_prepare (&state
, text
, lc_start
);
1325 try_complete_find_start_sign (&state
);
1327 /* Command substitution? */
1328 if (state
.p
> state
.q
&& state
.p
> state
.r
)
1330 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1331 matches
= completion_matches (str_cget_next_char (state
.p
),
1332 command_completion_function
,
1333 state
.flags
& (~INPUT_COMPLETE_FILENAMES
));
1334 if (matches
!= NULL
)
1335 *lc_start
+= str_get_next_char (state
.p
) - state
.word
;
1338 /* Variable name? */
1339 else if (state
.q
> state
.p
&& state
.q
> state
.r
)
1341 SHOW_C_CTX ("try_complete:var_subst");
1342 matches
= completion_matches (state
.q
, variable_completion_function
, state
.flags
);
1343 if (matches
!= NULL
)
1344 *lc_start
+= state
.q
- state
.word
;
1347 /* Starts with '@', then look through the known hostnames for
1348 completion first. */
1349 else if (state
.r
> state
.p
&& state
.r
> state
.q
)
1351 SHOW_C_CTX ("try_complete:host_subst");
1352 matches
= completion_matches (state
.r
, hostname_completion_function
, state
.flags
);
1353 if (matches
!= NULL
)
1354 *lc_start
+= state
.r
- state
.word
;
1357 /* Starts with '~' and there is no slash in the word, then
1358 try completing this word as a username. */
1359 if (matches
== NULL
&& *state
.word
== '~' && (state
.flags
& INPUT_COMPLETE_USERNAMES
) != 0
1360 && strchr (state
.word
, PATH_SEP
) == NULL
)
1362 SHOW_C_CTX ("try_complete:user_subst");
1363 matches
= completion_matches (state
.word
, username_completion_function
, state
.flags
);
1366 /* If this word is in a command position, then
1367 complete over possible command names, including aliases, functions,
1368 and command names. */
1369 if (matches
== NULL
)
1370 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1372 /* And finally if nothing found, try complete directory name */
1373 if (matches
== NULL
)
1375 state
.in_command_position
= 0;
1376 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1379 g_free (state
.word
);
1381 if (matches
!= NULL
&&
1382 (flags
& (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
)) !=
1383 (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
))
1385 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1388 for (m
= matches
; *m
!= NULL
; m
++)
1393 *m
= strutils_shell_escape (*m
);
1401 /* --------------------------------------------------------------------------------------------- */
1404 complete_engine_fill_completions (WInput
* in
)
1407 const char *word_separators
;
1409 word_separators
= (in
->completion_flags
& INPUT_COMPLETE_SHELL_ESC
) ? " \t;|<>" : "\t;|<>";
1411 end
= str_offset_to_pos (in
->buffer
, in
->point
);
1416 /* get symbol before in->point */
1419 for (i
= in
->point
- 1; i
> 0; i
--)
1423 for (; s
>= in
->buffer
; str_prev_char (&s
))
1425 start
= s
- in
->buffer
;
1426 if (strchr (word_separators
, *s
) != NULL
&& !strutils_is_char_escaped (in
->buffer
, s
))
1433 start
= s
- in
->buffer
;
1436 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1439 /* --------------------------------------------------------------------------------------------- */
1441 /* declared in lib/widget/input.h */
1443 input_complete (WInput
* in
)
1447 if (!str_is_valid_string (in
->buffer
))
1450 if (in
->completions
!= NULL
)
1451 engine_flags
= DO_QUERY
;
1454 engine_flags
= DO_INSERTION
;
1456 if (mc_global
.widget
.show_all_if_ambiguous
)
1457 engine_flags
|= DO_QUERY
;
1460 while (complete_engine (in
, engine_flags
))
1464 /* --------------------------------------------------------------------------------------------- */
1467 input_complete_free (WInput
* in
)
1469 g_strfreev (in
->completions
);
1470 in
->completions
= NULL
;
1473 /* --------------------------------------------------------------------------------------------- */