1 /* Input line filename/username/hostname/variable/command completion.
2 (Let mc type for you...)
4 Copyright (C) 1995 The Free Software Foundation
6 Written by: 1995 Jakub Jelinek
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
29 #include <sys/types.h>
42 #include "key.h" /* XCTRL and ALT macros */
43 #include "../vfs/vfs.h"
45 /* This flag is used in filename_completion_function */
46 static int ignore_filenames
= 0;
48 /* This flag is used by command_completion_function */
49 /* to hint the filename_completion_function */
50 static int look_for_executables
= 0;
53 filename_completion_function (char *text
, int state
)
55 static DIR *directory
;
56 static char *filename
= NULL
;
57 static char *dirname
= NULL
;
58 static char *users_dirname
= NULL
;
59 static int filename_len
;
60 int isdir
= 1, isexec
= 0;
62 struct dirent
*entry
= NULL
;
64 /* 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
);
90 g_free (users_dirname
);
92 users_dirname
= filename
= NULL
;
95 canonicalize_pathname (dirname
);
96 /* Here we should do something with variable expansion
98 Maybe a dream - UNIMPLEMENTED yet. */
100 directory
= mc_opendir (dirname
);
101 filename_len
= strlen (filename
);
104 /* Now that we have some state, we can read the directory. */
106 while (directory
&& (entry
= mc_readdir (directory
))){
107 /* Special case for no filename.
108 All entries except "." and ".." match. */
110 if (!strcmp (entry
->d_name
, ".") || !strcmp (entry
->d_name
, ".."))
113 /* Otherwise, if these match up to the length of filename, then
114 it may be a match. */
115 if ((entry
->d_name
[0] != filename
[0]) ||
116 ((NLENGTH (entry
)) < filename_len
) ||
117 strncmp (filename
, entry
->d_name
, filename_len
))
120 isdir
= 1; isexec
= 0;
122 char *tmp
= g_malloc (3 + strlen (dirname
) + NLENGTH (entry
));
123 struct stat tempstat
;
125 strcpy (tmp
, dirname
);
126 strcat (tmp
, PATH_SEP_STR
);
127 strcat (tmp
, entry
->d_name
);
128 canonicalize_pathname (tmp
);
130 if (!mc_stat (tmp
, &tempstat
)){
131 uid_t my_uid
= getuid ();
132 gid_t my_gid
= getgid ();
134 if (!S_ISDIR (tempstat
.st_mode
)){
136 if ((!my_uid
&& (tempstat
.st_mode
& 0111)) ||
137 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100)) ||
138 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010)) ||
139 (tempstat
.st_mode
& 0001))
145 switch (look_for_executables
)
150 case 1: if (!isexec
&& !isdir
)
154 if (ignore_filenames
&& !isdir
)
161 mc_closedir (directory
);
173 g_free (users_dirname
);
174 users_dirname
= NULL
;
180 if (users_dirname
&& (users_dirname
[0] != '.' || users_dirname
[1])){
181 int dirlen
= strlen (users_dirname
);
182 temp
= g_malloc (3 + dirlen
+ NLENGTH (entry
));
183 strcpy (temp
, users_dirname
);
184 /* We need a `/' at the end. */
185 if (users_dirname
[dirlen
- 1] != PATH_SEP
){
186 temp
[dirlen
] = PATH_SEP
;
187 temp
[dirlen
+ 1] = 0;
189 strcat (temp
, entry
->d_name
);
191 temp
= g_malloc (2 + NLENGTH (entry
));
192 strcpy (temp
, entry
->d_name
);
195 strcat (temp
, PATH_SEP_STR
);
200 /* We assume here that text[0] == '~' , if you want to call it in another way,
201 you have to change the code */
203 char *username_completion_function (char *text
, int state
)
209 username_completion_function (char *text
, int state
)
211 static struct passwd
*entry
;
214 if (!state
){ /* Initialization stuff */
216 userlen
= strlen (text
+ 1);
218 while ((entry
= getpwent ()) != NULL
){
219 /* Null usernames should result in all users as possible completions. */
222 else if (text
[1] == entry
->pw_name
[0] &&
223 !strncmp (text
+ 1, entry
->pw_name
, userlen
))
231 char *temp
= g_malloc (3 + strlen (entry
->pw_name
));
234 strcpy (temp
+ 1, entry
->pw_name
);
235 strcat (temp
, PATH_SEP_STR
);
240 extern char **environ
;
243 /* We assume text [0] == '$' and want to have a look at text [1], if it is
244 equal to '{', so that we should append '}' at the end */
246 variable_completion_function (char *text
, int state
)
249 static int varlen
, isbrace
;
252 if (!state
){ /* Initialization stuff */
253 isbrace
= (text
[1] == '{');
254 varlen
= strlen (text
+ 1 + isbrace
);
259 p
= strchr (*env_p
, '=');
260 if (p
&& p
- *env_p
>= varlen
&& !strncmp (text
+ 1 + isbrace
, *env_p
, varlen
))
268 char *temp
= g_malloc (2 + 2 * isbrace
+ p
- *env_p
);
273 strncpy (temp
+ 1 + isbrace
, *env_p
, p
- *env_p
);
275 strcpy (temp
+ 2 + (p
- *env_p
), "}");
277 temp
[1 + p
- *env_p
] = 0;
283 #define whitespace(c) ((c) == ' ' || (c) == '\t')
284 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
286 static char **hosts
= NULL
;
287 static char **hosts_p
= NULL
;
288 static int hosts_alloclen
= 0;
289 static void fetch_hosts (char *filename
)
291 FILE *file
= fopen (filename
, "r");
292 char *temp
, buffer
[256], *name
;
293 register int i
, start
;
298 while ((temp
= fgets (buffer
, 255, file
)) != NULL
){
299 /* Skip to first character. */
300 for (i
= 0; buffer
[i
] && cr_whitespace (buffer
[i
]); i
++);
301 /* Ignore comments... */
302 if (buffer
[i
] == '#')
304 /* Handle $include. */
305 if (!strncmp (buffer
+ i
, "$include ", 9)){
306 char *includefile
= buffer
+ i
+ 9;
309 /* Find start of filename. */
310 while (*includefile
&& whitespace (*includefile
))
314 /* Find end of filename. */
315 while (*t
&& !cr_whitespace (*t
))
319 fetch_hosts (includefile
);
324 while (buffer
[i
] && !cr_whitespace (buffer
[i
]))
327 /* Get the host names separated by white space. */
328 while (buffer
[i
] && buffer
[i
] != '#'){
329 while (buffer
[i
] && cr_whitespace (buffer
[i
]))
331 if (buffer
[i
] == '#')
333 for (start
= i
; buffer
[i
] && !cr_whitespace (buffer
[i
]); i
++);
336 name
= g_strndup (buffer
+ start
, i
- start
);
340 if (hosts_p
- hosts
>= hosts_alloclen
){
341 int j
= hosts_p
- hosts
;
343 hosts
= g_realloc ((void *)hosts
, ((hosts_alloclen
+= 30) + 1) * sizeof (char *));
346 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
347 if (!strcmp (name
, *host_p
))
348 break; /* We do not want any duplicates */
349 if (host_p
== hosts_p
){
361 hostname_completion_function (char *text
, int state
)
363 static char **host_p
;
364 static int textstart
, textlen
;
366 if (!state
){ /* Initialization stuff */
370 for (host_p
= hosts
; *host_p
; host_p
++)
374 hosts
= g_new (char *, (hosts_alloclen
= 30) + 1);
377 fetch_hosts ((p
= getenv ("HOSTFILE")) ? p
: "/etc/hosts");
379 textstart
= (*text
== '@') ? 1 : 0;
380 textlen
= strlen (text
+ textstart
);
385 break; /* Match all of them */
386 else if (!strncmp (text
+ textstart
, *host_p
, textlen
))
392 for (host_p
= hosts
; *host_p
; host_p
++)
398 char *temp
= g_malloc (2 + strlen (*host_p
));
402 strcpy (temp
+ textstart
, *host_p
);
409 * This is the function to call when the word to complete is in a position
410 * where a command word can be found. It looks around $PATH, looking for
411 * commands that match. It also scans aliases, function names, and the
412 * table of shell built-ins.
415 command_completion_function (char *text
, int state
)
417 static char *path_end
;
418 static int isabsolute
;
421 static const char * const * words
;
423 static char *cur_path
;
424 static char *cur_word
;
425 static int init_state
;
426 static const char * const bash_reserved
[] = {
427 "if", "then", "else", "elif", "fi", "case", "esac", "for", "select",
428 "while", "until", "do", "done", "in", "function" , 0
430 static const char * const bash_builtins
[] = {
431 "alias", "bg", "bind", "break", "builtin", "cd", "command", "continue",
432 "declare", "dirs", "echo", "enable", "eval", "exec", "exit", "export",
433 "fc", "fg", "getopts", "hash", "help", "history", "jobs", "kill", "let",
434 "local", "logout", "popd", "pushd", "pwd", "read", "readonly", "return",
435 "set", "shift", "source", "suspend", "test", "times", "trap", "type",
436 "typeset", "ulimit", "umask", "unalias", "unset", "wait" , 0
440 if (!state
){ /* Initialize us a little bit */
441 isabsolute
= strchr (text
, PATH_SEP
) != 0;
442 look_for_executables
= isabsolute
? 1 : 2;
444 words
= bash_reserved
;
446 text_len
= strlen (text
);
447 path
= getenv ("PATH");
449 p
= path
= g_strdup (path
);
450 path_end
= strchr (p
, 0);
451 while ((p
= strchr (p
, PATH_ENV_SEP
))){
459 p
= filename_completion_function (text
, state
);
461 look_for_executables
= 0;
467 case 0: /* Reserved words */
469 if (!strncmp (*words
, text
, text_len
))
470 return g_strdup (*(words
++));
474 words
= bash_builtins
;
475 case 1: /* Builtin commands */
477 if (!strncmp (*words
, text
, text_len
))
478 return g_strdup (*(words
++));
486 case 2: /* And looking through the $PATH */
491 if (cur_path
>= path_end
)
493 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
499 p
= canonicalize_pathname (expanded
);
500 cur_word
= concat_dir_and_file (p
, text
);
502 cur_path
= strchr (cur_path
, 0) + 1;
505 found
= filename_completion_function (cur_word
, state
- init_state
);
514 look_for_executables
= 0;
519 if ((p
= strrchr (found
, PATH_SEP
)) != NULL
){
530 match_compare (const void *a
, const void *b
)
532 return strcmp (*(char **)a
, *(char **)b
);
535 /* Returns an array of char * matches with the longest common denominator
536 in the 1st entry. Then a NULL terminated list of different possible
538 You have to supply your own CompletionFunction with the word you
539 want to complete as the first argument and an count of previous matches
541 In case no matches were found we return NULL. */
543 completion_matches (char *text
, CompletionFunction entry_function
)
545 /* Number of slots in match_list. */
548 /* The list of matches. */
549 char **match_list
= g_new (char *, (match_list_size
= 30) + 1);
551 /* Number of matches actually found. */
554 /* Temporary string binder. */
557 match_list
[1] = NULL
;
559 while ((string
= (*entry_function
) (text
, matches
)) != NULL
){
560 if (matches
+ 1 == match_list_size
)
561 match_list
= (char **) g_realloc (match_list
, ((match_list_size
+= 30) + 1) * sizeof (char *));
562 match_list
[++matches
] = string
;
563 match_list
[matches
+ 1] = NULL
;
566 /* If there were any matches, then look through them finding out the
567 lowest common denominator. That then becomes match_list[0]. */
571 int low
= 4096; /* Count of max-matched characters. */
573 /* If only one match, just use that. */
575 match_list
[0] = match_list
[1];
576 match_list
[1] = (char *)NULL
;
580 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
582 /* And compare each member of the list with
583 the next, finding out where they stop matching.
584 If we find two equal strings, we have to put one away... */
587 while (j
< matches
+ 1)
589 register int c1
, c2
, si
;
591 for (si
= 0;(c1
= match_list
[i
][si
]) && (c2
= match_list
[j
][si
]); si
++)
594 if (!c1
&& !match_list
[j
][si
]){ /* Two equal strings */
595 g_free (match_list
[j
]);
600 if (low
> si
) low
= si
;
601 if (i
+ 1 != j
) /* So there's some gap */
602 match_list
[i
+ 1] = match_list
[j
];
606 match_list
[matches
+ 1] = NULL
;
607 match_list
[0] = g_malloc (low
+ 1);
608 strncpy (match_list
[0], match_list
[1], low
);
609 match_list
[0][low
] = 0;
611 } else { /* There were no matches. */
619 check_is_cd (char *text
, int start
, int flags
)
621 char *p
, *q
= text
+ start
;
623 for (p
= text
; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); p
++);
624 if (((flags
& INPUT_COMPLETE_COMMANDS
) &&
625 !strncmp (p
, "cd", 2) && (p
[2] == ' ' || p
[2] == '\t') &&
627 (flags
& INPUT_COMPLETE_CD
))
632 /* Returns an array of matches, or NULL if none. */
634 try_complete (char *text
, int *start
, int *end
, int flags
)
636 int in_command_position
= 0, i
;
638 char **matches
= NULL
;
639 char *command_separator_chars
= ";|&{(`";
640 char *p
= NULL
, *q
= NULL
, *r
= NULL
;
641 int is_cd
= check_is_cd (text
, *start
, flags
);
643 ignore_filenames
= 0;
646 word
= g_strdup (text
+ *start
);
649 /* Determine if this could be a command word. It is if it appears at
650 the start of the line (ignoring preceding whitespace), or if it
651 appears after a character that separates commands. And we have to
652 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
653 if (!is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
)){
655 while (i
> -1 && (text
[i
] == ' ' || text
[i
] == '\t'))
658 in_command_position
++;
659 else if (strchr (command_separator_chars
, text
[i
])){
660 register int this_char
, prev_char
;
662 in_command_position
++;
665 /* Handle the two character tokens `>&', `<&', and `>|'.
666 We are not in a command position after one of these. */
668 prev_char
= text
[i
- 1];
670 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>')) ||
671 (this_char
== '|' && prev_char
== '>'))
672 in_command_position
= 0;
673 else if (i
> 0 && text
[i
-1] == '\\') /* Quoted */
674 in_command_position
= 0;
679 if (flags
& INPUT_COMPLETE_COMMANDS
)
680 p
= strrchr (word
, '`');
681 if (flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
682 q
= strrchr (word
, '$');
683 if (flags
& INPUT_COMPLETE_HOSTNAMES
)
684 r
= strrchr (word
, '@');
685 if (q
&& q
[1] == '(' && INPUT_COMPLETE_COMMANDS
){
691 /* Command substitution? */
693 matches
= completion_matches (p
+ 1, command_completion_function
);
695 *start
+= p
+ 1 - word
;
699 else if (q
> p
&& q
> r
){
700 matches
= completion_matches (q
, variable_completion_function
);
705 /* Starts with '@', then look through the known hostnames for
707 else if (r
> p
&& r
> q
){
708 matches
= completion_matches (r
, hostname_completion_function
);
713 /* Starts with `~' and there is no slash in the word, then
714 try completing this word as a username. */
715 if (!matches
&& *word
== '~' && (flags
& INPUT_COMPLETE_USERNAMES
) && !strchr (word
, PATH_SEP
))
716 matches
= completion_matches (word
, username_completion_function
);
719 /* And finally if this word is in a command position, then
720 complete over possible command names, including aliases, functions,
721 and command names. */
722 if (!matches
&& in_command_position
)
723 matches
= completion_matches (word
, command_completion_function
);
725 else if (!matches
&& (flags
& INPUT_COMPLETE_FILENAMES
)){
727 ignore_filenames
= 1;
728 matches
= completion_matches (word
, filename_completion_function
);
729 ignore_filenames
= 0;
730 if (!matches
&& is_cd
&& *word
!= PATH_SEP
&& *word
!= '~'){
731 char *p
, *q
= text
+ *start
;
733 for (p
= text
; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); p
++);
734 if (!strncmp (p
, "cd", 2))
735 for (p
+= 2; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); p
++);
737 char *cdpath
= getenv ("CDPATH");
744 while (!matches
&& c
== ':'){
745 s
= strchr (cdpath
, ':');
747 s
= strchr (cdpath
, 0);
751 r
= concat_dir_and_file (cdpath
, word
);
752 ignore_filenames
= 1;
753 matches
= completion_matches (r
, filename_completion_function
);
754 ignore_filenames
= 0;
770 void free_completions (WInput
*in
)
774 if (!in
->completions
)
776 for (p
=in
->completions
; *p
; p
++)
778 g_free (in
->completions
);
779 in
->completions
= NULL
;
782 static int query_height
, query_width
;
783 static WInput
*input
;
785 static int start
, end
;
787 static int insert_text (WInput
*in
, char *text
, int len
)
789 len
= min (len
, strlen (text
)) + start
- end
;
790 if (strlen (in
->buffer
) + len
>= in
->current_max_len
){
791 /* Expand the buffer */
792 char *narea
= g_realloc (in
->buffer
, in
->current_max_len
+ len
+ in
->field_len
);
795 in
->current_max_len
+= len
+ in
->field_len
;
798 if (strlen (in
->buffer
)+1 < in
->current_max_len
){
800 int i
= strlen (&in
->buffer
[end
]);
802 in
->buffer
[end
+ len
+ i
] = in
->buffer
[end
+ i
];
804 char *p
= in
->buffer
+ end
+ len
, *q
= in
->buffer
+ end
;
809 strncpy (in
->buffer
+ start
, text
, len
- start
+ end
);
811 update_input (in
, 1);
818 query_callback (Dlg_head
* h
, int Par
, int Msg
)
822 common_dialog_repaint (h
);
836 if (end
== min_end
) {
843 e1
= e
= ((WListbox
*) (h
->current
->widget
))->list
;
846 (input
->buffer
+ start
, e1
->text
,
848 listbox_select_entry ((WListbox
*) (h
->current
->
850 handle_char (input
, Par
);
852 send_message (h
, h
->current
->widget
,
862 if (Par
> 0xff || !is_printable (Par
)) {
863 if (is_in_input_map (input
, Par
) == 2) {
866 h
->ret_value
= B_USER
; /* This means we want to refill the
867 list box and start again */
876 char *last_text
= NULL
;
878 e1
= e
= ((WListbox
*) (h
->current
->widget
))->list
;
881 (input
->buffer
+ start
, e1
->text
, end
- start
)) {
882 if (e1
->text
[end
- start
] == Par
) {
884 register int c1
, c2
, si
;
886 for (si
= end
- start
+ 1;
887 (c1
= last_text
[si
]) &&
888 (c2
= e1
->text
[si
]); si
++)
893 last_text
= e1
->text
;
897 listbox_select_entry ((WListbox
*) (h
->
901 last_text
= e1
->text
;
907 if (need_redraw
== 2) {
908 insert_text (input
, last_text
, low
);
909 send_message (h
, h
->current
->widget
, WIDGET_DRAW
, 0);
910 } else if (need_redraw
== 1) {
911 h
->ret_value
= B_ENTER
;
922 static int querylist_callback (void *data
)
927 #define DO_INSERTION 1
929 /* Returns 1 if the user would like to see us again */
931 complete_engine (WInput
*in
, int what_to_do
)
933 if (in
->completions
&& in
->point
!= end
)
934 free_completions (in
);
935 if (!in
->completions
){
937 for (start
= end
? end
- 1 : 0; start
> -1; start
--)
938 if (strchr (" \t;|<>", in
->buffer
[start
]))
942 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
944 if (in
->completions
){
945 if (what_to_do
& DO_INSERTION
) {
946 if (insert_text (in
, in
->completions
[0], strlen (in
->completions
[0]))){
947 if (in
->completions
[1])
950 free_completions (in
);
954 /* FIXME: evil evil evil. We do not go into the query completion engine
955 * because we do not have a Gtk dialog for it. Gtk-ted does not like
956 * this; if we enable this code, it will crash.
958 if ((what_to_do
& DO_QUERY
) && in
->completions
[1]) {
959 int maxlen
= 0, i
, count
= 0;
961 int start_x
, start_y
;
964 WListbox
*query_list
;
966 for (p
=in
->completions
+ 1; *p
; count
++, p
++)
967 if ((i
= strlen (*p
)) > maxlen
)
969 start_x
= in
->widget
.x
;
970 start_y
= in
->widget
.y
;
971 if (start_y
- 2 >= count
) {
972 y
= start_y
- 2 - count
;
975 if (start_y
>= LINES
- start_y
- 1) {
980 h
= LINES
- start_y
- 1;
983 x
= start
- in
->first_shown
- 2 + start_x
;
995 query_dlg
= create_dlg (y
, x
, query_height
, query_width
,
996 dialog_colors
, query_callback
,
997 "[Completion]", "complete", DLG_COMPACT
);
998 query_list
= listbox_new (1, 1, w
- 2, h
- 2, 0, querylist_callback
, NULL
);
999 add_widget (query_dlg
, query_list
);
1000 for (p
= in
->completions
+ 1; *p
; p
++)
1001 listbox_add_item (query_list
, 0, 0, *p
, NULL
);
1002 run_dlg (query_dlg
);
1004 if (query_dlg
->ret_value
== B_ENTER
){
1005 listbox_get_current (query_list
, &q
, NULL
);
1007 insert_text (in
, q
, strlen (q
));
1009 if (q
|| end
!= min_end
)
1010 free_completions (in
);
1011 i
= query_dlg
->ret_value
; /* B_USER if user wants to start over again */
1012 destroy_dlg (query_dlg
);
1021 void complete (WInput
*in
)
1023 if (in
->completions
)
1024 while (complete_engine (in
, DO_QUERY
));
1025 else if (show_all_if_ambiguous
){
1026 complete_engine (in
, DO_INSERTION
);
1027 while (complete_engine (in
, DO_QUERY
));
1029 complete_engine (in
, DO_INSERTION
);