1 /* Input line filename/username/hostname/variable/command completion.
2 (Let mc type for you...)
4 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
5 2007 Free Software Foundation, Inc.
7 Written by: 1995 Jakub Jelinek
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
30 #include <sys/types.h>
44 #include "key.h" /* XCTRL and ALT macros */
46 typedef char *CompletionFunction (char *, int);
48 /* This flag is used in filename_completion_function */
49 static int ignore_filenames
= 0;
51 /* This flag is used by command_completion_function */
52 /* to hint the filename_completion_function */
53 static int look_for_executables
= 0;
56 filename_completion_function (char *text
, int state
)
58 static DIR *directory
;
59 static char *filename
= NULL
;
60 static char *dirname
= NULL
;
61 static char *users_dirname
= NULL
;
62 static size_t filename_len
;
63 int isdir
= 1, isexec
= 0;
65 struct dirent
*entry
= NULL
;
67 /* If we're starting the match process, initialize us a bit. */
73 g_free (users_dirname
);
75 if ((*text
) && (temp
= strrchr (text
, PATH_SEP
))){
76 filename
= g_strdup (++temp
);
77 dirname
= g_strndup (text
, temp
- text
);
79 dirname
= g_strdup (".");
80 filename
= g_strdup (text
);
83 /* We aren't done yet. We also support the "~user" syntax. */
85 /* Save the version of the directory that the user typed. */
86 users_dirname
= dirname
;
88 dirname
= tilde_expand (dirname
);
89 canonicalize_pathname (dirname
);
90 /* Here we should do something with variable expansion
92 Maybe a dream - UNIMPLEMENTED yet. */
94 directory
= mc_opendir (dirname
);
95 filename_len
= strlen (filename
);
98 /* Now that we have some state, we can read the directory. */
100 while (directory
&& (entry
= mc_readdir (directory
))){
101 /* Special case for no filename.
102 All entries except "." and ".." match. */
104 if (!strcmp (entry
->d_name
, ".") || !strcmp (entry
->d_name
, ".."))
107 /* Otherwise, if these match up to the length of filename, then
108 it may be a match. */
109 if ((entry
->d_name
[0] != filename
[0]) ||
110 ((NLENGTH (entry
)) < filename_len
) ||
111 strncmp (filename
, entry
->d_name
, filename_len
))
114 isdir
= 1; isexec
= 0;
116 char *tmp
= g_malloc (3 + strlen (dirname
) + NLENGTH (entry
));
117 struct stat tempstat
;
119 strcpy (tmp
, dirname
);
120 strcat (tmp
, PATH_SEP_STR
);
121 strcat (tmp
, entry
->d_name
);
122 canonicalize_pathname (tmp
);
124 if (!mc_stat (tmp
, &tempstat
)){
125 uid_t my_uid
= getuid ();
126 gid_t my_gid
= getgid ();
128 if (!S_ISDIR (tempstat
.st_mode
)){
130 if ((!my_uid
&& (tempstat
.st_mode
& 0111)) ||
131 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100)) ||
132 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010)) ||
133 (tempstat
.st_mode
& 0001))
139 switch (look_for_executables
)
144 case 1: if (!isexec
&& !isdir
)
148 if (ignore_filenames
&& !isdir
)
155 mc_closedir (directory
);
162 g_free (users_dirname
);
163 users_dirname
= NULL
;
168 if (users_dirname
&& (users_dirname
[0] != '.' || users_dirname
[1])){
169 int dirlen
= strlen (users_dirname
);
170 temp
= g_malloc (3 + dirlen
+ NLENGTH (entry
));
171 strcpy (temp
, users_dirname
);
172 /* We need a `/' at the end. */
173 if (users_dirname
[dirlen
- 1] != PATH_SEP
){
174 temp
[dirlen
] = PATH_SEP
;
175 temp
[dirlen
+ 1] = 0;
177 strcat (temp
, entry
->d_name
);
179 temp
= g_malloc (2 + NLENGTH (entry
));
180 strcpy (temp
, entry
->d_name
);
183 strcat (temp
, PATH_SEP_STR
);
188 /* We assume here that text[0] == '~' , if you want to call it in another way,
189 you have to change the code */
191 username_completion_function (char *text
, int state
)
193 static struct passwd
*entry
;
196 if (!state
){ /* Initialization stuff */
198 userlen
= strlen (text
+ 1);
200 while ((entry
= getpwent ()) != NULL
){
201 /* Null usernames should result in all users as possible completions. */
204 else if (text
[1] == entry
->pw_name
[0] &&
205 !strncmp (text
+ 1, entry
->pw_name
, userlen
))
213 char *temp
= g_malloc (3 + strlen (entry
->pw_name
));
216 strcpy (temp
+ 1, entry
->pw_name
);
217 strcat (temp
, PATH_SEP_STR
);
222 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
223 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
224 extern char **environ
;
227 /* We assume text [0] == '$' and want to have a look at text [1], if it is
228 equal to '{', so that we should append '}' at the end */
230 variable_completion_function (char *text
, int state
)
233 static int varlen
, isbrace
;
234 const char *p
= NULL
;
236 if (!state
){ /* Initialization stuff */
237 isbrace
= (text
[1] == '{');
238 varlen
= strlen (text
+ 1 + isbrace
);
243 p
= strchr (*env_p
, '=');
244 if (p
&& p
- *env_p
>= varlen
&& !strncmp (text
+ 1 + isbrace
, *env_p
, varlen
))
252 char *temp
= g_malloc (2 + 2 * isbrace
+ p
- *env_p
);
257 memcpy (temp
+ 1 + isbrace
, *env_p
, p
- *env_p
);
259 strcpy (temp
+ 2 + (p
- *env_p
), "}");
261 temp
[1 + p
- *env_p
] = 0;
267 #define whitespace(c) ((c) == ' ' || (c) == '\t')
268 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
270 static char **hosts
= NULL
;
271 static char **hosts_p
= NULL
;
272 static int hosts_alloclen
= 0;
273 static void fetch_hosts (const char *filename
)
275 FILE *file
= fopen (filename
, "r");
276 char buffer
[256], *name
;
277 register int i
, start
;
282 while (fgets (buffer
, 255, file
) != NULL
){
283 /* Skip to first character. */
284 for (i
= 0; buffer
[i
] && cr_whitespace (buffer
[i
]); i
++);
285 /* Ignore comments... */
286 if (buffer
[i
] == '#')
288 /* Handle $include. */
289 if (!strncmp (buffer
+ i
, "$include ", 9)){
290 char *includefile
= buffer
+ i
+ 9;
293 /* Find start of filename. */
294 while (*includefile
&& whitespace (*includefile
))
298 /* Find end of filename. */
299 while (*t
&& !cr_whitespace (*t
))
303 fetch_hosts (includefile
);
308 while (buffer
[i
] && !cr_whitespace (buffer
[i
]))
311 /* Get the host names separated by white space. */
312 while (buffer
[i
] && buffer
[i
] != '#'){
313 while (buffer
[i
] && cr_whitespace (buffer
[i
]))
315 if (buffer
[i
] == '#')
317 for (start
= i
; buffer
[i
] && !cr_whitespace (buffer
[i
]); i
++);
320 name
= g_strndup (buffer
+ start
, i
- start
);
324 if (hosts_p
- hosts
>= hosts_alloclen
){
325 int j
= hosts_p
- hosts
;
327 hosts
= g_realloc ((void *)hosts
, ((hosts_alloclen
+= 30) + 1) * sizeof (char *));
330 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
331 if (!strcmp (name
, *host_p
))
332 break; /* We do not want any duplicates */
333 if (host_p
== hosts_p
){
345 hostname_completion_function (char *text
, int state
)
347 static char **host_p
;
348 static int textstart
, textlen
;
350 if (!state
){ /* Initialization stuff */
354 for (host_p
= hosts
; *host_p
; host_p
++)
358 hosts
= g_new (char *, (hosts_alloclen
= 30) + 1);
361 fetch_hosts ((p
= getenv ("HOSTFILE")) ? p
: "/etc/hosts");
363 textstart
= (*text
== '@') ? 1 : 0;
364 textlen
= strlen (text
+ textstart
);
369 break; /* Match all of them */
370 else if (!strncmp (text
+ textstart
, *host_p
, textlen
))
376 for (host_p
= hosts
; *host_p
; host_p
++)
382 char *temp
= g_malloc (2 + strlen (*host_p
));
386 strcpy (temp
+ textstart
, *host_p
);
393 * This is the function to call when the word to complete is in a position
394 * where a command word can be found. It looks around $PATH, looking for
395 * commands that match. It also scans aliases, function names, and the
396 * table of shell built-ins.
399 command_completion_function (char *text
, int state
)
401 static const char *path_end
;
402 static int isabsolute
;
405 static const char *const *words
;
407 static char *cur_path
;
408 static char *cur_word
;
409 static int init_state
;
410 static const char *const bash_reserved
[] = {
411 "if", "then", "else", "elif", "fi", "case", "esac", "for",
412 "select", "while", "until", "do", "done", "in", "function", 0
414 static const char *const bash_builtins
[] = {
415 "alias", "bg", "bind", "break", "builtin", "cd", "command",
416 "continue", "declare", "dirs", "echo", "enable", "eval",
417 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
418 "help", "history", "jobs", "kill", "let", "local", "logout",
419 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
420 "shift", "source", "suspend", "test", "times", "trap", "type",
421 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
425 if (!state
) { /* Initialize us a little bit */
426 isabsolute
= strchr (text
, PATH_SEP
) != 0;
427 look_for_executables
= isabsolute
? 1 : 2;
429 words
= bash_reserved
;
431 text_len
= strlen (text
);
432 if (!path
&& (path
= g_strdup (getenv ("PATH"))) != NULL
) {
434 path_end
= strchr (p
, 0);
435 while ((p
= strchr (p
, PATH_ENV_SEP
))) {
443 p
= filename_completion_function (text
, state
);
445 look_for_executables
= 0;
451 case 0: /* Reserved words */
453 if (!strncmp (*words
, text
, text_len
))
454 return g_strdup (*(words
++));
458 words
= bash_builtins
;
459 case 1: /* Builtin commands */
461 if (!strncmp (*words
, text
, text_len
))
462 return g_strdup (*(words
++));
470 case 2: /* And looking through the $PATH */
475 if (cur_path
>= path_end
)
477 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
478 cur_word
= concat_dir_and_file (expanded
, text
);
480 canonicalize_pathname (cur_word
);
481 cur_path
= strchr (cur_path
, 0) + 1;
485 filename_completion_function (cur_word
,
495 look_for_executables
= 0;
500 if ((p
= strrchr (found
, PATH_SEP
)) != NULL
) {
511 match_compare (const void *a
, const void *b
)
513 return strcmp (*(char **)a
, *(char **)b
);
516 /* Returns an array of char * matches with the longest common denominator
517 in the 1st entry. Then a NULL terminated list of different possible
519 You have to supply your own CompletionFunction with the word you
520 want to complete as the first argument and an count of previous matches
522 In case no matches were found we return NULL. */
524 completion_matches (char *text
, CompletionFunction entry_function
)
526 /* Number of slots in match_list. */
529 /* The list of matches. */
530 char **match_list
= g_new (char *, (match_list_size
= 30) + 1);
532 /* Number of matches actually found. */
535 /* Temporary string binder. */
538 match_list
[1] = NULL
;
540 while ((string
= (*entry_function
) (text
, matches
)) != NULL
){
541 if (matches
+ 1 == match_list_size
)
542 match_list
= (char **) g_realloc (match_list
, ((match_list_size
+= 30) + 1) * sizeof (char *));
543 match_list
[++matches
] = string
;
544 match_list
[matches
+ 1] = NULL
;
547 /* If there were any matches, then look through them finding out the
548 lowest common denominator. That then becomes match_list[0]. */
552 int low
= 4096; /* Count of max-matched characters. */
554 /* If only one match, just use that. */
556 match_list
[0] = match_list
[1];
557 match_list
[1] = NULL
;
561 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
563 /* And compare each member of the list with
564 the next, finding out where they stop matching.
565 If we find two equal strings, we have to put one away... */
568 while (j
< matches
+ 1)
570 register int c1
, c2
, si
;
572 for (si
= 0;(c1
= match_list
[i
][si
]) && (c2
= match_list
[j
][si
]); si
++)
575 if (!c1
&& !match_list
[j
][si
]){ /* Two equal strings */
576 g_free (match_list
[j
]);
580 continue; /* Look for a run of equal strings */
582 if (low
> si
) low
= si
;
583 if (i
+ 1 != j
) /* So there's some gap */
584 match_list
[i
+ 1] = match_list
[j
];
588 match_list
[matches
+ 1] = NULL
;
589 match_list
[0] = g_strndup(match_list
[1], low
);
591 } else { /* There were no matches. */
598 /* Check if directory completion is needed */
600 check_is_cd (const char *text
, int start
, int flags
)
604 if (flags
& INPUT_COMPLETE_CD
)
607 if (!(flags
& INPUT_COMPLETE_COMMANDS
))
610 /* Skip initial spaces */
613 while (p
< q
&& *p
&& isspace ((unsigned char) *p
))
616 /* Check if the command is "cd" and the cursor is after it */
617 if (p
[0] == 'c' && p
[1] == 'd' && isspace ((unsigned char) p
[2])
624 /* Returns an array of matches, or NULL if none. */
626 try_complete (char *text
, int *start
, int *end
, int flags
)
628 int in_command_position
= 0, i
;
630 char **matches
= NULL
;
631 const char *command_separator_chars
= ";|&{(`";
632 char *p
= NULL
, *q
= NULL
, *r
= NULL
;
633 int is_cd
= check_is_cd (text
, *start
, flags
);
635 ignore_filenames
= 0;
638 word
= g_strdup (text
+ *start
);
641 /* Determine if this could be a command word. It is if it appears at
642 the start of the line (ignoring preceding whitespace), or if it
643 appears after a character that separates commands. And we have to
644 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
645 if (!is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
)){
647 while (i
> -1 && (text
[i
] == ' ' || text
[i
] == '\t'))
650 in_command_position
++;
651 else if (strchr (command_separator_chars
, text
[i
])){
652 register int this_char
, prev_char
;
654 in_command_position
++;
657 /* Handle the two character tokens `>&', `<&', and `>|'.
658 We are not in a command position after one of these. */
660 prev_char
= text
[i
- 1];
662 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>')) ||
663 (this_char
== '|' && prev_char
== '>'))
664 in_command_position
= 0;
665 else if (i
> 0 && text
[i
-1] == '\\') /* Quoted */
666 in_command_position
= 0;
671 if (flags
& INPUT_COMPLETE_COMMANDS
)
672 p
= strrchr (word
, '`');
673 if (flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
674 q
= strrchr (word
, '$');
675 if (flags
& INPUT_COMPLETE_HOSTNAMES
)
676 r
= strrchr (word
, '@');
677 if (q
&& q
[1] == '(' && INPUT_COMPLETE_COMMANDS
){
683 /* Command substitution? */
685 matches
= completion_matches (p
+ 1, command_completion_function
);
687 *start
+= p
+ 1 - word
;
691 else if (q
> p
&& q
> r
){
692 matches
= completion_matches (q
, variable_completion_function
);
697 /* Starts with '@', then look through the known hostnames for
699 else if (r
> p
&& r
> q
){
700 matches
= completion_matches (r
, hostname_completion_function
);
705 /* Starts with `~' and there is no slash in the word, then
706 try completing this word as a username. */
707 if (!matches
&& *word
== '~' && (flags
& INPUT_COMPLETE_USERNAMES
) && !strchr (word
, PATH_SEP
))
708 matches
= completion_matches (word
, username_completion_function
);
711 /* And finally if this word is in a command position, then
712 complete over possible command names, including aliases, functions,
713 and command names. */
714 if (!matches
&& in_command_position
)
715 matches
= completion_matches (word
, command_completion_function
);
717 else if (!matches
&& (flags
& INPUT_COMPLETE_FILENAMES
)){
719 ignore_filenames
= 1;
720 matches
= completion_matches (word
, filename_completion_function
);
721 ignore_filenames
= 0;
722 if (!matches
&& is_cd
&& *word
!= PATH_SEP
&& *word
!= '~'){
723 char *p
, *q
= text
+ *start
;
725 for (p
= text
; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); p
++);
726 if (!strncmp (p
, "cd", 2))
727 for (p
+= 2; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); p
++);
729 char * const cdpath_ref
= g_strdup (getenv ("CDPATH"));
730 char *cdpath
= cdpath_ref
;
737 while (!matches
&& c
== ':'){
738 s
= strchr (cdpath
, ':');
740 s
= strchr (cdpath
, 0);
744 r
= concat_dir_and_file (cdpath
, word
);
745 ignore_filenames
= 1;
746 matches
= completion_matches (r
, filename_completion_function
);
747 ignore_filenames
= 0;
763 void free_completions (WInput
*in
)
767 if (!in
->completions
)
769 for (p
=in
->completions
; *p
; p
++)
771 g_free (in
->completions
);
772 in
->completions
= NULL
;
775 static int query_height
, query_width
;
776 static WInput
*input
;
778 static int start
, end
;
780 static int insert_text (WInput
*in
, char *text
, ssize_t len
)
782 len
= min (len
, (ssize_t
) strlen (text
)) + start
- end
;
783 if (strlen (in
->buffer
) + len
>= (size_t) in
->current_max_len
){
784 /* Expand the buffer */
785 char *narea
= g_realloc (in
->buffer
, in
->current_max_len
+ len
+ in
->field_len
);
788 in
->current_max_len
+= len
+ in
->field_len
;
791 if (strlen (in
->buffer
)+1 < (size_t) in
->current_max_len
){
793 int i
= strlen (&in
->buffer
[end
]);
795 in
->buffer
[end
+ len
+ i
] = in
->buffer
[end
+ i
];
797 char *p
= in
->buffer
+ end
+ len
, *q
= in
->buffer
+ end
;
802 memcpy (in
->buffer
+ start
, text
, len
- start
+ end
);
804 update_input (in
, 1);
811 query_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
823 if (end
== min_end
) {
830 e1
= e
= ((WListbox
*) (h
->current
))->list
;
833 (input
->buffer
+ start
, e1
->text
,
835 listbox_select_entry ((WListbox
*) (h
->current
),
837 handle_char (input
, parm
);
839 send_message (h
->current
, WIDGET_DRAW
, 0);
848 if (parm
> 0xff || !is_printable (parm
)) {
849 if (is_in_input_map (input
, parm
) == 2) {
852 h
->ret_value
= B_USER
; /* This means we want to refill the
853 list box and start again */
857 return MSG_NOT_HANDLED
;
862 char *last_text
= NULL
;
864 e1
= e
= ((WListbox
*) (h
->current
))->list
;
867 (input
->buffer
+ start
, e1
->text
, end
- start
)) {
868 if (e1
->text
[end
- start
] == parm
) {
870 register int c1
, c2
, si
;
872 for (si
= end
- start
+ 1;
874 && (c2
= e1
->text
[si
]); si
++)
879 last_text
= e1
->text
;
883 listbox_select_entry ((WListbox
*) (h
->
886 last_text
= e1
->text
;
892 if (need_redraw
== 2) {
893 insert_text (input
, last_text
, low
);
894 send_message (h
->current
, WIDGET_DRAW
, 0);
895 } else if (need_redraw
== 1) {
896 h
->ret_value
= B_ENTER
;
905 return default_dlg_callback (h
, msg
, parm
);
909 #define DO_INSERTION 1
911 /* Returns 1 if the user would like to see us again */
913 complete_engine (WInput
*in
, int what_to_do
)
915 char *complete
= NULL
;
916 if (in
->completions
&& in
->point
!= end
)
917 free_completions (in
);
918 if (!in
->completions
){
920 for (start
= end
? end
- 1 : 0; start
> -1; start
--)
921 if (strchr (" \t;|<>", in
->buffer
[start
]))
925 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
927 if (in
->completions
){
928 if (what_to_do
& DO_INSERTION
|| ((what_to_do
& DO_QUERY
) && !in
->completions
[1])) {
929 complete
= escape_string(in
->completions
[0]);
930 if (insert_text (in
, complete
, strlen (complete
))){
931 if (in
->completions
[1])
934 free_completions (in
);
938 if ((what_to_do
& DO_QUERY
) && in
->completions
&& in
->completions
[1]) {
939 int maxlen
= 0, i
, count
= 0;
941 int start_x
, start_y
;
944 WListbox
*query_list
;
946 for (p
=in
->completions
+ 1; *p
; count
++, p
++) {
947 *p
= escape_string(*p
);
948 if ((i
= strlen (*p
)) > maxlen
)
951 start_x
= in
->widget
.x
;
952 start_y
= in
->widget
.y
;
953 if (start_y
- 2 >= count
) {
954 y
= start_y
- 2 - count
;
957 if (start_y
>= LINES
- start_y
- 1) {
962 h
= LINES
- start_y
- 1;
965 x
= start
- in
->first_shown
- 2 + start_x
;
977 query_dlg
= create_dlg (y
, x
, query_height
, query_width
,
978 dialog_colors
, query_callback
,
979 "[Completion]", NULL
, DLG_COMPACT
);
980 query_list
= listbox_new (1, 1, w
- 2, h
- 2, NULL
);
981 add_widget (query_dlg
, query_list
);
982 for (p
= in
->completions
+ 1; *p
; p
++)
983 listbox_add_item (query_list
, 0, 0, *p
, NULL
);
986 if (query_dlg
->ret_value
== B_ENTER
){
987 listbox_get_current (query_list
, &q
, NULL
);
989 insert_text (in
, q
, strlen (q
));
991 if (q
|| end
!= min_end
)
992 free_completions (in
);
993 i
= query_dlg
->ret_value
; /* B_USER if user wants to start over again */
994 destroy_dlg (query_dlg
);
1003 void complete (WInput
*in
)
1007 if (in
->completions
)
1008 engine_flags
= DO_QUERY
;
1011 engine_flags
= DO_INSERTION
;
1013 if (show_all_if_ambiguous
)
1014 engine_flags
|= DO_QUERY
;
1017 while (complete_engine (in
, engine_flags
));