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. */
24 * \brief Source: Input line filename/username/hostname/variable/command completion
34 #include <sys/types.h>
41 #include "../src/tty/tty.h"
42 #include "../src/tty/key.h" /* XCTRL and ALT macros */
43 #include "../vfs/vfs.h"
48 #include "main.h" /* show_all_if_ambiguous */
50 #include "../src/strescape.h"
53 typedef char *CompletionFunction (const char * text
, int state
, INPUT_COMPLETE_FLAGS flags
);
55 /* #define DO_COMPLETION_DEBUG */
56 #ifdef DO_COMPLETION_DEBUG
58 * Useful to print/debug completion flags
60 static const char * show_c_flags(INPUT_COMPLETE_FLAGS flags
)
62 static char s_cf
[] = "FHCVUDS";
64 s_cf
[0] = (flags
& INPUT_COMPLETE_FILENAMES
) ? 'F' : ' ';
65 s_cf
[1] = (flags
& INPUT_COMPLETE_HOSTNAMES
) ? 'H' : ' ';
66 s_cf
[2] = (flags
& INPUT_COMPLETE_COMMANDS
) ? 'C' : ' ';
67 s_cf
[3] = (flags
& INPUT_COMPLETE_VARIABLES
) ? 'V' : ' ';
68 s_cf
[4] = (flags
& INPUT_COMPLETE_USERNAMES
) ? 'U' : ' ';
69 s_cf
[5] = (flags
& INPUT_COMPLETE_CD
) ? 'D' : ' ';
70 s_cf
[6] = (flags
& INPUT_COMPLETE_SHELL_ESC
) ? 'S' : ' ';
74 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
76 #define SHOW_C_CTX(func)
77 #endif /* DO_CMPLETION_DEBUG */
80 filename_completion_function (const char * text
, int state
, INPUT_COMPLETE_FLAGS flags
)
82 static DIR *directory
;
83 static char *filename
= NULL
;
84 static char *dirname
= NULL
;
85 static char *users_dirname
= NULL
;
86 static size_t filename_len
;
87 int isdir
= 1, isexec
= 0;
89 struct dirent
*entry
= NULL
;
91 SHOW_C_CTX("filename_completion_function");
93 if (text
&& (flags
& INPUT_COMPLETE_SHELL_ESC
))
99 u_text
= strutils_shell_unescape (text
);
101 result
= filename_completion_function (u_text
, state
, flags
& (~INPUT_COMPLETE_SHELL_ESC
));
104 e_result
= strutils_shell_escape (result
);
110 /* If we're starting the match process, initialize us a bit. */
116 g_free (users_dirname
);
118 if ((*text
) && (temp
= strrchr (text
, PATH_SEP
))){
119 filename
= g_strdup (++temp
);
120 dirname
= g_strndup (text
, temp
- text
);
122 dirname
= g_strdup (".");
123 filename
= g_strdup (text
);
126 /* We aren't done yet. We also support the "~user" syntax. */
128 /* Save the version of the directory that the user typed. */
129 users_dirname
= dirname
;
130 dirname
= tilde_expand (dirname
);
131 canonicalize_pathname (dirname
);
133 /* Here we should do something with variable expansion
135 Maybe a dream - UNIMPLEMENTED yet. */
137 directory
= mc_opendir (dirname
);
138 filename_len
= strlen (filename
);
141 /* Now that we have some state, we can read the directory. */
143 while (directory
&& (entry
= mc_readdir (directory
))){
144 if (!str_is_valid_string (entry
->d_name
))
147 /* Special case for no filename.
148 All entries except "." and ".." match. */
149 if (filename_len
== 0) {
150 if (!strcmp (entry
->d_name
, ".") || !strcmp (entry
->d_name
, ".."))
153 /* Otherwise, if these match up to the length of filename, then
154 it may be a match. */
155 if ((entry
->d_name
[0] != filename
[0]) ||
156 ((NLENGTH (entry
)) < filename_len
) ||
157 strncmp (filename
, entry
->d_name
, filename_len
))
160 isdir
= 1; isexec
= 0;
163 struct stat tempstat
;
165 tmp
= g_strconcat (dirname
, PATH_SEP_STR
, entry
->d_name
, (char *) NULL
);
166 canonicalize_pathname (tmp
);
168 if (!mc_stat (tmp
, &tempstat
)){
169 uid_t my_uid
= getuid ();
170 gid_t my_gid
= getgid ();
172 if (!S_ISDIR (tempstat
.st_mode
)){
174 if ((!my_uid
&& (tempstat
.st_mode
& 0111)) ||
175 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100)) ||
176 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010)) ||
177 (tempstat
.st_mode
& 0001))
183 /* stat failed, strange. not a dir in any case */
188 if ((flags
& INPUT_COMPLETE_COMMANDS
)
189 && (isexec
|| isdir
))
191 if ((flags
& INPUT_COMPLETE_CD
)
194 if (flags
& (INPUT_COMPLETE_FILENAMES
))
200 mc_closedir (directory
);
207 g_free (users_dirname
);
208 users_dirname
= NULL
;
213 if (users_dirname
&& (users_dirname
[0] != '.' || users_dirname
[1])){
214 size_t dirlen
= strlen (users_dirname
);
215 temp
= g_malloc (3 + dirlen
+ NLENGTH (entry
));
216 strcpy (temp
, users_dirname
);
217 /* We need a `/' at the end. */
218 if (users_dirname
[dirlen
- 1] != PATH_SEP
){
219 temp
[dirlen
] = PATH_SEP
;
220 temp
[dirlen
+ 1] = 0;
222 strcat (temp
, entry
->d_name
);
224 temp
= g_malloc (2 + NLENGTH (entry
));
225 strcpy (temp
, entry
->d_name
);
228 strcat (temp
, PATH_SEP_STR
);
234 /* We assume here that text[0] == '~' , if you want to call it in another way,
235 you have to change the code */
237 username_completion_function (const char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
239 static struct passwd
*entry
;
240 static size_t userlen
;
243 SHOW_C_CTX("username_completion_function");
245 if (text
[0] == '\\' && text
[1] == '~')
247 if (!state
){ /* Initialization stuff */
249 userlen
= strlen (text
+ 1);
251 while ((entry
= getpwent ()) != NULL
){
252 /* Null usernames should result in all users as possible completions. */
255 if (text
[1] == entry
->pw_name
[0]
256 && !strncmp (text
+ 1, entry
->pw_name
, userlen
))
261 return g_strconcat ("~", entry
->pw_name
, PATH_SEP_STR
, (char *) NULL
);
267 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
268 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
269 extern char **environ
;
272 /* We assume text [0] == '$' and want to have a look at text [1], if it is
273 equal to '{', so that we should append '}' at the end */
275 variable_completion_function (const char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
278 static int varlen
, isbrace
;
279 const char *p
= NULL
;
282 SHOW_C_CTX("variable_completion_function");
284 if (!state
){ /* Initialization stuff */
285 isbrace
= (text
[1] == '{');
286 varlen
= strlen (text
+ 1 + isbrace
);
291 p
= strchr (*env_p
, '=');
292 if (p
&& p
- *env_p
>= varlen
&& !strncmp (text
+ 1 + isbrace
, *env_p
, varlen
))
300 char *temp
= g_malloc (2 + 2 * isbrace
+ p
- *env_p
);
305 memcpy (temp
+ 1 + isbrace
, *env_p
, p
- *env_p
);
307 strcpy (temp
+ 2 + (p
- *env_p
), "}");
309 temp
[1 + p
- *env_p
] = 0;
315 #define whitespace(c) ((c) == ' ' || (c) == '\t')
316 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
318 static char **hosts
= NULL
;
319 static char **hosts_p
= NULL
;
320 static int hosts_alloclen
= 0;
321 static void fetch_hosts (const char *filename
)
323 FILE *file
= fopen (filename
, "r");
324 char buffer
[256], *name
;
331 while (fgets (buffer
, 255, file
) != NULL
){
332 /* Skip to first character. */
334 bi
[0] != '\0' && str_isspace (bi
);
335 str_next_char (&bi
));
337 /* Ignore comments... */
340 /* Handle $include. */
341 if (!strncmp (bi
, "$include ", 9)){
342 char *includefile
= bi
+ 9;
345 /* Find start of filename. */
346 while (*includefile
&& whitespace (*includefile
))
350 /* Find end of filename. */
351 while (t
[0] != '\0' && !str_isspace (t
))
355 fetch_hosts (includefile
);
360 while (bi
[0] != '\0' && !str_isspace (bi
))
363 /* Get the host names separated by white space. */
364 while (bi
[0] != '\0' && bi
[0] != '#'){
365 while (bi
[0] != '\0' && str_isspace (bi
))
370 bi
[0] != '\0' && !str_isspace (bi
);
371 str_next_char (&bi
));
373 if (bi
- start
== 0) continue;
375 name
= g_strndup (start
, bi
- start
);
379 if (hosts_p
- hosts
>= hosts_alloclen
){
380 int j
= hosts_p
- hosts
;
382 hosts
= g_realloc ((void *)hosts
, ((hosts_alloclen
+= 30) + 1) * sizeof (char *));
385 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
386 if (!strcmp (name
, *host_p
))
387 break; /* We do not want any duplicates */
388 if (host_p
== hosts_p
){
400 hostname_completion_function (const char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
402 static char **host_p
;
403 static int textstart
, textlen
;
406 SHOW_C_CTX("hostname_completion_function");
408 if (!state
){ /* Initialization stuff */
412 for (host_p
= hosts
; *host_p
; host_p
++)
416 hosts
= g_new (char *, (hosts_alloclen
= 30) + 1);
419 fetch_hosts ((p
= getenv ("HOSTFILE")) ? p
: "/etc/hosts");
421 textstart
= (*text
== '@') ? 1 : 0;
422 textlen
= strlen (text
+ textstart
);
427 break; /* Match all of them */
428 else if (!strncmp (text
+ textstart
, *host_p
, textlen
))
434 for (host_p
= hosts
; *host_p
; host_p
++)
440 char *temp
= g_malloc (2 + strlen (*host_p
));
444 strcpy (temp
+ textstart
, *host_p
);
451 * This is the function to call when the word to complete is in a position
452 * where a command word can be found. It looks around $PATH, looking for
453 * commands that match. It also scans aliases, function names, and the
454 * table of shell built-ins.
457 command_completion_function (const char *_text
, int state
, INPUT_COMPLETE_FLAGS flags
)
460 static const char *path_end
;
461 static gboolean isabsolute
;
464 static const char *const *words
;
466 static char *cur_path
;
467 static char *cur_word
;
468 static int init_state
;
469 static const char *const bash_reserved
[] = {
470 "if", "then", "else", "elif", "fi", "case", "esac", "for",
471 "select", "while", "until", "do", "done", "in", "function", 0
473 static const char *const bash_builtins
[] = {
474 "alias", "bg", "bind", "break", "builtin", "cd", "command",
475 "continue", "declare", "dirs", "echo", "enable", "eval",
476 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
477 "help", "history", "jobs", "kill", "let", "local", "logout",
478 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
479 "shift", "source", "suspend", "test", "times", "trap", "type",
480 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
484 SHOW_C_CTX("command_completion_function");
486 if (!(flags
& INPUT_COMPLETE_COMMANDS
))
488 text
= strutils_shell_unescape(_text
);
489 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
491 if (!state
) { /* Initialize us a little bit */
492 isabsolute
= strchr (text
, PATH_SEP
) != NULL
;
494 words
= bash_reserved
;
496 text_len
= strlen (text
);
497 if (!path
&& (path
= g_strdup (getenv ("PATH"))) != NULL
) {
499 path_end
= strchr (p
, 0);
500 while ((p
= strchr (p
, PATH_ENV_SEP
))) {
508 p
= filename_completion_function (text
, state
, flags
);
512 p
= strutils_shell_escape (p
);
522 case 0: /* Reserved words */
524 if (!strncmp (*words
, text
, text_len
))
525 return g_strdup (*(words
++));
529 words
= bash_builtins
;
530 case 1: /* Builtin commands */
532 if (!strncmp (*words
, text
, text_len
))
533 return g_strdup (*(words
++));
541 case 2: /* And looking through the $PATH */
546 if (cur_path
>= path_end
)
548 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
549 cur_word
= concat_dir_and_file (expanded
, text
);
551 canonicalize_pathname (cur_word
);
552 cur_path
= strchr (cur_path
, 0) + 1;
556 filename_completion_function (cur_word
,
557 state
- init_state
, flags
);
568 } else if ((p
= strrchr (found
, PATH_SEP
)) != NULL
) {
570 found
= strutils_shell_escape (p
+ 1);
579 match_compare (const void *a
, const void *b
)
581 return strcmp (*(char **)a
, *(char **)b
);
584 /* Returns an array of char * matches with the longest common denominator
585 in the 1st entry. Then a NULL terminated list of different possible
587 You have to supply your own CompletionFunction with the word you
588 want to complete as the first argument and an count of previous matches
590 In case no matches were found we return NULL. */
592 completion_matches (const char *text
, CompletionFunction entry_function
, INPUT_COMPLETE_FLAGS flags
)
594 /* Number of slots in match_list. */
597 /* The list of matches. */
598 char **match_list
= g_new (char *, (match_list_size
= 30) + 1);
600 /* Number of matches actually found. */
603 /* Temporary string binder. */
606 match_list
[1] = NULL
;
608 while ((string
= (*entry_function
) (text
, matches
, flags
)) != NULL
){
609 if (matches
+ 1 == match_list_size
)
610 match_list
= (char **) g_realloc (match_list
, ((match_list_size
+= 30) + 1) * sizeof (char *));
611 match_list
[++matches
] = string
;
612 match_list
[matches
+ 1] = NULL
;
615 /* If there were any matches, then look through them finding out the
616 lowest common denominator. That then becomes match_list[0]. */
620 int low
= 4096; /* Count of max-matched characters. */
622 /* If only one match, just use that. */
624 match_list
[0] = match_list
[1];
625 match_list
[1] = NULL
;
629 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
631 /* And compare each member of the list with
632 the next, finding out where they stop matching.
633 If we find two equal strings, we have to put one away... */
636 while (j
< matches
+ 1)
641 for (si
= match_list
[i
], sj
= match_list
[j
];
644 ni
= str_get_next_char (si
);
645 nj
= str_get_next_char (sj
);
647 if (ni
- si
!= nj
- sj
) break;
648 if (strncmp (si
, sj
, ni
- si
) != 0) break;
654 if (si
[0] == '\0' && sj
[0] == '\0'){ /* Two equal strings */
655 g_free (match_list
[j
]);
659 continue; /* Look for a run of equal strings */
661 if (low
> si
- match_list
[i
]) low
= si
- match_list
[i
];
662 if (i
+ 1 != j
) /* So there's some gap */
663 match_list
[i
+ 1] = match_list
[j
];
667 match_list
[matches
+ 1] = NULL
;
668 match_list
[0] = g_strndup(match_list
[1], low
);
670 } else { /* There were no matches. */
677 /* Check if directory completion is needed */
679 check_is_cd (const char *text
, int start
, INPUT_COMPLETE_FLAGS flags
)
684 SHOW_C_CTX("check_is_cd");
685 if (!(flags
& INPUT_COMPLETE_CD
))
688 /* Skip initial spaces */
690 q
= (char*)text
+ start
;
691 while (p
< q
&& p
[0] != '\0' && str_isspace (p
))
694 /* Check if the command is "cd" and the cursor is after it */
699 text
+= str_isspace (p
);
700 if (test
== 3 && (p
< q
))
706 /* Returns an array of matches, or NULL if none. */
708 try_complete (char *text
, int *start
, int *end
, INPUT_COMPLETE_FLAGS flags
)
710 int in_command_position
= 0;
712 char **matches
= NULL
;
713 const char *command_separator_chars
= ";|&{(`";
714 char *p
= NULL
, *q
= NULL
, *r
= NULL
;
715 int is_cd
= check_is_cd (text
, *start
, flags
);
718 SHOW_C_CTX("try_complete");
719 word
= g_strndup (text
+ *start
, *end
- *start
);
721 /* Determine if this could be a command word. It is if it appears at
722 the start of the line (ignoring preceding whitespace), or if it
723 appears after a character that separates commands. And we have to
724 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
725 if (!is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
)){
726 ti
= str_get_prev_char (&text
[*start
]);
727 while (ti
> text
&& (ti
[0] == ' ' || ti
[0] == '\t'))
729 if (ti
<= text
&& (ti
[0] == ' ' || ti
[0] == '\t'))
730 in_command_position
++;
731 else if (strchr (command_separator_chars
, ti
[0])){
732 register int this_char
, prev_char
;
734 in_command_position
++;
737 /* Handle the two character tokens `>&', `<&', and `>|'.
738 We are not in a command position after one of these. */
740 prev_char
= str_get_prev_char (ti
)[0];
742 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>')) ||
743 (this_char
== '|' && prev_char
== '>'))
744 in_command_position
= 0;
746 else if (ti
> text
&& str_get_prev_char (ti
)[0] == '\\') /* Quoted */
747 in_command_position
= 0;
752 if (flags
& INPUT_COMPLETE_COMMANDS
)
753 p
= strrchr (word
, '`');
754 if (flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
755 q
= strrchr (word
, '$');
756 if (flags
& INPUT_COMPLETE_HOSTNAMES
)
757 r
= strrchr (word
, '@');
758 if (q
&& q
[1] == '(' && INPUT_COMPLETE_COMMANDS
){
760 p
= str_get_next_char (q
);
764 /* Command substitution? */
766 SHOW_C_CTX("try_complete:cmd_backq_subst");
767 matches
= completion_matches (str_cget_next_char (p
),
768 command_completion_function
,
769 flags
& (~INPUT_COMPLETE_FILENAMES
));
771 *start
+= str_get_next_char (p
) - word
;
775 else if (q
> p
&& q
> r
){
776 SHOW_C_CTX("try_complete:var_subst");
777 matches
= completion_matches (q
, variable_completion_function
, flags
);
782 /* Starts with '@', then look through the known hostnames for
784 else if (r
> p
&& r
> q
){
785 SHOW_C_CTX("try_complete:host_subst");
786 matches
= completion_matches (r
, hostname_completion_function
, flags
);
791 /* Starts with `~' and there is no slash in the word, then
792 try completing this word as a username. */
793 if (!matches
&& *word
== '~' && (flags
& INPUT_COMPLETE_USERNAMES
) && !strchr (word
, PATH_SEP
))
795 SHOW_C_CTX("try_complete:user_subst");
796 matches
= completion_matches (word
, username_completion_function
, flags
);
800 /* And finally if this word is in a command position, then
801 complete over possible command names, including aliases, functions,
802 and command names. */
803 if (!matches
&& in_command_position
)
805 SHOW_C_CTX("try_complete:cmd_subst");
806 matches
= completion_matches (word
, command_completion_function
, flags
& (~INPUT_COMPLETE_FILENAMES
));
809 else if (!matches
&& (flags
& INPUT_COMPLETE_FILENAMES
)){
811 flags
&= ~(INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_COMMANDS
);
812 SHOW_C_CTX("try_complete:filename_subst_1");
813 matches
= completion_matches (word
, filename_completion_function
, flags
);
814 if (!matches
&& is_cd
&& *word
!= PATH_SEP
&& *word
!= '~'){
816 for (p
= text
; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); str_next_char (&p
));
817 if (!strncmp (p
, "cd", 2))
818 for (p
+= 2; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); str_next_char (&p
));
820 char * const cdpath_ref
= g_strdup (getenv ("CDPATH"));
821 char *cdpath
= cdpath_ref
;
828 while (!matches
&& c
== ':'){
829 s
= strchr (cdpath
, ':');
831 s
= strchr (cdpath
, 0);
835 r
= concat_dir_and_file (cdpath
, word
);
836 SHOW_C_CTX("try_complete:filename_subst_2");
837 matches
= completion_matches (r
, filename_completion_function
, flags
);
841 cdpath
= str_get_next_char (s
);
853 void free_completions (WInput
*in
)
857 if (!in
->completions
)
859 for (p
=in
->completions
; *p
; p
++)
861 g_free (in
->completions
);
862 in
->completions
= NULL
;
865 static int query_height
, query_width
;
866 static WInput
*input
;
868 static int start
, end
;
871 insert_text (WInput
*in
, char *text
, ssize_t size
)
873 int buff_len
= str_length (in
->buffer
);
875 size
= min (size
, (ssize_t
) strlen (text
)) + start
- end
;
876 if (strlen (in
->buffer
) + size
>= (size_t) in
->current_max_size
){
877 /* Expand the buffer */
878 char *narea
= g_try_realloc (in
->buffer
, in
->current_max_size
879 + size
+ in
->field_width
);
882 in
->current_max_size
+= size
+ in
->field_width
;
885 if (strlen (in
->buffer
)+1 < (size_t) in
->current_max_size
){
887 int i
= strlen (&in
->buffer
[end
]);
889 in
->buffer
[end
+ size
+ i
] = in
->buffer
[end
+ i
];
890 } else if (size
< 0){
891 char *p
= in
->buffer
+ end
+ size
, *q
= in
->buffer
+ end
;
896 memcpy (in
->buffer
+ start
, text
, size
- start
+ end
);
897 in
->point
+= str_length (in
->buffer
) - buff_len
;
898 update_input (in
, 1);
905 query_callback (Dlg_head
*h
, Widget
*sender
,
906 dlg_msg_t msg
, int parm
, void *data
)
908 static char buff
[MB_LEN_MAX
] = "";
923 if (end
== min_end
) {
930 e1
= e
= ((WListbox
*) (h
->current
))->list
;
932 if (!strncmp (input
->buffer
+ start
,
933 e1
->text
, end
- start
- 1)) {
935 listbox_select_entry ((WListbox
*) (h
->current
), e1
);
936 end
= str_get_prev_char (&(input
->buffer
[end
]))
938 handle_char (input
, parm
);
939 send_message (h
->current
, WIDGET_DRAW
, 0);
948 if (parm
< 32 || parm
> 256) {
950 if (is_in_input_map (input
, parm
) == 2) {
953 h
->ret_value
= B_USER
; /* This means we want to refill the
954 list box and start again */
958 return MSG_NOT_HANDLED
;
963 char *last_text
= NULL
;
965 buff
[bl
] = (char) parm
;
968 switch (str_is_valid_char (buff
, bl
)) {
975 e1
= e
= ((WListbox
*) (h
->current
))->list
;
977 if (!strncmp (input
->buffer
+ start
,
978 e1
->text
, end
- start
)) {
980 if (strncmp (&e1
->text
[end
- start
], buff
, bl
) == 0) {
984 si
= &(e1
->text
[end
- start
]);
985 sl
= &(last_text
[end
- start
]);
987 for (; si
[0] != '\0' && sl
[0] != '\0';) {
989 ni
= str_get_next_char (si
);
990 nl
= str_get_next_char (sl
);
992 if (ni
- si
!= nl
- sl
) break;
993 if (strncmp (si
, sl
, ni
- si
) != 0) break;
999 if (low
> si
- &e1
->text
[end
- start
])
1000 low
= si
- &e1
->text
[end
- start
];
1002 last_text
= e1
->text
;
1006 listbox_select_entry ((WListbox
*) (h
->
1009 last_text
= e1
->text
;
1015 if (need_redraw
== 2) {
1016 insert_text (input
, last_text
, low
);
1017 send_message (h
->current
, WIDGET_DRAW
, 0);
1018 } else if (need_redraw
== 1) {
1019 h
->ret_value
= B_ENTER
;
1029 return default_dlg_callback (h
, sender
, msg
, parm
, data
);
1033 #define DO_INSERTION 1
1035 /* Returns 1 if the user would like to see us again */
1037 complete_engine (WInput
*in
, int what_to_do
)
1041 if (in
->completions
&& (str_offset_to_pos (in
->buffer
, in
->point
)) != end
)
1042 free_completions (in
);
1043 if (!in
->completions
){
1044 end
= str_offset_to_pos (in
->buffer
, in
->point
);
1045 for (s
= in
->point
? in
->point
- 1 : 0; s
>= 0; s
--) {
1046 start
= str_offset_to_pos (in
->buffer
, s
);
1047 if (strchr (" \t;|<>", in
->buffer
[start
])) {
1048 if (start
< end
) start
= str_offset_to_pos (in
->buffer
, s
+ 1);
1049 /* FIXME: maybe need check '\\' prev char
1050 if (start > 0 && in->buffer [start-1] == '\\')
1055 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1058 if (in
->completions
){
1059 if (what_to_do
& DO_INSERTION
|| ((what_to_do
& DO_QUERY
) && !in
->completions
[1])) {
1060 char * lc_complete
= in
->completions
[0];
1061 if (insert_text (in
, lc_complete
, strlen (lc_complete
))){
1062 if (in
->completions
[1])
1065 free_completions (in
);
1069 if ((what_to_do
& DO_QUERY
) && in
->completions
&& in
->completions
[1]) {
1070 int maxlen
= 0, i
, count
= 0;
1072 int start_x
, start_y
;
1074 Dlg_head
*query_dlg
;
1075 WListbox
*query_list
;
1077 for (p
=in
->completions
+ 1; *p
; count
++, p
++)
1078 if ((i
= str_term_width1 (*p
)) > maxlen
)
1080 start_x
= in
->widget
.x
;
1081 start_y
= in
->widget
.y
;
1082 if (start_y
- 2 >= count
) {
1083 y
= start_y
- 2 - count
;
1086 if (start_y
>= LINES
- start_y
- 1) {
1091 h
= LINES
- start_y
- 1;
1094 x
= start
- in
->term_first_shown
- 2 + start_x
;
1106 query_dlg
= create_dlg (y
, x
, query_height
, query_width
,
1107 dialog_colors
, query_callback
,
1108 "[Completion]", NULL
, DLG_COMPACT
);
1109 query_list
= listbox_new (1, 1, h
- 2, w
- 2, NULL
);
1110 add_widget (query_dlg
, query_list
);
1111 for (p
= in
->completions
+ 1; *p
; p
++)
1112 listbox_add_item (query_list
, 0, 0, *p
, NULL
);
1113 run_dlg (query_dlg
);
1115 if (query_dlg
->ret_value
== B_ENTER
){
1116 listbox_get_current (query_list
, &q
, NULL
);
1118 insert_text (in
, q
, strlen (q
));
1120 if (q
|| end
!= min_end
)
1121 free_completions (in
);
1122 i
= query_dlg
->ret_value
; /* B_USER if user wants to start over again */
1123 destroy_dlg (query_dlg
);
1132 void complete (WInput
*in
)
1136 if (!str_is_valid_string (in
->buffer
)) return;
1138 if (in
->completions
)
1139 engine_flags
= DO_QUERY
;
1142 engine_flags
= DO_INSERTION
;
1144 if (show_all_if_ambiguous
)
1145 engine_flags
|= DO_QUERY
;
1148 while (complete_engine (in
, engine_flags
));