2 Input line filename/username/hostname/variable/command completion.
3 (Let mc type for you...)
5 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
7 the Free Software Foundation, Inc.
11 Slava Zanko <slavazanko@gmail.com>, 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
40 #include <sys/types.h>
45 #include "lib/global.h"
47 #include "lib/tty/tty.h"
48 #include "lib/tty/key.h" /* XCTRL and ALT macros */
49 #include "lib/vfs/vfs.h"
50 #include "lib/strescape.h"
51 #include "lib/strutil.h"
53 #include "lib/widget.h"
55 #include "input_complete.h"
57 /*** global variables ****************************************************************************/
59 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
60 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
61 extern char **environ
;
64 /*** file scope macro definitions ****************************************************************/
66 /* #define DO_COMPLETION_DEBUG */
67 #ifdef DO_COMPLETION_DEBUG
68 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
70 #define SHOW_C_CTX(func)
71 #endif /* DO_CMPLETION_DEBUG */
73 #define whitespace(c) ((c) == ' ' || (c) == '\t')
74 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
76 #define DO_INSERTION 1
79 /*** file scope type declarations ****************************************************************/
81 typedef char *CompletionFunction (const char *text
, int state
, input_complete_t flags
);
85 size_t in_command_position
;
91 input_complete_t flags
;
92 } try_complete_automation_state_t
;
94 /*** file scope variables ************************************************************************/
96 static char **hosts
= NULL
;
97 static char **hosts_p
= NULL
;
98 static int hosts_alloclen
= 0;
100 static int query_height
, query_width
;
101 static WInput
*input
;
103 static int start
= 0;
106 /*** file scope functions ************************************************************************/
107 /* --------------------------------------------------------------------------------------------- */
109 char **try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
);
110 void complete_engine_fill_completions (WInput
* in
);
112 #ifdef DO_COMPLETION_DEBUG
114 * Useful to print/debug completion flags
117 show_c_flags (input_complete_t flags
)
119 static char s_cf
[] = "FHCVUDS";
121 s_cf
[0] = (flags
& INPUT_COMPLETE_FILENAMES
) ? 'F' : ' ';
122 s_cf
[1] = (flags
& INPUT_COMPLETE_HOSTNAMES
) ? 'H' : ' ';
123 s_cf
[2] = (flags
& INPUT_COMPLETE_COMMANDS
) ? 'C' : ' ';
124 s_cf
[3] = (flags
& INPUT_COMPLETE_VARIABLES
) ? 'V' : ' ';
125 s_cf
[4] = (flags
& INPUT_COMPLETE_USERNAMES
) ? 'U' : ' ';
126 s_cf
[5] = (flags
& INPUT_COMPLETE_CD
) ? 'D' : ' ';
127 s_cf
[6] = (flags
& INPUT_COMPLETE_SHELL_ESC
) ? 'S' : ' ';
131 #endif /* DO_CMPLETION_DEBUG */
133 /* --------------------------------------------------------------------------------------------- */
136 filename_completion_function (const char *text
, int state
, input_complete_t flags
)
138 static DIR *directory
= NULL
;
139 static char *filename
= NULL
;
140 static char *dirname
= NULL
;
141 static char *users_dirname
= NULL
;
142 static size_t filename_len
;
143 int isdir
= 1, isexec
= 0;
144 static vfs_path_t
*dirname_vpath
= NULL
;
146 struct dirent
*entry
= NULL
;
148 SHOW_C_CTX ("filename_completion_function");
150 if (text
&& (flags
& INPUT_COMPLETE_SHELL_ESC
))
156 u_text
= strutils_shell_unescape (text
);
158 result
= filename_completion_function (u_text
, state
, flags
& (~INPUT_COMPLETE_SHELL_ESC
));
161 e_result
= strutils_shell_escape (result
);
167 /* If we're starting the match process, initialize us a bit. */
174 g_free (users_dirname
);
175 vfs_path_free (dirname_vpath
);
177 if ((*text
!= '\0') && (temp
= strrchr (text
, PATH_SEP
)) != NULL
)
179 filename
= g_strdup (++temp
);
180 dirname
= g_strndup (text
, temp
- text
);
184 dirname
= g_strdup (".");
185 filename
= g_strdup (text
);
188 /* We aren't done yet. We also support the "~user" syntax. */
190 /* Save the version of the directory that the user typed. */
191 users_dirname
= dirname
;
192 dirname
= tilde_expand (dirname
);
193 canonicalize_pathname (dirname
);
194 dirname_vpath
= vfs_path_from_str (dirname
);
196 /* Here we should do something with variable expansion
198 Maybe a dream - UNIMPLEMENTED yet. */
200 directory
= mc_opendir (dirname_vpath
);
201 filename_len
= strlen (filename
);
204 /* Now that we have some state, we can read the directory. */
206 while (directory
&& (entry
= mc_readdir (directory
)))
208 if (!str_is_valid_string (entry
->d_name
))
211 /* Special case for no filename.
212 All entries except "." and ".." match. */
213 if (filename_len
== 0)
215 if (!strcmp (entry
->d_name
, ".") || !strcmp (entry
->d_name
, ".."))
220 /* Otherwise, if these match up to the length of filename, then
221 it may be a match. */
222 if ((entry
->d_name
[0] != filename
[0]) ||
223 ((NLENGTH (entry
)) < filename_len
) ||
224 strncmp (filename
, entry
->d_name
, filename_len
))
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)
238 uid_t my_uid
= getuid ();
239 gid_t my_gid
= getgid ();
241 if (!S_ISDIR (tempstat
.st_mode
))
244 if ((!my_uid
&& (tempstat
.st_mode
& 0111)) ||
245 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100)) ||
246 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010)) ||
247 (tempstat
.st_mode
& 0001))
253 /* stat failed, strange. not a dir in any case */
256 vfs_path_free (tmp_vpath
);
258 if ((flags
& INPUT_COMPLETE_COMMANDS
) && (isexec
|| isdir
))
260 if ((flags
& INPUT_COMPLETE_CD
) && isdir
)
262 if (flags
& (INPUT_COMPLETE_FILENAMES
))
270 mc_closedir (directory
);
275 vfs_path_free (dirname_vpath
);
276 dirname_vpath
= NULL
;
279 g_free (users_dirname
);
280 users_dirname
= NULL
;
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 (temp
->str
[temp
->len
- 1] != PATH_SEP
)
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
;
313 static size_t userlen
;
316 SHOW_C_CTX ("username_completion_function");
318 if (text
[0] == '\\' && text
[1] == '~')
321 { /* Initialization stuff */
323 userlen
= strlen (text
+ 1);
325 while ((entry
= getpwent ()) != NULL
)
327 /* Null usernames should result in all users as possible completions. */
330 if (text
[1] == entry
->pw_name
[0] && !strncmp (text
+ 1, entry
->pw_name
, userlen
))
335 return g_strconcat ("~", entry
->pw_name
, PATH_SEP_STR
, (char *) NULL
);
341 /* --------------------------------------------------------------------------------------------- */
342 /** We assume text [0] == '$' and want to have a look at text [1], if it is
343 equal to '{', so that we should append '}' at the end */
346 variable_completion_function (const char *text
, int state
, input_complete_t flags
)
349 static unsigned int isbrace
;
350 static size_t varlen
;
351 const char *p
= NULL
;
354 SHOW_C_CTX ("variable_completion_function");
357 { /* Initialization stuff */
358 isbrace
= (text
[1] == '{') ? 1 : 0;
359 varlen
= strlen (text
+ 1 + isbrace
);
365 p
= strchr (*env_p
, '=');
366 if (p
&& ((size_t) (p
- *env_p
) >= varlen
) && !strncmp (text
+ 1 + isbrace
, *env_p
, varlen
))
377 temp
= g_string_new_len (*env_p
, p
- *env_p
);
381 g_string_prepend_c (temp
, '{');
382 g_string_append_c (temp
, '}');
384 g_string_prepend_c (temp
, '$');
388 return g_string_free (temp
, FALSE
);
392 /* --------------------------------------------------------------------------------------------- */
395 fetch_hosts (const char *filename
)
397 FILE *file
= fopen (filename
, "r");
398 char buffer
[256], *name
;
405 while (fgets (buffer
, 255, file
) != NULL
)
407 /* Skip to first character. */
408 for (bi
= buffer
; bi
[0] != '\0' && str_isspace (bi
); str_next_char (&bi
));
410 /* Ignore comments... */
413 /* Handle $include. */
414 if (!strncmp (bi
, "$include ", 9))
416 char *includefile
= bi
+ 9;
419 /* Find start of filename. */
420 while (*includefile
&& whitespace (*includefile
))
424 /* Find end of filename. */
425 while (t
[0] != '\0' && !str_isspace (t
))
429 fetch_hosts (includefile
);
434 while (bi
[0] != '\0' && !str_isspace (bi
))
437 /* Get the host names separated by white space. */
438 while (bi
[0] != '\0' && bi
[0] != '#')
440 while (bi
[0] != '\0' && str_isspace (bi
))
444 for (lc_start
= bi
; bi
[0] != '\0' && !str_isspace (bi
); str_next_char (&bi
));
446 if (bi
- lc_start
== 0)
449 name
= g_strndup (lc_start
, bi
- lc_start
);
453 if (hosts_p
- hosts
>= hosts_alloclen
)
458 hosts_alloclen
+= 30;
459 hosts
= g_renew (char *, hosts
, hosts_alloclen
+ 1);
462 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
463 if (!strcmp (name
, *host_p
))
464 break; /* We do not want any duplicates */
465 if (host_p
== hosts_p
)
478 /* --------------------------------------------------------------------------------------------- */
481 hostname_completion_function (const char *text
, int state
, input_complete_t flags
)
483 static char **host_p
;
484 static unsigned int textstart
;
485 static size_t textlen
;
488 SHOW_C_CTX ("hostname_completion_function");
491 { /* Initialization stuff */
496 hosts
= g_new (char *, hosts_alloclen
+ 1);
499 p
= getenv ("HOSTFILE");
500 fetch_hosts (p
!= NULL
? p
: "/etc/hosts");
502 textstart
= (*text
== '@') ? 1 : 0;
503 textlen
= strlen (text
+ textstart
);
506 for (; *host_p
!= NULL
; host_p
++)
509 break; /* Match all of them */
510 if (strncmp (text
+ textstart
, *host_p
, textlen
) == 0)
524 temp
= g_string_sized_new (8);
527 g_string_append_c (temp
, '@');
528 g_string_append (temp
, *host_p
);
531 return g_string_free (temp
, FALSE
);
535 /* --------------------------------------------------------------------------------------------- */
537 * This is the function to call when the word to complete is in a position
538 * where a command word can be found. It looks around $PATH, looking for
539 * commands that match. It also scans aliases, function names, and the
540 * table of shell built-ins.
544 command_completion_function (const char *_text
, int state
, input_complete_t flags
)
547 static const char *path_end
;
548 static gboolean isabsolute
;
550 static size_t text_len
;
551 static const char *const *words
;
553 static char *cur_path
;
554 static char *cur_word
;
555 static int init_state
;
556 static const char *const bash_reserved
[] = {
557 "if", "then", "else", "elif", "fi", "case", "esac", "for",
558 "select", "while", "until", "do", "done", "in", "function", 0
560 static const char *const bash_builtins
[] = {
561 "alias", "bg", "bind", "break", "builtin", "cd", "command",
562 "continue", "declare", "dirs", "echo", "enable", "eval",
563 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
564 "help", "history", "jobs", "kill", "let", "local", "logout",
565 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
566 "shift", "source", "suspend", "test", "times", "trap", "type",
567 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
571 SHOW_C_CTX ("command_completion_function");
573 if (!(flags
& INPUT_COMPLETE_COMMANDS
))
576 text
= strutils_shell_unescape (_text
);
577 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
580 { /* Initialize us a little bit */
581 isabsolute
= strchr (text
, PATH_SEP
) != NULL
;
584 words
= bash_reserved
;
586 text_len
= strlen (text
);
590 path
= g_strdup (getenv ("PATH"));
594 path_end
= strchr (p
, '\0');
595 while ((p
= strchr (p
, PATH_ENV_SEP
)) != NULL
)
606 p
= filename_completion_function (text
, state
, flags
);
612 p
= strutils_shell_escape (p
);
623 case 0: /* Reserved words */
624 for (; *words
!= NULL
; words
++)
625 if (strncmp (*words
, text
, text_len
) == 0)
628 return g_strdup (*(words
++));
631 words
= bash_builtins
;
632 case 1: /* Builtin commands */
633 for (; *words
!= NULL
; words
++)
634 if (strncmp (*words
, text
, text_len
) == 0)
637 return g_strdup (*(words
++));
644 case 2: /* And looking through the $PATH */
651 if (cur_path
>= path_end
)
653 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
654 cur_word
= mc_build_filename (expanded
, text
, NULL
);
656 canonicalize_pathname (cur_word
);
657 cur_path
= strchr (cur_path
, 0) + 1;
660 found
= filename_completion_function (cur_word
, state
- init_state
, flags
);
676 p
= strrchr (found
, PATH_SEP
);
680 found
= strutils_shell_escape (p
+ 1);
689 /* --------------------------------------------------------------------------------------------- */
692 match_compare (const void *a
, const void *b
)
694 return strcmp (*(char **) a
, *(char **) b
);
697 /* --------------------------------------------------------------------------------------------- */
698 /** Returns an array of char * matches with the longest common denominator
699 in the 1st entry. Then a NULL terminated list of different possible
701 You have to supply your own CompletionFunction with the word you
702 want to complete as the first argument and an count of previous matches
704 In case no matches were found we return NULL. */
707 completion_matches (const char *text
, CompletionFunction entry_function
, input_complete_t flags
)
709 /* Number of slots in match_list. */
710 size_t match_list_size
= 30;
711 /* The list of matches. */
713 /* Number of matches actually found. */
716 /* Temporary string binder. */
719 match_list
= g_new (char *, match_list_size
+ 1);
720 match_list
[1] = NULL
;
722 while ((string
= (*entry_function
) (text
, matches
, flags
)) != NULL
)
724 if (matches
+ 1 == match_list_size
)
726 match_list_size
+= 30;
727 match_list
= (char **) g_renew (char *, match_list
, match_list_size
+ 1);
729 match_list
[++matches
] = string
;
730 match_list
[matches
+ 1] = NULL
;
733 /* If there were any matches, then look through them finding out the
734 lowest common denominator. That then becomes match_list[0]. */
737 register size_t i
= 1;
738 int low
= 4096; /* Count of max-matched characters. */
740 /* If only one match, just use that. */
743 match_list
[0] = match_list
[1];
744 match_list
[1] = NULL
;
750 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
752 /* And compare each member of the list with
753 the next, finding out where they stop matching.
754 If we find two equal strings, we have to put one away... */
757 while (j
< matches
+ 1)
762 for (si
= match_list
[i
], sj
= match_list
[j
]; si
[0] && sj
[0];)
765 ni
= str_get_next_char (si
);
766 nj
= str_get_next_char (sj
);
768 if (ni
- si
!= nj
- sj
)
770 if (strncmp (si
, sj
, ni
- si
) != 0)
777 if (si
[0] == '\0' && sj
[0] == '\0')
778 { /* Two equal strings */
779 g_free (match_list
[j
]);
783 continue; /* Look for a run of equal strings */
785 else if (low
> si
- match_list
[i
])
786 low
= si
- match_list
[i
];
787 if (i
+ 1 != j
) /* So there's some gap */
788 match_list
[i
+ 1] = match_list
[j
];
793 match_list
[matches
+ 1] = NULL
;
794 match_list
[0] = g_strndup (match_list
[1], low
);
798 { /* There were no matches. */
805 /* --------------------------------------------------------------------------------------------- */
806 /** Check if directory completion is needed */
808 check_is_cd (const char *text
, int lc_start
, input_complete_t flags
)
812 SHOW_C_CTX ("check_is_cd");
814 if ((flags
& INPUT_COMPLETE_CD
) == 0)
817 /* Skip initial spaces */
819 q
= (char *) text
+ lc_start
;
820 while (p
< q
&& p
[0] != '\0' && str_isspace (p
))
823 /* Check if the command is "cd" and the cursor is after it */
824 return (p
[0] == 'c' && p
[1] == 'd' && str_isspace (p
+ 2) && p
+ 2 < q
);
827 /* --------------------------------------------------------------------------------------------- */
830 try_complete_commands_prepare (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
832 const char *command_separator_chars
= ";|&{(`";
839 ti
= str_get_prev_char (&text
[*lc_start
]);
840 while (ti
> text
&& (ti
[0] == ' ' || ti
[0] == '\t'))
845 state
->in_command_position
++;
846 else if (strchr (command_separator_chars
, ti
[0]) != NULL
)
848 int this_char
, prev_char
;
850 state
->in_command_position
++;
854 /* Handle the two character tokens `>&', `<&', and `>|'.
855 We are not in a command position after one of these. */
857 prev_char
= str_get_prev_char (ti
)[0];
860 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>'))
861 || (this_char
== '|' && prev_char
== '>') || (ti
!= text
862 && str_get_prev_char (ti
)[0] == '\\'))
863 state
->in_command_position
= 0;
868 /* --------------------------------------------------------------------------------------------- */
871 try_complete_find_start_sign (try_complete_automation_state_t
* state
)
873 if (state
->flags
& INPUT_COMPLETE_COMMANDS
)
874 state
->p
= strrchr (state
->word
, '`');
875 if (state
->flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
877 state
->q
= strrchr (state
->word
, '$');
879 /* don't substitute variable in \$ case */
880 if (strutils_is_char_escaped (state
->word
, state
->q
))
884 qlen
= strlen (state
->q
);
886 memmove (state
->q
- 1, state
->q
, qlen
+ 1);
888 state
->flags
&= ~INPUT_COMPLETE_VARIABLES
;
892 if (state
->flags
& INPUT_COMPLETE_HOSTNAMES
)
893 state
->r
= strrchr (state
->word
, '@');
894 if (state
->q
&& state
->q
[1] == '(' && (state
->flags
& INPUT_COMPLETE_COMMANDS
))
896 if (state
->q
> state
->p
)
897 state
->p
= str_get_next_char (state
->q
);
902 /* --------------------------------------------------------------------------------------------- */
905 try_complete_all_possible (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
907 char **matches
= NULL
;
909 if (state
->in_command_position
!= 0)
911 SHOW_C_CTX ("try_complete:cmd_subst");
913 completion_matches (state
->word
, command_completion_function
,
914 state
->flags
& (~INPUT_COMPLETE_FILENAMES
));
916 else if ((state
->flags
& INPUT_COMPLETE_FILENAMES
) != 0)
919 state
->flags
&= ~(INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_COMMANDS
);
920 SHOW_C_CTX ("try_complete:filename_subst_1");
921 matches
= completion_matches (state
->word
, filename_completion_function
, state
->flags
);
923 if (matches
== NULL
&& state
->is_cd
&& *state
->word
!= PATH_SEP
&& *state
->word
!= '~')
925 state
->q
= text
+ *lc_start
;
926 for (state
->p
= text
;
927 *state
->p
&& state
->p
< state
->q
&& (*state
->p
== ' ' || *state
->p
== '\t');
928 str_next_char (&state
->p
))
930 if (!strncmp (state
->p
, "cd", 2))
932 *state
->p
&& state
->p
< state
->q
&& (*state
->p
== ' ' || *state
->p
== '\t');
933 str_next_char (&state
->p
))
935 if (state
->p
== state
->q
)
937 char *const cdpath_ref
= g_strdup (getenv ("CDPATH"));
938 char *cdpath
= cdpath_ref
;
945 while (!matches
&& c
== ':')
947 s
= strchr (cdpath
, ':');
949 s
= strchr (cdpath
, 0);
954 state
->r
= mc_build_filename (cdpath
, state
->word
, NULL
);
955 SHOW_C_CTX ("try_complete:filename_subst_2");
957 completion_matches (state
->r
, filename_completion_function
,
962 cdpath
= str_get_next_char (s
);
971 /* --------------------------------------------------------------------------------------------- */
974 insert_text (WInput
* in
, char *text
, ssize_t size
)
978 buff_len
= str_length (in
->buffer
);
979 size
= min (size
, (ssize_t
) strlen (text
)) + start
- end
;
980 if (strlen (in
->buffer
) + size
>= (size_t) in
->current_max_size
)
982 /* Expand the buffer */
985 narea
= g_try_realloc (in
->buffer
, in
->current_max_size
+ size
+ in
->field_width
);
989 in
->current_max_size
+= size
+ in
->field_width
;
992 if (strlen (in
->buffer
) + 1 < (size_t) in
->current_max_size
)
995 memmove (in
->buffer
+ end
+ size
, in
->buffer
+ end
, strlen (&in
->buffer
[end
]) + 1);
996 memmove (in
->buffer
+ start
, text
, size
- (start
- end
));
997 in
->point
+= str_length (in
->buffer
) - buff_len
;
998 input_update (in
, TRUE
);
1004 /* --------------------------------------------------------------------------------------------- */
1007 query_callback (Widget
* w
, Widget
* sender
, widget_msg_t msg
, int parm
, void *data
)
1009 static char buff
[MB_LEN_MAX
] = "";
1012 WDialog
*h
= DIALOG (w
);
1028 /* exit from completion list if input line is empty */
1034 /* Refill the list box and start again */
1035 else if (end
== min_end
)
1037 end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1038 input_handle_char (input
, parm
);
1039 h
->ret_value
= B_USER
;
1049 new_end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1051 for (i
= 0, e
= LISTBOX (h
->current
->data
)->list
;
1052 e
!= NULL
; i
++, e
= g_list_next (e
))
1054 WLEntry
*le
= LENTRY (e
->data
);
1056 if (strncmp (input
->buffer
+ start
, le
->text
, new_end
- start
) == 0)
1058 listbox_select_entry (LISTBOX (h
->current
->data
), i
);
1060 input_handle_char (input
, parm
);
1061 send_message (h
->current
->data
, NULL
, MSG_DRAW
, 0, NULL
);
1069 if (parm
< 32 || parm
> 255)
1072 if (input_key_is_in_map (input
, parm
) != 2)
1073 return MSG_NOT_HANDLED
;
1078 /* This means we want to refill the list box and start again */
1079 h
->ret_value
= B_USER
;
1087 int need_redraw
= 0;
1089 char *last_text
= NULL
;
1091 buff
[bl
++] = (char) parm
;
1093 switch (str_is_valid_char (buff
, bl
))
1102 for (i
= 0, e
= LISTBOX (h
->current
->data
)->list
;
1103 e
!= NULL
; i
++, e
= g_list_next (e
))
1105 WLEntry
*le
= LENTRY (e
->data
);
1107 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
) == 0
1108 && strncmp (&le
->text
[end
- start
], buff
, bl
) == 0)
1110 if (need_redraw
== 0)
1113 listbox_select_entry (LISTBOX (h
->current
->data
), i
);
1114 last_text
= le
->text
;
1122 /* count symbols between start and end */
1123 for (si
= le
->text
+ start
; si
< le
->text
+ end
;
1124 str_next_char (&si
), si_num
++)
1126 for (sl
= last_text
+ start
; sl
< last_text
+ end
;
1127 str_next_char (&sl
), sl_num
++)
1130 /* pointers to next symbols */
1131 si
= &le
->text
[str_offset_to_pos (le
->text
, ++si_num
)];
1132 sl
= &last_text
[str_offset_to_pos (last_text
, ++sl_num
)];
1134 while (si
[0] != '\0' && sl
[0] != '\0')
1136 char *nexti
, *nextl
;
1138 nexti
= str_get_next_char (si
);
1139 nextl
= str_get_next_char (sl
);
1141 if (nexti
- si
!= nextl
- sl
|| strncmp (si
, sl
, nexti
- si
) != 0)
1150 last_text
= le
->text
;
1152 si
= &last_text
[str_offset_to_pos (last_text
, si_num
)];
1153 if (low
> si
- last_text
)
1154 low
= si
- last_text
;
1161 if (need_redraw
== 2)
1163 insert_text (input
, last_text
, low
);
1164 send_message (h
->current
->data
, NULL
, MSG_DRAW
, 0, NULL
);
1166 else if (need_redraw
== 1)
1168 h
->ret_value
= B_ENTER
;
1178 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
1182 /* --------------------------------------------------------------------------------------------- */
1184 /** Returns 1 if the user would like to see us again */
1186 complete_engine (WInput
* in
, int what_to_do
)
1188 if (in
->completions
!= NULL
&& str_offset_to_pos (in
->buffer
, in
->point
) != end
)
1189 input_free_completions (in
);
1191 if (in
->completions
== NULL
)
1192 complete_engine_fill_completions (in
);
1194 if (in
->completions
!= NULL
)
1196 if (what_to_do
& DO_INSERTION
|| ((what_to_do
& DO_QUERY
) && !in
->completions
[1]))
1198 char *lc_complete
= in
->completions
[0];
1199 if (insert_text (in
, lc_complete
, strlen (lc_complete
)))
1201 if (in
->completions
[1])
1204 input_free_completions (in
);
1209 if ((what_to_do
& DO_QUERY
) && in
->completions
&& in
->completions
[1])
1211 int maxlen
= 0, i
, count
= 0;
1213 int start_x
, start_y
;
1216 WListbox
*query_list
;
1218 for (p
= in
->completions
+ 1; *p
!= NULL
; count
++, p
++)
1220 i
= str_term_width1 (*p
);
1224 start_x
= WIDGET (in
)->x
;
1225 start_y
= WIDGET (in
)->y
;
1226 if (start_y
- 2 >= count
)
1228 y
= start_y
- 2 - count
;
1233 if (start_y
>= LINES
- start_y
- 1)
1241 h
= LINES
- start_y
- 1;
1244 x
= start
- in
->term_first_shown
- 2 + start_x
;
1256 query_dlg
= create_dlg (TRUE
, y
, x
, query_height
, query_width
,
1257 dialog_colors
, query_callback
, NULL
,
1258 "[Completion]", NULL
, DLG_COMPACT
);
1259 query_list
= listbox_new (1, 1, h
- 2, w
- 2, FALSE
, NULL
);
1260 add_widget (query_dlg
, query_list
);
1261 for (p
= in
->completions
+ 1; *p
; p
++)
1262 listbox_add_item (query_list
, LISTBOX_APPEND_AT_END
, 0, *p
, NULL
);
1263 run_dlg (query_dlg
);
1265 if (query_dlg
->ret_value
== B_ENTER
)
1267 listbox_get_current (query_list
, &q
, NULL
);
1269 insert_text (in
, q
, strlen (q
));
1271 if (q
|| end
!= min_end
)
1272 input_free_completions (in
);
1273 i
= query_dlg
->ret_value
; /* B_USER if user wants to start over again */
1274 destroy_dlg (query_dlg
);
1284 /* --------------------------------------------------------------------------------------------- */
1285 /*** public functions ****************************************************************************/
1286 /* --------------------------------------------------------------------------------------------- */
1288 /** Returns an array of matches, or NULL if none. */
1290 try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
)
1292 try_complete_automation_state_t state
;
1293 char **matches
= NULL
;
1295 memset (&state
, 0, sizeof (try_complete_automation_state_t
));
1296 state
.flags
= flags
;
1298 SHOW_C_CTX ("try_complete");
1299 state
.word
= g_strndup (text
+ *lc_start
, *lc_end
- *lc_start
);
1301 state
.is_cd
= check_is_cd (text
, *lc_start
, state
.flags
);
1303 /* Determine if this could be a command word. It is if it appears at
1304 the start of the line (ignoring preceding whitespace), or if it
1305 appears after a character that separates commands. And we have to
1306 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1307 if (!state
.is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
))
1308 try_complete_commands_prepare (&state
, text
, lc_start
);
1310 try_complete_find_start_sign (&state
);
1312 /* Command substitution? */
1313 if (state
.p
> state
.q
&& state
.p
> state
.r
)
1315 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1316 matches
= completion_matches (str_cget_next_char (state
.p
),
1317 command_completion_function
,
1318 state
.flags
& (~INPUT_COMPLETE_FILENAMES
));
1320 *lc_start
+= str_get_next_char (state
.p
) - state
.word
;
1323 /* Variable name? */
1324 else if (state
.q
> state
.p
&& state
.q
> state
.r
)
1326 SHOW_C_CTX ("try_complete:var_subst");
1327 matches
= completion_matches (state
.q
, variable_completion_function
, state
.flags
);
1329 *lc_start
+= state
.q
- state
.word
;
1332 /* Starts with '@', then look through the known hostnames for
1333 completion first. */
1334 else if (state
.r
> state
.p
&& state
.r
> state
.q
)
1336 SHOW_C_CTX ("try_complete:host_subst");
1337 matches
= completion_matches (state
.r
, hostname_completion_function
, state
.flags
);
1339 *lc_start
+= state
.r
- state
.word
;
1342 /* Starts with `~' and there is no slash in the word, then
1343 try completing this word as a username. */
1344 if (!matches
&& *state
.word
== '~' && (state
.flags
& INPUT_COMPLETE_USERNAMES
)
1345 && !strchr (state
.word
, PATH_SEP
))
1347 SHOW_C_CTX ("try_complete:user_subst");
1348 matches
= completion_matches (state
.word
, username_completion_function
, state
.flags
);
1351 /* And finally if this word is in a command position, then
1352 complete over possible command names, including aliases, functions,
1353 and command names. */
1354 if (matches
== NULL
)
1355 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1357 g_free (state
.word
);
1362 /* --------------------------------------------------------------------------------------------- */
1365 complete_engine_fill_completions (WInput
* in
)
1368 const char *word_separators
;
1370 word_separators
= (in
->completion_flags
& INPUT_COMPLETE_SHELL_ESC
) ? " \t;|<>" : "\t;|<>";
1372 end
= str_offset_to_pos (in
->buffer
, in
->point
);
1377 /* get symbol before in->point */
1380 for (i
= in
->point
- 1; i
> 0; i
--)
1384 for (; s
>= in
->buffer
; str_prev_char (&s
))
1386 start
= s
- in
->buffer
;
1387 if (strchr (word_separators
, *s
) != NULL
&& !strutils_is_char_escaped (in
->buffer
, s
))
1394 start
= s
- in
->buffer
;
1397 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1400 /* --------------------------------------------------------------------------------------------- */
1402 /* declared in lib/widget/input.h */
1404 complete (WInput
* in
)
1408 if (!str_is_valid_string (in
->buffer
))
1411 if (in
->completions
!= NULL
)
1412 engine_flags
= DO_QUERY
;
1415 engine_flags
= DO_INSERTION
;
1417 if (mc_global
.widget
.show_all_if_ambiguous
)
1418 engine_flags
|= DO_QUERY
;
1421 while (complete_engine (in
, engine_flags
))
1425 /* --------------------------------------------------------------------------------------------- */