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>
39 #include "lib/global.h"
41 #include "lib/tty/tty.h"
42 #include "lib/tty/key.h" /* XCTRL and ALT macros */
43 #include "lib/vfs/mc-vfs/vfs.h"
44 #include "lib/strescape.h"
45 #include "lib/strutil.h"
50 #include "main.h" /* show_all_if_ambiguous */
52 typedef char *CompletionFunction (const char * text
, int state
, INPUT_COMPLETE_FLAGS flags
);
54 /* #define DO_COMPLETION_DEBUG */
55 #ifdef DO_COMPLETION_DEBUG
57 * Useful to print/debug completion flags
59 static const char * show_c_flags(INPUT_COMPLETE_FLAGS flags
)
61 static char s_cf
[] = "FHCVUDS";
63 s_cf
[0] = (flags
& INPUT_COMPLETE_FILENAMES
) ? 'F' : ' ';
64 s_cf
[1] = (flags
& INPUT_COMPLETE_HOSTNAMES
) ? 'H' : ' ';
65 s_cf
[2] = (flags
& INPUT_COMPLETE_COMMANDS
) ? 'C' : ' ';
66 s_cf
[3] = (flags
& INPUT_COMPLETE_VARIABLES
) ? 'V' : ' ';
67 s_cf
[4] = (flags
& INPUT_COMPLETE_USERNAMES
) ? 'U' : ' ';
68 s_cf
[5] = (flags
& INPUT_COMPLETE_CD
) ? 'D' : ' ';
69 s_cf
[6] = (flags
& INPUT_COMPLETE_SHELL_ESC
) ? 'S' : ' ';
73 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
75 #define SHOW_C_CTX(func)
76 #endif /* DO_CMPLETION_DEBUG */
79 filename_completion_function (const char * text
, int state
, INPUT_COMPLETE_FLAGS flags
)
81 static DIR *directory
;
82 static char *filename
= NULL
;
83 static char *dirname
= NULL
;
84 static char *users_dirname
= NULL
;
85 static size_t filename_len
;
86 int isdir
= 1, isexec
= 0;
88 struct dirent
*entry
= NULL
;
90 SHOW_C_CTX("filename_completion_function");
92 if (text
&& (flags
& INPUT_COMPLETE_SHELL_ESC
))
98 u_text
= strutils_shell_unescape (text
);
100 result
= filename_completion_function (u_text
, state
, flags
& (~INPUT_COMPLETE_SHELL_ESC
));
103 e_result
= strutils_shell_escape (result
);
109 /* If we're starting the match process, initialize us a bit. */
115 g_free (users_dirname
);
117 if ((*text
) && (temp
= strrchr (text
, PATH_SEP
))){
118 filename
= g_strdup (++temp
);
119 dirname
= g_strndup (text
, temp
- text
);
121 dirname
= g_strdup (".");
122 filename
= g_strdup (text
);
125 /* We aren't done yet. We also support the "~user" syntax. */
127 /* Save the version of the directory that the user typed. */
128 users_dirname
= dirname
;
129 dirname
= tilde_expand (dirname
);
130 canonicalize_pathname (dirname
);
132 /* Here we should do something with variable expansion
134 Maybe a dream - UNIMPLEMENTED yet. */
136 directory
= mc_opendir (dirname
);
137 filename_len
= strlen (filename
);
140 /* Now that we have some state, we can read the directory. */
142 while (directory
&& (entry
= mc_readdir (directory
))){
143 if (!str_is_valid_string (entry
->d_name
))
146 /* Special case for no filename.
147 All entries except "." and ".." match. */
148 if (filename_len
== 0) {
149 if (!strcmp (entry
->d_name
, ".") || !strcmp (entry
->d_name
, ".."))
152 /* Otherwise, if these match up to the length of filename, then
153 it may be a match. */
154 if ((entry
->d_name
[0] != filename
[0]) ||
155 ((NLENGTH (entry
)) < filename_len
) ||
156 strncmp (filename
, entry
->d_name
, filename_len
))
159 isdir
= 1; isexec
= 0;
162 struct stat tempstat
;
164 tmp
= g_strconcat (dirname
, PATH_SEP_STR
, entry
->d_name
, (char *) NULL
);
165 canonicalize_pathname (tmp
);
167 if (!mc_stat (tmp
, &tempstat
)){
168 uid_t my_uid
= getuid ();
169 gid_t my_gid
= getgid ();
171 if (!S_ISDIR (tempstat
.st_mode
)){
173 if ((!my_uid
&& (tempstat
.st_mode
& 0111)) ||
174 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100)) ||
175 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010)) ||
176 (tempstat
.st_mode
& 0001))
182 /* stat failed, strange. not a dir in any case */
187 if ((flags
& INPUT_COMPLETE_COMMANDS
)
188 && (isexec
|| isdir
))
190 if ((flags
& INPUT_COMPLETE_CD
)
193 if (flags
& (INPUT_COMPLETE_FILENAMES
))
199 mc_closedir (directory
);
206 g_free (users_dirname
);
207 users_dirname
= NULL
;
212 if (users_dirname
&& (users_dirname
[0] != '.' || users_dirname
[1])){
213 size_t dirlen
= strlen (users_dirname
);
214 temp
= g_malloc (3 + dirlen
+ NLENGTH (entry
));
215 strcpy (temp
, users_dirname
);
216 /* We need a `/' at the end. */
217 if (users_dirname
[dirlen
- 1] != PATH_SEP
){
218 temp
[dirlen
] = PATH_SEP
;
219 temp
[dirlen
+ 1] = 0;
221 strcat (temp
, entry
->d_name
);
223 temp
= g_malloc (2 + NLENGTH (entry
));
224 strcpy (temp
, entry
->d_name
);
227 strcat (temp
, PATH_SEP_STR
);
233 /* We assume here that text[0] == '~' , if you want to call it in another way,
234 you have to change the code */
236 username_completion_function (const char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
238 static struct passwd
*entry
;
239 static size_t userlen
;
242 SHOW_C_CTX("username_completion_function");
244 if (text
[0] == '\\' && text
[1] == '~')
246 if (!state
){ /* Initialization stuff */
248 userlen
= strlen (text
+ 1);
250 while ((entry
= getpwent ()) != NULL
){
251 /* Null usernames should result in all users as possible completions. */
254 if (text
[1] == entry
->pw_name
[0]
255 && !strncmp (text
+ 1, entry
->pw_name
, userlen
))
260 return g_strconcat ("~", entry
->pw_name
, PATH_SEP_STR
, (char *) NULL
);
266 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
267 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
268 extern char **environ
;
271 /* We assume text [0] == '$' and want to have a look at text [1], if it is
272 equal to '{', so that we should append '}' at the end */
274 variable_completion_function (const char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
277 static int varlen
, isbrace
;
278 const char *p
= NULL
;
281 SHOW_C_CTX("variable_completion_function");
283 if (!state
){ /* Initialization stuff */
284 isbrace
= (text
[1] == '{');
285 varlen
= strlen (text
+ 1 + isbrace
);
290 p
= strchr (*env_p
, '=');
291 if (p
&& p
- *env_p
>= varlen
&& !strncmp (text
+ 1 + isbrace
, *env_p
, varlen
))
299 char *temp
= g_malloc (2 + 2 * isbrace
+ p
- *env_p
);
304 memcpy (temp
+ 1 + isbrace
, *env_p
, p
- *env_p
);
306 strcpy (temp
+ 2 + (p
- *env_p
), "}");
308 temp
[1 + p
- *env_p
] = 0;
314 #define whitespace(c) ((c) == ' ' || (c) == '\t')
315 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
317 static char **hosts
= NULL
;
318 static char **hosts_p
= NULL
;
319 static int hosts_alloclen
= 0;
320 static void fetch_hosts (const char *filename
)
322 FILE *file
= fopen (filename
, "r");
323 char buffer
[256], *name
;
330 while (fgets (buffer
, 255, file
) != NULL
){
331 /* Skip to first character. */
333 bi
[0] != '\0' && str_isspace (bi
);
334 str_next_char (&bi
));
336 /* Ignore comments... */
339 /* Handle $include. */
340 if (!strncmp (bi
, "$include ", 9)){
341 char *includefile
= bi
+ 9;
344 /* Find start of filename. */
345 while (*includefile
&& whitespace (*includefile
))
349 /* Find end of filename. */
350 while (t
[0] != '\0' && !str_isspace (t
))
354 fetch_hosts (includefile
);
359 while (bi
[0] != '\0' && !str_isspace (bi
))
362 /* Get the host names separated by white space. */
363 while (bi
[0] != '\0' && bi
[0] != '#'){
364 while (bi
[0] != '\0' && str_isspace (bi
))
369 bi
[0] != '\0' && !str_isspace (bi
);
370 str_next_char (&bi
));
372 if (bi
- start
== 0) continue;
374 name
= g_strndup (start
, bi
- start
);
378 if (hosts_p
- hosts
>= hosts_alloclen
){
379 int j
= hosts_p
- hosts
;
381 hosts
= g_realloc ((void *)hosts
, ((hosts_alloclen
+= 30) + 1) * sizeof (char *));
384 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
385 if (!strcmp (name
, *host_p
))
386 break; /* We do not want any duplicates */
387 if (host_p
== hosts_p
){
399 hostname_completion_function (const char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
401 static char **host_p
;
402 static int textstart
, textlen
;
405 SHOW_C_CTX("hostname_completion_function");
407 if (!state
){ /* Initialization stuff */
411 for (host_p
= hosts
; *host_p
; host_p
++)
415 hosts
= g_new (char *, (hosts_alloclen
= 30) + 1);
418 fetch_hosts ((p
= getenv ("HOSTFILE")) ? p
: "/etc/hosts");
420 textstart
= (*text
== '@') ? 1 : 0;
421 textlen
= strlen (text
+ textstart
);
426 break; /* Match all of them */
427 else if (!strncmp (text
+ textstart
, *host_p
, textlen
))
433 for (host_p
= hosts
; *host_p
; host_p
++)
439 char *temp
= g_malloc (2 + strlen (*host_p
));
443 strcpy (temp
+ textstart
, *host_p
);
450 * This is the function to call when the word to complete is in a position
451 * where a command word can be found. It looks around $PATH, looking for
452 * commands that match. It also scans aliases, function names, and the
453 * table of shell built-ins.
456 command_completion_function (const char *_text
, int state
, INPUT_COMPLETE_FLAGS flags
)
459 static const char *path_end
;
460 static gboolean isabsolute
;
463 static const char *const *words
;
465 static char *cur_path
;
466 static char *cur_word
;
467 static int init_state
;
468 static const char *const bash_reserved
[] = {
469 "if", "then", "else", "elif", "fi", "case", "esac", "for",
470 "select", "while", "until", "do", "done", "in", "function", 0
472 static const char *const bash_builtins
[] = {
473 "alias", "bg", "bind", "break", "builtin", "cd", "command",
474 "continue", "declare", "dirs", "echo", "enable", "eval",
475 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
476 "help", "history", "jobs", "kill", "let", "local", "logout",
477 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
478 "shift", "source", "suspend", "test", "times", "trap", "type",
479 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
483 SHOW_C_CTX("command_completion_function");
485 if (!(flags
& INPUT_COMPLETE_COMMANDS
))
487 text
= strutils_shell_unescape(_text
);
488 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
490 if (!state
) { /* Initialize us a little bit */
491 isabsolute
= strchr (text
, PATH_SEP
) != NULL
;
493 words
= bash_reserved
;
495 text_len
= strlen (text
);
496 if (!path
&& (path
= g_strdup (getenv ("PATH"))) != NULL
) {
498 path_end
= strchr (p
, 0);
499 while ((p
= strchr (p
, PATH_ENV_SEP
))) {
507 p
= filename_completion_function (text
, state
, flags
);
511 p
= strutils_shell_escape (p
);
521 case 0: /* Reserved words */
523 if (!strncmp (*words
, text
, text_len
))
524 return g_strdup (*(words
++));
528 words
= bash_builtins
;
529 case 1: /* Builtin commands */
531 if (!strncmp (*words
, text
, text_len
))
532 return g_strdup (*(words
++));
540 case 2: /* And looking through the $PATH */
545 if (cur_path
>= path_end
)
547 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
548 cur_word
= concat_dir_and_file (expanded
, text
);
550 canonicalize_pathname (cur_word
);
551 cur_path
= strchr (cur_path
, 0) + 1;
555 filename_completion_function (cur_word
,
556 state
- init_state
, flags
);
567 } else if ((p
= strrchr (found
, PATH_SEP
)) != NULL
) {
569 found
= strutils_shell_escape (p
+ 1);
578 match_compare (const void *a
, const void *b
)
580 return strcmp (*(char **)a
, *(char **)b
);
583 /* Returns an array of char * matches with the longest common denominator
584 in the 1st entry. Then a NULL terminated list of different possible
586 You have to supply your own CompletionFunction with the word you
587 want to complete as the first argument and an count of previous matches
589 In case no matches were found we return NULL. */
591 completion_matches (const char *text
, CompletionFunction entry_function
, INPUT_COMPLETE_FLAGS flags
)
593 /* Number of slots in match_list. */
596 /* The list of matches. */
597 char **match_list
= g_new (char *, (match_list_size
= 30) + 1);
599 /* Number of matches actually found. */
602 /* Temporary string binder. */
605 match_list
[1] = NULL
;
607 while ((string
= (*entry_function
) (text
, matches
, flags
)) != NULL
){
608 if (matches
+ 1 == match_list_size
)
609 match_list
= (char **) g_realloc (match_list
, ((match_list_size
+= 30) + 1) * sizeof (char *));
610 match_list
[++matches
] = string
;
611 match_list
[matches
+ 1] = NULL
;
614 /* If there were any matches, then look through them finding out the
615 lowest common denominator. That then becomes match_list[0]. */
619 int low
= 4096; /* Count of max-matched characters. */
621 /* If only one match, just use that. */
623 match_list
[0] = match_list
[1];
624 match_list
[1] = NULL
;
628 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
630 /* And compare each member of the list with
631 the next, finding out where they stop matching.
632 If we find two equal strings, we have to put one away... */
635 while (j
< matches
+ 1)
640 for (si
= match_list
[i
], sj
= match_list
[j
];
643 ni
= str_get_next_char (si
);
644 nj
= str_get_next_char (sj
);
646 if (ni
- si
!= nj
- sj
) break;
647 if (strncmp (si
, sj
, ni
- si
) != 0) break;
653 if (si
[0] == '\0' && sj
[0] == '\0'){ /* Two equal strings */
654 g_free (match_list
[j
]);
658 continue; /* Look for a run of equal strings */
660 if (low
> si
- match_list
[i
]) low
= si
- match_list
[i
];
661 if (i
+ 1 != j
) /* So there's some gap */
662 match_list
[i
+ 1] = match_list
[j
];
666 match_list
[matches
+ 1] = NULL
;
667 match_list
[0] = g_strndup(match_list
[1], low
);
669 } else { /* There were no matches. */
676 /* Check if directory completion is needed */
678 check_is_cd (const char *text
, int start
, INPUT_COMPLETE_FLAGS flags
)
683 SHOW_C_CTX("check_is_cd");
684 if (!(flags
& INPUT_COMPLETE_CD
))
687 /* Skip initial spaces */
689 q
= (char*)text
+ start
;
690 while (p
< q
&& p
[0] != '\0' && str_isspace (p
))
693 /* Check if the command is "cd" and the cursor is after it */
698 text
+= str_isspace (p
);
699 if (test
== 3 && (p
< q
))
705 /* Returns an array of matches, or NULL if none. */
707 try_complete (char *text
, int *start
, int *end
, INPUT_COMPLETE_FLAGS flags
)
709 int in_command_position
= 0;
711 char **matches
= NULL
;
712 const char *command_separator_chars
= ";|&{(`";
713 char *p
= NULL
, *q
= NULL
, *r
= NULL
;
714 int is_cd
= check_is_cd (text
, *start
, flags
);
717 SHOW_C_CTX("try_complete");
718 word
= g_strndup (text
+ *start
, *end
- *start
);
720 /* Determine if this could be a command word. It is if it appears at
721 the start of the line (ignoring preceding whitespace), or if it
722 appears after a character that separates commands. And we have to
723 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
724 if (!is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
)){
725 ti
= str_get_prev_char (&text
[*start
]);
726 while (ti
> text
&& (ti
[0] == ' ' || ti
[0] == '\t'))
728 if (ti
<= text
&& (ti
[0] == ' ' || ti
[0] == '\t'))
729 in_command_position
++;
730 else if (strchr (command_separator_chars
, ti
[0])){
731 register int this_char
, prev_char
;
733 in_command_position
++;
736 /* Handle the two character tokens `>&', `<&', and `>|'.
737 We are not in a command position after one of these. */
739 prev_char
= str_get_prev_char (ti
)[0];
741 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>')) ||
742 (this_char
== '|' && prev_char
== '>'))
743 in_command_position
= 0;
745 else if (ti
> text
&& str_get_prev_char (ti
)[0] == '\\') /* Quoted */
746 in_command_position
= 0;
751 if (flags
& INPUT_COMPLETE_COMMANDS
)
752 p
= strrchr (word
, '`');
753 if (flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
754 q
= strrchr (word
, '$');
755 if (flags
& INPUT_COMPLETE_HOSTNAMES
)
756 r
= strrchr (word
, '@');
757 if (q
&& q
[1] == '(' && INPUT_COMPLETE_COMMANDS
){
759 p
= str_get_next_char (q
);
763 /* Command substitution? */
765 SHOW_C_CTX("try_complete:cmd_backq_subst");
766 matches
= completion_matches (str_cget_next_char (p
),
767 command_completion_function
,
768 flags
& (~INPUT_COMPLETE_FILENAMES
));
770 *start
+= str_get_next_char (p
) - word
;
774 else if (q
> p
&& q
> r
){
775 SHOW_C_CTX("try_complete:var_subst");
776 matches
= completion_matches (q
, variable_completion_function
, flags
);
781 /* Starts with '@', then look through the known hostnames for
783 else if (r
> p
&& r
> q
){
784 SHOW_C_CTX("try_complete:host_subst");
785 matches
= completion_matches (r
, hostname_completion_function
, flags
);
790 /* Starts with `~' and there is no slash in the word, then
791 try completing this word as a username. */
792 if (!matches
&& *word
== '~' && (flags
& INPUT_COMPLETE_USERNAMES
) && !strchr (word
, PATH_SEP
))
794 SHOW_C_CTX("try_complete:user_subst");
795 matches
= completion_matches (word
, username_completion_function
, flags
);
799 /* And finally if this word is in a command position, then
800 complete over possible command names, including aliases, functions,
801 and command names. */
802 if (!matches
&& in_command_position
)
804 SHOW_C_CTX("try_complete:cmd_subst");
805 matches
= completion_matches (word
, command_completion_function
, flags
& (~INPUT_COMPLETE_FILENAMES
));
808 else if (!matches
&& (flags
& INPUT_COMPLETE_FILENAMES
)){
810 flags
&= ~(INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_COMMANDS
);
811 SHOW_C_CTX("try_complete:filename_subst_1");
812 matches
= completion_matches (word
, filename_completion_function
, flags
);
813 if (!matches
&& is_cd
&& *word
!= PATH_SEP
&& *word
!= '~'){
815 for (p
= text
; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); str_next_char (&p
));
816 if (!strncmp (p
, "cd", 2))
817 for (p
+= 2; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); str_next_char (&p
));
819 char * const cdpath_ref
= g_strdup (getenv ("CDPATH"));
820 char *cdpath
= cdpath_ref
;
827 while (!matches
&& c
== ':'){
828 s
= strchr (cdpath
, ':');
830 s
= strchr (cdpath
, 0);
834 r
= concat_dir_and_file (cdpath
, word
);
835 SHOW_C_CTX("try_complete:filename_subst_2");
836 matches
= completion_matches (r
, filename_completion_function
, flags
);
840 cdpath
= str_get_next_char (s
);
852 void free_completions (WInput
*in
)
856 if (!in
->completions
)
858 for (p
=in
->completions
; *p
; p
++)
860 g_free (in
->completions
);
861 in
->completions
= NULL
;
864 static int query_height
, query_width
;
865 static WInput
*input
;
867 static int start
, end
;
870 insert_text (WInput
*in
, char *text
, ssize_t size
)
872 int buff_len
= str_length (in
->buffer
);
874 size
= min (size
, (ssize_t
) strlen (text
)) + start
- end
;
875 if (strlen (in
->buffer
) + size
>= (size_t) in
->current_max_size
){
876 /* Expand the buffer */
877 char *narea
= g_try_realloc (in
->buffer
, in
->current_max_size
878 + size
+ in
->field_width
);
881 in
->current_max_size
+= size
+ in
->field_width
;
884 if (strlen (in
->buffer
)+1 < (size_t) in
->current_max_size
){
886 int i
= strlen (&in
->buffer
[end
]);
888 in
->buffer
[end
+ size
+ i
] = in
->buffer
[end
+ i
];
889 } else if (size
< 0){
890 char *p
= in
->buffer
+ end
+ size
, *q
= in
->buffer
+ end
;
895 memcpy (in
->buffer
+ start
, text
, size
- start
+ end
);
896 in
->point
+= str_length (in
->buffer
) - buff_len
;
897 update_input (in
, 1);
904 query_callback (Dlg_head
*h
, Widget
*sender
,
905 dlg_msg_t msg
, int parm
, void *data
)
907 static char buff
[MB_LEN_MAX
] = "";
922 if (end
== min_end
) {
929 for (i
= 0, e
= ((WListbox
*) h
->current
)->list
;
931 i
++, e
= g_list_next (e
)) {
932 WLEntry
*le
= (WLEntry
*) e
->data
;
934 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
- 1) == 0) {
935 listbox_select_entry ((WListbox
*) h
->current
, i
);
936 end
= str_get_prev_char (&(input
->buffer
[end
])) - input
->buffer
;
937 handle_char (input
, parm
);
938 send_message (h
->current
, WIDGET_DRAW
, 0);
946 if (parm
< 32 || parm
> 256) {
948 if (is_in_input_map (input
, parm
) == 2) {
951 h
->ret_value
= B_USER
; /* This means we want to refill the
952 list box and start again */
956 return MSG_NOT_HANDLED
;
962 char *last_text
= NULL
;
964 buff
[bl
] = (char) parm
;
967 switch (str_is_valid_char (buff
, bl
)) {
974 for (i
= 0, e
= ((WListbox
*) h
->current
)->list
;
976 i
++, e
= g_list_next (e
)) {
977 WLEntry
*le
= (WLEntry
*) e
->data
;
979 if (strncmp (input
->buffer
+ start
, le
->text
, end
- start
) == 0) {
980 if (strncmp (&le
->text
[end
- start
], buff
, bl
) == 0) {
985 si
= &(le
->text
[end
- start
]);
986 sl
= &(last_text
[end
- start
]);
988 for (; si
[0] != '\0' && sl
[0] != '\0';) {
989 nexti
= str_get_next_char (si
);
990 nextl
= str_get_next_char (sl
);
992 if (nexti
- si
!= nextl
- sl
)
994 if (strncmp (si
, sl
, nexti
- si
) != 0)
1001 if (low
> si
- &le
->text
[end
- start
])
1002 low
= si
- &le
->text
[end
- start
];
1004 last_text
= le
->text
;
1008 listbox_select_entry ((WListbox
*) h
->current
, i
);
1009 last_text
= le
->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, FALSE
, NULL
);
1110 add_widget (query_dlg
, query_list
);
1111 for (p
= in
->completions
+ 1; *p
; p
++)
1112 listbox_add_item (query_list
, LISTBOX_APPEND_AT_END
, 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
));