2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995-2024
6 Free Software Foundation, Inc.
10 Slava Zanko <slavazanko@gmail.com>, 2013
11 Andrew Borodin <aborodin@vmail.ru>, 2013-2022
13 This file is part of the Midnight Commander.
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 /** \file lib/widget/input_complete.c
30 * \brief Source: Input line filename/username/hostname/variable/command completion
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/strutil.h"
53 #include "lib/widget.h"
55 /*** global variables ****************************************************************************/
57 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
58 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
59 extern char **environ
;
62 /*** file scope macro definitions ****************************************************************/
64 /* #define DO_COMPLETION_DEBUG */
65 #ifdef DO_COMPLETION_DEBUG
66 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
68 #define SHOW_C_CTX(func)
69 #endif /* DO_CMPLETION_DEBUG */
71 #define DO_INSERTION 1
74 /*** file scope type declarations ****************************************************************/
76 typedef char *CompletionFunction (const char *text
, int state
, input_complete_t flags
);
80 size_t in_command_position
;
86 input_complete_t flags
;
87 } try_complete_automation_state_t
;
89 /*** forward declarations (file scope functions) *************************************************/
91 char **try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
);
92 void complete_engine_fill_completions (WInput
* in
);
94 /*** file scope variables ************************************************************************/
96 static char **hosts
= NULL
;
97 static char **hosts_p
= NULL
;
98 static int hosts_alloclen
= 0;
100 static WInput
*input
;
102 static int start
= 0;
105 /* --------------------------------------------------------------------------------------------- */
106 /*** file scope functions ************************************************************************/
107 /* --------------------------------------------------------------------------------------------- */
109 #ifdef DO_COMPLETION_DEBUG
111 * Useful to print/debug completion flags
114 show_c_flags (input_complete_t flags
)
116 static char s_cf
[] = "FHCVUDS";
118 s_cf
[0] = (flags
& INPUT_COMPLETE_FILENAMES
) != 0 ? 'F' : ' ';
119 s_cf
[1] = (flags
& INPUT_COMPLETE_HOSTNAMES
) != 0 ? 'H' : ' ';
120 s_cf
[2] = (flags
& INPUT_COMPLETE_COMMANDS
) != 0 ? 'C' : ' ';
121 s_cf
[3] = (flags
& INPUT_COMPLETE_VARIABLES
) != 0 ? 'V' : ' ';
122 s_cf
[4] = (flags
& INPUT_COMPLETE_USERNAMES
) != 0 ? 'U' : ' ';
123 s_cf
[5] = (flags
& INPUT_COMPLETE_CD
) != 0 ? 'D' : ' ';
124 s_cf
[6] = (flags
& INPUT_COMPLETE_SHELL_ESC
) != 0 ? 'S' : ' ';
128 #endif /* DO_CMPLETION_DEBUG */
130 /* --------------------------------------------------------------------------------------------- */
133 filename_completion_function (const char *text
, int state
, input_complete_t flags
)
135 static DIR *directory
= NULL
;
136 static char *filename
= NULL
;
137 static char *dirname
= NULL
;
138 static char *users_dirname
= NULL
;
139 static size_t filename_len
= 0;
140 static vfs_path_t
*dirname_vpath
= NULL
;
142 gboolean isdir
= TRUE
, isexec
= FALSE
;
143 struct vfs_dirent
*entry
= NULL
;
145 SHOW_C_CTX ("filename_completion_function");
147 if (text
!= NULL
&& (flags
& INPUT_COMPLETE_SHELL_ESC
) != 0)
153 u_text
= str_shell_unescape (text
);
155 result
= filename_completion_function (u_text
, state
, flags
& (~INPUT_COMPLETE_SHELL_ESC
));
158 e_result
= str_shell_escape (result
);
164 /* If we're starting the match process, initialize us a bit. */
171 g_free (users_dirname
);
172 vfs_path_free (dirname_vpath
, TRUE
);
174 if ((*text
!= '\0') && (temp
= strrchr (text
, PATH_SEP
)) != NULL
)
176 filename
= g_strdup (++temp
);
177 dirname
= g_strndup (text
, temp
- text
);
181 dirname
= g_strdup (".");
182 filename
= g_strdup (text
);
185 /* We aren't done yet. We also support the "~user" syntax. */
187 /* Save the version of the directory that the user typed. */
188 users_dirname
= dirname
;
189 dirname
= tilde_expand (dirname
);
190 canonicalize_pathname (dirname
);
191 dirname_vpath
= vfs_path_from_str (dirname
);
193 /* Here we should do something with variable expansion
195 Maybe a dream - UNIMPLEMENTED yet. */
197 directory
= mc_opendir (dirname_vpath
);
198 filename_len
= strlen (filename
);
201 /* Now that we have some state, we can read the directory. */
203 while (directory
!= NULL
&& (entry
= mc_readdir (directory
)) != NULL
)
205 if (!str_is_valid_string (entry
->d_name
))
208 /* Special case for no filename.
209 All entries except "." and ".." match. */
210 if (filename_len
== 0)
212 if (DIR_IS_DOT (entry
->d_name
) || DIR_IS_DOTDOT (entry
->d_name
))
217 /* Otherwise, if these match up to the length of filename, then
218 it may be a match. */
219 if ((entry
->d_name
[0] != filename
[0]) ||
220 ((NLENGTH (entry
)) < filename_len
) ||
221 strncmp (filename
, entry
->d_name
, filename_len
) != 0)
229 struct stat tempstat
;
230 vfs_path_t
*tmp_vpath
;
232 tmp_vpath
= vfs_path_build_filename (dirname
, entry
->d_name
, (char *) NULL
);
235 if (mc_stat (tmp_vpath
, &tempstat
) == 0)
243 if (!S_ISDIR (tempstat
.st_mode
))
247 if ((my_uid
== 0 && (tempstat
.st_mode
& 0111) != 0) ||
248 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100) != 0) ||
249 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010) != 0) ||
250 (tempstat
.st_mode
& 0001) != 0)
256 /* stat failed, strange. not a dir in any case */
259 vfs_path_free (tmp_vpath
, TRUE
);
262 if ((flags
& INPUT_COMPLETE_COMMANDS
) != 0 && (isexec
|| isdir
))
264 if ((flags
& INPUT_COMPLETE_CD
) != 0 && isdir
)
266 if ((flags
& INPUT_COMPLETE_FILENAMES
) != 0)
272 if (directory
!= NULL
)
274 mc_closedir (directory
);
277 MC_PTR_FREE (dirname
);
278 vfs_path_free (dirname_vpath
, TRUE
);
279 dirname_vpath
= NULL
;
280 MC_PTR_FREE (filename
);
281 MC_PTR_FREE (users_dirname
);
288 temp
= g_string_sized_new (16);
290 if (users_dirname
!= NULL
&& (users_dirname
[0] != '.' || users_dirname
[1] != '\0'))
292 g_string_append (temp
, users_dirname
);
294 /* We need a '/' at the end. */
295 if (!IS_PATH_SEP (temp
->str
[temp
->len
- 1]))
296 g_string_append_c (temp
, PATH_SEP
);
298 g_string_append (temp
, entry
->d_name
);
300 g_string_append_c (temp
, PATH_SEP
);
302 return g_string_free (temp
, FALSE
);
306 /* --------------------------------------------------------------------------------------------- */
307 /** We assume here that text[0] == '~' , if you want to call it in another way,
308 you have to change the code */
311 username_completion_function (const char *text
, int state
, input_complete_t flags
)
313 static struct passwd
*entry
= NULL
;
314 static size_t userlen
= 0;
317 SHOW_C_CTX ("username_completion_function");
319 if (text
[0] == '\\' && text
[1] == '~')
322 { /* Initialization stuff */
324 userlen
= strlen (text
+ 1);
327 while ((entry
= getpwent ()) != NULL
)
329 /* Null usernames should result in all users as possible completions. */
332 if (text
[1] == entry
->pw_name
[0] && strncmp (text
+ 1, entry
->pw_name
, userlen
) == 0)
337 return g_strconcat ("~", entry
->pw_name
, PATH_SEP_STR
, (char *) NULL
);
343 /* --------------------------------------------------------------------------------------------- */
344 /** We assume text [0] == '$' and want to have a look at text [1], if it is
345 equal to '{', so that we should append '}' at the end */
348 variable_completion_function (const char *text
, int state
, input_complete_t flags
)
350 static char **env_p
= NULL
;
351 static gboolean isbrace
= FALSE
;
352 static size_t varlen
= 0;
353 const char *p
= NULL
;
356 SHOW_C_CTX ("variable_completion_function");
359 { /* Initialization stuff */
360 isbrace
= (text
[1] == '{');
361 varlen
= strlen (text
+ 1 + isbrace
);
365 while (*env_p
!= NULL
)
367 p
= strchr (*env_p
, '=');
368 if (p
!= NULL
&& ((size_t) (p
- *env_p
) >= varlen
)
369 && strncmp (text
+ 1 + isbrace
, *env_p
, varlen
) == 0)
380 temp
= g_string_new_len (*env_p
, p
- *env_p
);
384 g_string_prepend_c (temp
, '{');
385 g_string_append_c (temp
, '}');
387 g_string_prepend_c (temp
, '$');
391 return g_string_free (temp
, FALSE
);
395 /* --------------------------------------------------------------------------------------------- */
398 fetch_hosts (const char *filename
)
406 file
= fopen (filename
, "r");
410 while (fgets (buffer
, sizeof (buffer
) - 1, file
) != NULL
)
412 /* Skip to first character. */
413 for (bi
= buffer
; bi
[0] != '\0' && str_isspace (bi
); str_next_char (&bi
))
416 /* Ignore comments... */
420 /* Handle $include. */
421 if (strncmp (bi
, "$include ", 9) == 0)
423 char *includefile
, *t
;
425 /* Find start of filename. */
426 includefile
= bi
+ 9;
427 while (*includefile
!= '\0' && whitespace (*includefile
))
431 /* Find end of filename. */
432 while (t
[0] != '\0' && !str_isspace (t
))
436 fetch_hosts (includefile
);
441 while (bi
[0] != '\0' && !str_isspace (bi
))
444 /* Get the host names separated by white space. */
445 while (bi
[0] != '\0' && bi
[0] != '#')
447 while (bi
[0] != '\0' && str_isspace (bi
))
451 for (lc_start
= bi
; bi
[0] != '\0' && !str_isspace (bi
); str_next_char (&bi
))
457 name
= g_strndup (lc_start
, bi
- lc_start
);
465 if (j
>= hosts_alloclen
)
467 hosts_alloclen
+= 30;
468 hosts
= g_renew (char *, hosts
, hosts_alloclen
+ 1);
472 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
473 if (strcmp (name
, *host_p
) == 0)
474 break; /* We do not want any duplicates */
476 if (host_p
== hosts_p
)
490 /* --------------------------------------------------------------------------------------------- */
493 hostname_completion_function (const char *text
, int state
, input_complete_t flags
)
495 static char **host_p
= NULL
;
496 static size_t textstart
= 0;
497 static size_t textlen
= 0;
500 SHOW_C_CTX ("hostname_completion_function");
503 { /* Initialization stuff */
508 hosts
= g_new (char *, hosts_alloclen
+ 1);
511 p
= getenv ("HOSTFILE");
512 fetch_hosts (p
!= NULL
? p
: "/etc/hosts");
514 textstart
= (*text
== '@') ? 1 : 0;
515 textlen
= strlen (text
+ textstart
);
518 for (; *host_p
!= NULL
; host_p
++)
521 break; /* Match all of them */
522 if (strncmp (text
+ textstart
, *host_p
, textlen
) == 0)
536 temp
= g_string_sized_new (8);
539 g_string_append_c (temp
, '@');
540 g_string_append (temp
, *host_p
);
543 return g_string_free (temp
, FALSE
);
547 /* --------------------------------------------------------------------------------------------- */
549 * This is the function to call when the word to complete is in a position
550 * where a command word can be found. It looks around $PATH, looking for
551 * commands that match. It also scans aliases, function names, and the
552 * table of shell built-ins.
556 command_completion_function (const char *text
, int state
, input_complete_t flags
)
558 static const char *path_end
= NULL
;
559 static gboolean isabsolute
= FALSE
;
560 static int phase
= 0;
561 static size_t text_len
= 0;
562 static const char *const *words
= NULL
;
563 static char *path
= NULL
;
564 static char *cur_path
= NULL
;
565 static char *cur_word
= NULL
;
566 static int init_state
= 0;
567 static const char *const bash_reserved
[] = {
568 "if", "then", "else", "elif", "fi", "case", "esac", "for",
569 "select", "while", "until", "do", "done", "in", "function", 0
571 static const char *const bash_builtins
[] = {
572 "alias", "bg", "bind", "break", "builtin", "cd", "command",
573 "continue", "declare", "dirs", "echo", "enable", "eval",
574 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
575 "help", "history", "jobs", "kill", "let", "local", "logout",
576 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
577 "shift", "source", "suspend", "test", "times", "trap", "type",
578 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
584 SHOW_C_CTX ("command_completion_function");
586 if ((flags
& INPUT_COMPLETE_COMMANDS
) == 0)
589 u_text
= str_shell_unescape (text
);
590 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
593 { /* Initialize us a little bit */
594 isabsolute
= strchr (u_text
, PATH_SEP
) != NULL
;
597 words
= bash_reserved
;
599 text_len
= strlen (u_text
);
603 path
= g_strdup (getenv ("PATH"));
607 path_end
= strchr (p
, '\0');
608 while ((p
= strchr (p
, PATH_ENV_SEP
)) != NULL
)
617 p
= filename_completion_function (u_text
, state
, flags
);
623 p
= str_shell_escape (p
);
634 case 0: /* Reserved words */
635 for (; *words
!= NULL
; words
++)
636 if (strncmp (*words
, u_text
, text_len
) == 0)
639 return g_strdup (*(words
++));
642 words
= bash_builtins
;
644 case 1: /* Builtin commands */
645 for (; *words
!= NULL
; words
++)
646 if (strncmp (*words
, u_text
, text_len
) == 0)
649 return g_strdup (*(words
++));
657 case 2: /* And looking through the $PATH */
658 while (found
== NULL
)
660 if (cur_word
== NULL
)
664 if (cur_path
>= path_end
)
666 expanded
= tilde_expand (*cur_path
!= '\0' ? cur_path
: ".");
667 cur_word
= mc_build_filename (expanded
, u_text
, (char *) NULL
);
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
= str_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 (str_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
)
985 text_len
= strlen (text
);
986 buff_len
= str_length (in
->buffer
->str
);
988 size
= (ssize_t
) text_len
;
990 size
= MIN (size
, (ssize_t
) text_len
);
992 new_size
= size
+ start
- end
;
995 /* make a hole within buffer */
999 tail_len
= in
->buffer
->len
- end
;
1005 tail
= g_strndup (in
->buffer
->str
+ end
, tail_len
);
1007 hole_end
= end
+ new_size
;
1008 if (in
->buffer
->len
< hole_end
)
1009 g_string_set_size (in
->buffer
, hole_end
+ tail_len
);
1011 g_string_overwrite_len (in
->buffer
, hole_end
, tail
, tail_len
);
1017 g_string_overwrite_len (in
->buffer
, start
, text
, size
);
1019 in
->point
+= str_length (in
->buffer
->str
) - buff_len
;
1020 input_update (in
, TRUE
);
1023 return new_size
!= 0;
1026 /* --------------------------------------------------------------------------------------------- */
1029 complete_callback (Widget
* w
, Widget
* sender
, widget_msg_t msg
, int parm
, void *data
)
1033 WGroup
*g
= GROUP (w
);
1034 WDialog
*h
= DIALOG (w
);
1050 /* exit from completion list if input line is empty */
1056 /* Refill the list box and start again */
1057 else if (end
== min_end
)
1059 end
= str_get_prev_char (input
->buffer
->str
+ end
) - input
->buffer
->str
;
1060 input_handle_char (input
, parm
);
1061 h
->ret_value
= B_USER
;
1070 new_end
= str_get_prev_char (input
->buffer
->str
+ end
) - input
->buffer
->str
;
1072 for (i
= 0, e
= listbox_get_first_link (LISTBOX (g
->current
->data
));
1073 e
!= NULL
; i
++, e
= g_list_next (e
))
1075 WLEntry
*le
= LENTRY (e
->data
);
1077 if (strncmp (input
->buffer
->str
+ start
, le
->text
, new_end
- start
) == 0)
1079 listbox_set_current (LISTBOX (g
->current
->data
), i
);
1081 input_handle_char (input
, parm
);
1082 widget_draw (WIDGET (g
->current
->data
));
1090 if (parm
< 32 || parm
> 255)
1093 if (widget_lookup_key (WIDGET (input
), parm
) != CK_Complete
)
1094 return MSG_NOT_HANDLED
;
1099 /* This means we want to refill the list box and start again */
1100 h
->ret_value
= B_USER
;
1105 static char buff
[MB_LEN_MAX
] = "";
1108 int need_redraw
= 0;
1110 char *last_text
= NULL
;
1112 buff
[bl
++] = (char) parm
;
1115 switch (str_is_valid_char (buff
, bl
))
1126 for (i
= 0, e
= listbox_get_first_link (LISTBOX (g
->current
->data
));
1127 e
!= NULL
; i
++, e
= g_list_next (e
))
1129 WLEntry
*le
= LENTRY (e
->data
);
1131 if (strncmp (input
->buffer
->str
+ start
, le
->text
, end
- start
) == 0
1132 && strncmp (le
->text
+ end
- start
, buff
, bl
) == 0)
1134 if (need_redraw
== 0)
1137 listbox_set_current (LISTBOX (g
->current
->data
), i
);
1138 last_text
= le
->text
;
1146 /* count symbols between start and end */
1147 for (si
= le
->text
+ start
; si
< le
->text
+ end
;
1148 str_next_char (&si
), si_num
++)
1150 for (sl
= last_text
+ start
; sl
< last_text
+ end
;
1151 str_next_char (&sl
), sl_num
++)
1154 /* pointers to next symbols */
1155 si
= &le
->text
[str_offset_to_pos (le
->text
, ++si_num
)];
1156 sl
= &last_text
[str_offset_to_pos (last_text
, ++sl_num
)];
1158 while (si
[0] != '\0' && sl
[0] != '\0')
1160 char *nexti
, *nextl
;
1162 nexti
= str_get_next_char (si
);
1163 nextl
= str_get_next_char (sl
);
1165 if (nexti
- si
!= nextl
- sl
|| strncmp (si
, sl
, nexti
- si
) != 0)
1174 last_text
= le
->text
;
1176 si
= &last_text
[str_offset_to_pos (last_text
, si_num
)];
1177 if (low
> si
- last_text
)
1178 low
= si
- last_text
;
1185 if (need_redraw
== 2)
1187 insert_text (input
, last_text
, low
);
1188 widget_draw (WIDGET (g
->current
->data
));
1190 else if (need_redraw
== 1)
1192 h
->ret_value
= B_ENTER
;
1201 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
1205 /* --------------------------------------------------------------------------------------------- */
1207 /** Returns TRUE if the user would like to see us again */
1209 complete_engine (WInput
* in
, int what_to_do
)
1211 if (in
->completions
!= NULL
&& str_offset_to_pos (in
->buffer
->str
, in
->point
) != end
)
1212 input_complete_free (in
);
1214 if (in
->completions
== NULL
)
1215 complete_engine_fill_completions (in
);
1217 if (in
->completions
== NULL
)
1221 if ((what_to_do
& DO_INSERTION
) != 0
1222 || ((what_to_do
& DO_QUERY
) != 0 && in
->completions
[1] == NULL
))
1224 char *lc_complete
= in
->completions
[0];
1226 if (!insert_text (in
, lc_complete
, -1) || in
->completions
[1] != NULL
)
1229 input_complete_free (in
);
1232 if ((what_to_do
& DO_QUERY
) != 0 && in
->completions
!= NULL
&& in
->completions
[1] != NULL
)
1234 int maxlen
= 0, count
= 0, i
;
1236 int start_x
, start_y
;
1238 WDialog
*complete_dlg
;
1239 WListbox
*complete_list
;
1241 for (p
= in
->completions
+ 1; *p
!= NULL
; count
++, p
++)
1243 i
= str_term_width1 (*p
);
1248 start_x
= WIDGET (in
)->rect
.x
;
1249 start_y
= WIDGET (in
)->rect
.y
;
1250 if (start_y
- 2 >= count
)
1252 y
= start_y
- 2 - count
;
1255 else if (start_y
>= LINES
- start_y
- 1)
1263 h
= LINES
- start_y
- 1;
1265 x
= start
- in
->term_first_shown
- 2 + start_x
;
1278 dlg_create (TRUE
, y
, x
, h
, w
, WPOS_KEEP_DEFAULT
, TRUE
,
1279 dialog_colors
, complete_callback
, NULL
, "[Completion]", NULL
);
1280 complete_list
= listbox_new (1, 1, h
- 2, w
- 2, FALSE
, NULL
);
1281 group_add_widget (GROUP (complete_dlg
), complete_list
);
1283 for (p
= in
->completions
+ 1; *p
!= NULL
; p
++)
1284 listbox_add_item (complete_list
, LISTBOX_APPEND_AT_END
, 0, *p
, NULL
, FALSE
);
1286 i
= dlg_run (complete_dlg
);
1290 listbox_get_current (complete_list
, &q
, NULL
);
1292 insert_text (in
, q
, -1);
1294 if (q
!= NULL
|| end
!= min_end
)
1295 input_complete_free (in
);
1296 widget_destroy (WIDGET (complete_dlg
));
1298 /* B_USER if user wants to start over again */
1299 return (i
== B_USER
);
1306 /* --------------------------------------------------------------------------------------------- */
1307 /*** public functions ****************************************************************************/
1308 /* --------------------------------------------------------------------------------------------- */
1310 /** Returns an array of matches, or NULL if none. */
1312 try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
)
1314 try_complete_automation_state_t state
;
1315 char **matches
= NULL
;
1317 memset (&state
, 0, sizeof (state
));
1318 state
.flags
= flags
;
1320 SHOW_C_CTX ("try_complete");
1321 state
.word
= g_strndup (text
+ *lc_start
, *lc_end
- *lc_start
);
1323 state
.is_cd
= check_is_cd (text
, *lc_start
, state
.flags
);
1325 /* Determine if this could be a command word. It is if it appears at
1326 the start of the line (ignoring preceding whitespace), or if it
1327 appears after a character that separates commands. And we have to
1328 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1329 if (!state
.is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
) != 0)
1330 try_complete_commands_prepare (&state
, text
, lc_start
);
1332 try_complete_find_start_sign (&state
);
1334 /* Command substitution? */
1335 if (state
.p
> state
.q
&& state
.p
> state
.r
)
1337 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1338 matches
= completion_matches (str_cget_next_char (state
.p
),
1339 command_completion_function
,
1340 state
.flags
& (~INPUT_COMPLETE_FILENAMES
));
1341 if (matches
!= NULL
)
1342 *lc_start
+= str_get_next_char (state
.p
) - state
.word
;
1345 /* Variable name? */
1346 else if (state
.q
> state
.p
&& state
.q
> state
.r
)
1348 SHOW_C_CTX ("try_complete:var_subst");
1349 matches
= completion_matches (state
.q
, variable_completion_function
, state
.flags
);
1350 if (matches
!= NULL
)
1351 *lc_start
+= state
.q
- state
.word
;
1354 /* Starts with '@', then look through the known hostnames for
1355 completion first. */
1356 else if (state
.r
> state
.p
&& state
.r
> state
.q
)
1358 SHOW_C_CTX ("try_complete:host_subst");
1359 matches
= completion_matches (state
.r
, hostname_completion_function
, state
.flags
);
1360 if (matches
!= NULL
)
1361 *lc_start
+= state
.r
- state
.word
;
1364 /* Starts with '~' and there is no slash in the word, then
1365 try completing this word as a username. */
1366 if (matches
== NULL
&& *state
.word
== '~' && (state
.flags
& INPUT_COMPLETE_USERNAMES
) != 0
1367 && strchr (state
.word
, PATH_SEP
) == NULL
)
1369 SHOW_C_CTX ("try_complete:user_subst");
1370 matches
= completion_matches (state
.word
, username_completion_function
, state
.flags
);
1373 /* If this word is in a command position, then
1374 complete over possible command names, including aliases, functions,
1375 and command names. */
1376 if (matches
== NULL
)
1377 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1379 /* And finally if nothing found, try complete directory name */
1380 if (matches
== NULL
)
1382 state
.in_command_position
= 0;
1383 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1386 g_free (state
.word
);
1388 if (matches
!= NULL
&&
1389 (flags
& (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
)) !=
1390 (INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_SHELL_ESC
))
1392 /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1395 for (m
= matches
; *m
!= NULL
; m
++)
1400 *m
= str_shell_escape (*m
);
1408 /* --------------------------------------------------------------------------------------------- */
1411 complete_engine_fill_completions (WInput
* in
)
1414 const char *word_separators
;
1416 word_separators
= (in
->completion_flags
& INPUT_COMPLETE_SHELL_ESC
) ? " \t;|<>" : "\t;|<>";
1418 end
= str_offset_to_pos (in
->buffer
->str
, in
->point
);
1420 s
= in
->buffer
->str
;
1423 /* get symbol before in->point */
1426 for (i
= in
->point
- 1; i
> 0; i
--)
1430 for (; s
>= in
->buffer
->str
; str_prev_char (&s
))
1432 start
= s
- in
->buffer
->str
;
1433 if (strchr (word_separators
, *s
) != NULL
&& !str_is_char_escaped (in
->buffer
->str
, s
))
1440 start
= s
- in
->buffer
->str
;
1443 in
->completions
= try_complete (in
->buffer
->str
, &start
, &end
, in
->completion_flags
);
1446 /* --------------------------------------------------------------------------------------------- */
1448 /* declared in lib/widget/input.h */
1450 input_complete (WInput
* in
)
1454 if (!str_is_valid_string (in
->buffer
->str
))
1457 if (in
->completions
!= NULL
)
1458 engine_flags
= DO_QUERY
;
1461 engine_flags
= DO_INSERTION
;
1463 if (mc_global
.widget
.show_all_if_ambiguous
)
1464 engine_flags
|= DO_QUERY
;
1467 while (complete_engine (in
, engine_flags
))
1471 /* --------------------------------------------------------------------------------------------- */
1474 input_complete_free (WInput
* in
)
1476 g_strfreev (in
->completions
);
1477 in
->completions
= NULL
;
1480 /* --------------------------------------------------------------------------------------------- */