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
)
455 int j
= hosts_p
- hosts
;
458 g_realloc ((void *) hosts
, ((hosts_alloclen
+= 30) + 1) * sizeof (char *));
461 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
462 if (!strcmp (name
, *host_p
))
463 break; /* We do not want any duplicates */
464 if (host_p
== hosts_p
)
477 /* --------------------------------------------------------------------------------------------- */
480 hostname_completion_function (const char *text
, int state
, input_complete_t flags
)
482 static char **host_p
;
483 static unsigned int textstart
;
484 static size_t textlen
;
487 SHOW_C_CTX ("hostname_completion_function");
490 { /* Initialization stuff */
495 for (host_p
= hosts
; *host_p
; host_p
++)
499 hosts
= g_new (char *, (hosts_alloclen
= 30) + 1);
502 fetch_hosts ((p
= getenv ("HOSTFILE")) ? p
: "/etc/hosts");
504 textstart
= (*text
== '@') ? 1 : 0;
505 textlen
= strlen (text
+ textstart
);
511 break; /* Match all of them */
512 else if (!strncmp (text
+ textstart
, *host_p
, textlen
))
519 for (host_p
= hosts
; *host_p
; host_p
++)
529 temp
= g_string_sized_new (8);
532 g_string_append_c (temp
, '@');
533 g_string_append (temp
, *host_p
);
536 return g_string_free (temp
, FALSE
);
540 /* --------------------------------------------------------------------------------------------- */
542 * This is the function to call when the word to complete is in a position
543 * where a command word can be found. It looks around $PATH, looking for
544 * commands that match. It also scans aliases, function names, and the
545 * table of shell built-ins.
549 command_completion_function (const char *_text
, int state
, input_complete_t flags
)
552 static const char *path_end
;
553 static gboolean isabsolute
;
555 static size_t text_len
;
556 static const char *const *words
;
558 static char *cur_path
;
559 static char *cur_word
;
560 static int init_state
;
561 static const char *const bash_reserved
[] = {
562 "if", "then", "else", "elif", "fi", "case", "esac", "for",
563 "select", "while", "until", "do", "done", "in", "function", 0
565 static const char *const bash_builtins
[] = {
566 "alias", "bg", "bind", "break", "builtin", "cd", "command",
567 "continue", "declare", "dirs", "echo", "enable", "eval",
568 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
569 "help", "history", "jobs", "kill", "let", "local", "logout",
570 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
571 "shift", "source", "suspend", "test", "times", "trap", "type",
572 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
576 SHOW_C_CTX ("command_completion_function");
578 if (!(flags
& INPUT_COMPLETE_COMMANDS
))
581 text
= strutils_shell_unescape (_text
);
582 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
585 { /* Initialize us a little bit */
586 isabsolute
= strchr (text
, PATH_SEP
) != NULL
;
589 words
= bash_reserved
;
591 text_len
= strlen (text
);
595 path
= g_strdup (getenv ("PATH"));
599 path_end
= strchr (p
, '\0');
600 while ((p
= strchr (p
, PATH_ENV_SEP
)) != NULL
)
611 p
= filename_completion_function (text
, state
, flags
);
616 p
= strutils_shell_escape (p
);
627 case 0: /* Reserved words */
630 if (strncmp (*words
, text
, text_len
) == 0)
633 return g_strdup (*(words
++));
638 words
= bash_builtins
;
639 case 1: /* Builtin commands */
642 if (strncmp (*words
, text
, text_len
) == 0)
645 return g_strdup (*(words
++));
654 case 2: /* And looking through the $PATH */
661 if (cur_path
>= path_end
)
663 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
664 cur_word
= mc_build_filename (expanded
, text
, NULL
);
666 canonicalize_pathname (cur_word
);
667 cur_path
= strchr (cur_path
, 0) + 1;
670 found
= filename_completion_function (cur_word
, state
- init_state
, flags
);
686 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 **) a
, *(char **) 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. */
722 /* The list of matches. */
723 char **match_list
= g_new (char *, (match_list_size
= 30) + 1);
725 /* Number of matches actually found. */
728 /* Temporary string binder. */
731 match_list
[1] = NULL
;
733 while ((string
= (*entry_function
) (text
, matches
, flags
)) != NULL
)
735 if (matches
+ 1 == match_list_size
)
737 (char **) g_realloc (match_list
, ((match_list_size
+= 30) + 1) * sizeof (char *));
738 match_list
[++matches
] = string
;
739 match_list
[matches
+ 1] = NULL
;
742 /* If there were any matches, then look through them finding out the
743 lowest common denominator. That then becomes match_list[0]. */
747 int low
= 4096; /* Count of max-matched characters. */
749 /* If only one match, just use that. */
752 match_list
[0] = match_list
[1];
753 match_list
[1] = NULL
;
759 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
761 /* And compare each member of the list with
762 the next, finding out where they stop matching.
763 If we find two equal strings, we have to put one away... */
766 while (j
< matches
+ 1)
771 for (si
= match_list
[i
], sj
= match_list
[j
]; si
[0] && sj
[0];)
774 ni
= str_get_next_char (si
);
775 nj
= str_get_next_char (sj
);
777 if (ni
- si
!= nj
- sj
)
779 if (strncmp (si
, sj
, ni
- si
) != 0)
786 if (si
[0] == '\0' && sj
[0] == '\0')
787 { /* Two equal strings */
788 g_free (match_list
[j
]);
792 continue; /* Look for a run of equal strings */
794 else if (low
> si
- match_list
[i
])
795 low
= si
- match_list
[i
];
796 if (i
+ 1 != j
) /* So there's some gap */
797 match_list
[i
+ 1] = match_list
[j
];
802 match_list
[matches
+ 1] = NULL
;
803 match_list
[0] = g_strndup (match_list
[1], low
);
807 { /* There were no matches. */
814 /* --------------------------------------------------------------------------------------------- */
815 /** Check if directory completion is needed */
817 check_is_cd (const char *text
, int lc_start
, input_complete_t flags
)
821 SHOW_C_CTX ("check_is_cd");
823 if ((flags
& INPUT_COMPLETE_CD
) == 0)
826 /* Skip initial spaces */
828 q
= (char *) text
+ lc_start
;
829 while (p
< q
&& p
[0] != '\0' && str_isspace (p
))
832 /* Check if the command is "cd" and the cursor is after it */
833 return (p
[0] == 'c' && p
[1] == 'd' && str_isspace (p
+ 2) && p
+ 2 < q
);
836 /* --------------------------------------------------------------------------------------------- */
839 try_complete_commands_prepare (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
841 const char *command_separator_chars
= ";|&{(`";
848 ti
= str_get_prev_char (&text
[*lc_start
]);
849 while (ti
> text
&& (ti
[0] == ' ' || ti
[0] == '\t'))
854 state
->in_command_position
++;
855 else if (strchr (command_separator_chars
, ti
[0]) != NULL
)
857 int this_char
, prev_char
;
859 state
->in_command_position
++;
863 /* Handle the two character tokens `>&', `<&', and `>|'.
864 We are not in a command position after one of these. */
866 prev_char
= str_get_prev_char (ti
)[0];
869 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>'))
870 || (this_char
== '|' && prev_char
== '>') || (ti
!= text
871 && str_get_prev_char (ti
)[0] == '\\'))
872 state
->in_command_position
= 0;
877 /* --------------------------------------------------------------------------------------------- */
880 try_complete_find_start_sign (try_complete_automation_state_t
* state
)
882 if (state
->flags
& INPUT_COMPLETE_COMMANDS
)
883 state
->p
= strrchr (state
->word
, '`');
884 if (state
->flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
886 state
->q
= strrchr (state
->word
, '$');
888 /* don't substitute variable in \$ case */
889 if (strutils_is_char_escaped (state
->word
, state
->q
))
893 qlen
= strlen (state
->q
);
895 memmove (state
->q
- 1, state
->q
, qlen
+ 1);
897 state
->flags
&= ~INPUT_COMPLETE_VARIABLES
;
901 if (state
->flags
& INPUT_COMPLETE_HOSTNAMES
)
902 state
->r
= strrchr (state
->word
, '@');
903 if (state
->q
&& state
->q
[1] == '(' && (state
->flags
& INPUT_COMPLETE_COMMANDS
))
905 if (state
->q
> state
->p
)
906 state
->p
= str_get_next_char (state
->q
);
911 /* --------------------------------------------------------------------------------------------- */
914 try_complete_all_possible (try_complete_automation_state_t
* state
, char *text
, int *lc_start
)
916 char **matches
= NULL
;
918 if (state
->in_command_position
!= 0)
920 SHOW_C_CTX ("try_complete:cmd_subst");
922 completion_matches (state
->word
, command_completion_function
,
923 state
->flags
& (~INPUT_COMPLETE_FILENAMES
));
925 else if ((state
->flags
& INPUT_COMPLETE_FILENAMES
) != 0)
928 state
->flags
&= ~(INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_COMMANDS
);
929 SHOW_C_CTX ("try_complete:filename_subst_1");
930 matches
= completion_matches (state
->word
, filename_completion_function
, state
->flags
);
932 if (matches
== NULL
&& state
->is_cd
&& *state
->word
!= PATH_SEP
&& *state
->word
!= '~')
934 state
->q
= text
+ *lc_start
;
935 for (state
->p
= text
;
936 *state
->p
&& state
->p
< state
->q
&& (*state
->p
== ' ' || *state
->p
== '\t');
937 str_next_char (&state
->p
))
939 if (!strncmp (state
->p
, "cd", 2))
941 *state
->p
&& state
->p
< state
->q
&& (*state
->p
== ' ' || *state
->p
== '\t');
942 str_next_char (&state
->p
))
944 if (state
->p
== state
->q
)
946 char *const cdpath_ref
= g_strdup (getenv ("CDPATH"));
947 char *cdpath
= cdpath_ref
;
954 while (!matches
&& c
== ':')
956 s
= strchr (cdpath
, ':');
958 s
= strchr (cdpath
, 0);
963 state
->r
= mc_build_filename (cdpath
, state
->word
, NULL
);
964 SHOW_C_CTX ("try_complete:filename_subst_2");
966 completion_matches (state
->r
, filename_completion_function
,
971 cdpath
= str_get_next_char (s
);
980 /* --------------------------------------------------------------------------------------------- */
983 insert_text (WInput
* in
, char *text
, ssize_t size
)
985 int buff_len
= str_length (in
->buffer
);
987 size
= min (size
, (ssize_t
) strlen (text
)) + start
- end
;
988 if (strlen (in
->buffer
) + size
>= (size_t) in
->current_max_size
)
990 /* Expand the buffer */
991 char *narea
= g_try_realloc (in
->buffer
, in
->current_max_size
+ size
+ in
->field_width
);
995 in
->current_max_size
+= size
+ in
->field_width
;
998 if (strlen (in
->buffer
) + 1 < (size_t) in
->current_max_size
)
1002 int i
= strlen (&in
->buffer
[end
]);
1004 in
->buffer
[end
+ size
+ i
] = in
->buffer
[end
+ i
];
1008 char *p
= in
->buffer
+ end
+ size
, *q
= in
->buffer
+ end
;
1013 memcpy (in
->buffer
+ start
, text
, size
- start
+ end
);
1014 in
->point
+= str_length (in
->buffer
) - buff_len
;
1015 input_update (in
, TRUE
);
1021 /* --------------------------------------------------------------------------------------------- */
1024 query_callback (Widget
* w
, Widget
* sender
, widget_msg_t msg
, int parm
, void *data
)
1026 static char buff
[MB_LEN_MAX
] = "";
1029 WDialog
*h
= DIALOG (w
);
1045 /* exit from completion list if input line is empty */
1051 /* Refill the list box and start again */
1052 else if (end
== min_end
)
1054 end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1055 input_handle_char (input
, parm
);
1056 h
->ret_value
= B_USER
;
1066 new_end
= str_get_prev_char (&input
->buffer
[end
]) - input
->buffer
;
1068 for (i
= 0, e
= LISTBOX (h
->current
->data
)->list
;
1069 e
!= NULL
; i
++, e
= g_list_next (e
))
1071 WLEntry
*le
= LENTRY (e
->data
);
1073 if (strncmp (input
->buffer
+ start
, le
->text
, new_end
- start
) == 0)
1075 listbox_select_entry (LISTBOX (h
->current
->data
), i
);
1077 input_handle_char (input
, parm
);
1078 send_message (h
->current
->data
, NULL
, MSG_DRAW
, 0, NULL
);
1086 if (parm
< 32 || parm
> 255)
1089 if (input_key_is_in_map (input
, parm
) != 2)
1090 return MSG_NOT_HANDLED
;
1095 /* This means we want to refill the list box and start again */
1096 h
->ret_value
= B_USER
;
1104 int need_redraw
= 0;
1106 char *last_text
= NULL
;
1108 buff
[bl
++] = (char) parm
;
1110 switch (str_is_valid_char (buff
, bl
))
1119 for (i
= 0, e
= LISTBOX (h
->current
->data
)->list
;
1120 e
!= NULL
; i
++, e
= g_list_next (e
))
1122 WLEntry
*le
= LENTRY (e
->data
);
1124 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
) == 0
1125 && strncmp (&le
->text
[end
- start
], buff
, bl
) == 0)
1127 if (need_redraw
== 0)
1130 listbox_select_entry (LISTBOX (h
->current
->data
), i
);
1131 last_text
= le
->text
;
1139 /* count symbols between start and end */
1140 for (si
= le
->text
+ start
; si
< le
->text
+ end
;
1141 str_next_char (&si
), si_num
++)
1143 for (sl
= last_text
+ start
; sl
< last_text
+ end
;
1144 str_next_char (&sl
), sl_num
++)
1147 /* pointers to next symbols */
1148 si
= &le
->text
[str_offset_to_pos (le
->text
, ++si_num
)];
1149 sl
= &last_text
[str_offset_to_pos (last_text
, ++sl_num
)];
1151 while (si
[0] != '\0' && sl
[0] != '\0')
1153 char *nexti
, *nextl
;
1155 nexti
= str_get_next_char (si
);
1156 nextl
= str_get_next_char (sl
);
1158 if (nexti
- si
!= nextl
- sl
|| strncmp (si
, sl
, nexti
- si
) != 0)
1167 last_text
= le
->text
;
1169 si
= &last_text
[str_offset_to_pos (last_text
, si_num
)];
1170 if (low
> si
- last_text
)
1171 low
= si
- last_text
;
1178 if (need_redraw
== 2)
1180 insert_text (input
, last_text
, low
);
1181 send_message (h
->current
->data
, NULL
, MSG_DRAW
, 0, NULL
);
1183 else if (need_redraw
== 1)
1185 h
->ret_value
= B_ENTER
;
1195 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
1199 /* --------------------------------------------------------------------------------------------- */
1201 /** Returns 1 if the user would like to see us again */
1203 complete_engine (WInput
* in
, int what_to_do
)
1205 if (in
->completions
!= NULL
&& str_offset_to_pos (in
->buffer
, in
->point
) != end
)
1206 input_free_completions (in
);
1208 if (in
->completions
== NULL
)
1209 complete_engine_fill_completions (in
);
1211 if (in
->completions
!= NULL
)
1213 if (what_to_do
& DO_INSERTION
|| ((what_to_do
& DO_QUERY
) && !in
->completions
[1]))
1215 char *lc_complete
= in
->completions
[0];
1216 if (insert_text (in
, lc_complete
, strlen (lc_complete
)))
1218 if (in
->completions
[1])
1221 input_free_completions (in
);
1226 if ((what_to_do
& DO_QUERY
) && in
->completions
&& in
->completions
[1])
1228 int maxlen
= 0, i
, count
= 0;
1230 int start_x
, start_y
;
1233 WListbox
*query_list
;
1235 for (p
= in
->completions
+ 1; *p
!= NULL
; count
++, p
++)
1237 i
= str_term_width1 (*p
);
1241 start_x
= WIDGET (in
)->x
;
1242 start_y
= WIDGET (in
)->y
;
1243 if (start_y
- 2 >= count
)
1245 y
= start_y
- 2 - count
;
1250 if (start_y
>= LINES
- start_y
- 1)
1258 h
= LINES
- start_y
- 1;
1261 x
= start
- in
->term_first_shown
- 2 + start_x
;
1273 query_dlg
= create_dlg (TRUE
, y
, x
, query_height
, query_width
,
1274 dialog_colors
, query_callback
, NULL
,
1275 "[Completion]", NULL
, DLG_COMPACT
);
1276 query_list
= listbox_new (1, 1, h
- 2, w
- 2, FALSE
, NULL
);
1277 add_widget (query_dlg
, query_list
);
1278 for (p
= in
->completions
+ 1; *p
; p
++)
1279 listbox_add_item (query_list
, LISTBOX_APPEND_AT_END
, 0, *p
, NULL
);
1280 run_dlg (query_dlg
);
1282 if (query_dlg
->ret_value
== B_ENTER
)
1284 listbox_get_current (query_list
, &q
, NULL
);
1286 insert_text (in
, q
, strlen (q
));
1288 if (q
|| end
!= min_end
)
1289 input_free_completions (in
);
1290 i
= query_dlg
->ret_value
; /* B_USER if user wants to start over again */
1291 destroy_dlg (query_dlg
);
1301 /* --------------------------------------------------------------------------------------------- */
1302 /*** public functions ****************************************************************************/
1303 /* --------------------------------------------------------------------------------------------- */
1305 /** Returns an array of matches, or NULL if none. */
1307 try_complete (char *text
, int *lc_start
, int *lc_end
, input_complete_t flags
)
1309 try_complete_automation_state_t state
;
1310 char **matches
= NULL
;
1312 memset (&state
, 0, sizeof (try_complete_automation_state_t
));
1313 state
.flags
= flags
;
1315 SHOW_C_CTX ("try_complete");
1316 state
.word
= g_strndup (text
+ *lc_start
, *lc_end
- *lc_start
);
1318 state
.is_cd
= check_is_cd (text
, *lc_start
, state
.flags
);
1320 /* Determine if this could be a command word. It is if it appears at
1321 the start of the line (ignoring preceding whitespace), or if it
1322 appears after a character that separates commands. And we have to
1323 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1324 if (!state
.is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
))
1325 try_complete_commands_prepare (&state
, text
, lc_start
);
1327 try_complete_find_start_sign (&state
);
1329 /* Command substitution? */
1330 if (state
.p
> state
.q
&& state
.p
> state
.r
)
1332 SHOW_C_CTX ("try_complete:cmd_backq_subst");
1333 matches
= completion_matches (str_cget_next_char (state
.p
),
1334 command_completion_function
,
1335 state
.flags
& (~INPUT_COMPLETE_FILENAMES
));
1337 *lc_start
+= str_get_next_char (state
.p
) - state
.word
;
1340 /* Variable name? */
1341 else if (state
.q
> state
.p
&& state
.q
> state
.r
)
1343 SHOW_C_CTX ("try_complete:var_subst");
1344 matches
= completion_matches (state
.q
, variable_completion_function
, state
.flags
);
1346 *lc_start
+= state
.q
- state
.word
;
1349 /* Starts with '@', then look through the known hostnames for
1350 completion first. */
1351 else if (state
.r
> state
.p
&& state
.r
> state
.q
)
1353 SHOW_C_CTX ("try_complete:host_subst");
1354 matches
= completion_matches (state
.r
, hostname_completion_function
, state
.flags
);
1356 *lc_start
+= state
.r
- state
.word
;
1359 /* Starts with `~' and there is no slash in the word, then
1360 try completing this word as a username. */
1361 if (!matches
&& *state
.word
== '~' && (state
.flags
& INPUT_COMPLETE_USERNAMES
)
1362 && !strchr (state
.word
, PATH_SEP
))
1364 SHOW_C_CTX ("try_complete:user_subst");
1365 matches
= completion_matches (state
.word
, username_completion_function
, state
.flags
);
1368 /* And finally if this word is in a command position, then
1369 complete over possible command names, including aliases, functions,
1370 and command names. */
1371 if (matches
== NULL
)
1372 matches
= try_complete_all_possible (&state
, text
, lc_start
);
1374 g_free (state
.word
);
1379 /* --------------------------------------------------------------------------------------------- */
1382 complete_engine_fill_completions (WInput
* in
)
1386 end
= str_offset_to_pos (in
->buffer
, in
->point
);
1391 /* get symbol before in->point */
1394 for (i
= in
->point
- 1; i
> 0; i
--)
1398 for (; s
>= in
->buffer
; str_prev_char (&s
))
1400 start
= s
- in
->buffer
;
1401 if (strchr (" \t;|<>", *s
) != NULL
)
1408 start
= s
- in
->buffer
;
1411 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1414 /* --------------------------------------------------------------------------------------------- */
1416 /* declared in lib/widget/input.h */
1418 complete (WInput
* in
)
1422 if (!str_is_valid_string (in
->buffer
))
1425 if (in
->completions
!= NULL
)
1426 engine_flags
= DO_QUERY
;
1429 engine_flags
= DO_INSERTION
;
1431 if (mc_global
.widget
.show_all_if_ambiguous
)
1432 engine_flags
|= DO_QUERY
;
1435 while (complete_engine (in
, engine_flags
))
1439 /* --------------------------------------------------------------------------------------------- */