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. */
29 #include <sys/types.h>
33 #include <mhl/memory.h>
34 #include <mhl/escape.h>
35 #include <mhl/string.h>
46 #include "key.h" /* XCTRL and ALT macros */
48 typedef char *CompletionFunction (char * text
, int state
, INPUT_COMPLETE_FLAGS flags
);
50 //#define DO_COMPLETION_DEBUG
51 #ifdef DO_COMPLETION_DEBUG
53 * Useful to print/debug completion flags
55 static const char * show_c_flags(INPUT_COMPLETE_FLAGS flags
)
57 static char s_cf
[] = "FHCVUDS";
59 s_cf
[0] = (flags
& INPUT_COMPLETE_FILENAMES
) ? 'F' : ' ';
60 s_cf
[1] = (flags
& INPUT_COMPLETE_HOSTNAMES
) ? 'H' : ' ';
61 s_cf
[2] = (flags
& INPUT_COMPLETE_COMMANDS
) ? 'C' : ' ';
62 s_cf
[3] = (flags
& INPUT_COMPLETE_VARIABLES
) ? 'V' : ' ';
63 s_cf
[4] = (flags
& INPUT_COMPLETE_USERNAMES
) ? 'U' : ' ';
64 s_cf
[5] = (flags
& INPUT_COMPLETE_CD
) ? 'D' : ' ';
65 s_cf
[6] = (flags
& INPUT_COMPLETE_SHELL_ESC
) ? 'S' : ' ';
69 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
71 #define SHOW_C_CTX(func)
72 #endif /* DO_CMPLETION_DEBUG */
75 filename_completion_function (char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
77 static DIR *directory
;
78 static char *filename
= NULL
;
79 static char *dirname
= NULL
;
80 static char *users_dirname
= NULL
;
81 static size_t filename_len
;
82 int isdir
= 1, isexec
= 0;
84 struct dirent
*entry
= NULL
;
86 SHOW_C_CTX("filename_completion_function");
88 if (text
&& (flags
& INPUT_COMPLETE_SHELL_ESC
))
89 text
= mhl_shell_unescape_buf (text
);
91 /* If we're starting the match process, initialize us a bit. */
97 g_free (users_dirname
);
99 if ((*text
) && (temp
= strrchr (text
, PATH_SEP
))){
100 filename
= g_strdup (++temp
);
101 dirname
= g_strndup (text
, temp
- text
);
103 dirname
= g_strdup (".");
104 filename
= g_strdup (text
);
107 /* We aren't done yet. We also support the "~user" syntax. */
109 /* Save the version of the directory that the user typed. */
110 users_dirname
= dirname
;
113 dirname
= tilde_expand (dirname
);
114 canonicalize_pathname (dirname
);
115 /* Here we should do something with variable expansion
117 Maybe a dream - UNIMPLEMENTED yet. */
119 directory
= mc_opendir (dirname
);
120 filename_len
= strlen (filename
);
123 /* Now that we have some state, we can read the directory. */
125 while (directory
&& (entry
= mc_readdir (directory
))){
126 /* Special case for no filename.
127 All entries except "." and ".." match. */
129 if (!strcmp (entry
->d_name
, ".") || !strcmp (entry
->d_name
, ".."))
132 /* Otherwise, if these match up to the length of filename, then
133 it may be a match. */
134 if ((entry
->d_name
[0] != filename
[0]) ||
135 ((NLENGTH (entry
)) < filename_len
) ||
136 strncmp (filename
, entry
->d_name
, filename_len
))
139 isdir
= 1; isexec
= 0;
141 char *tmp
= g_malloc (3 + strlen (dirname
) + NLENGTH (entry
));
142 struct stat tempstat
;
144 strcpy (tmp
, dirname
);
145 strcat (tmp
, PATH_SEP_STR
);
146 strcat (tmp
, entry
->d_name
);
147 canonicalize_pathname (tmp
);
149 if (!mc_stat (tmp
, &tempstat
)){
150 uid_t my_uid
= getuid ();
151 gid_t my_gid
= getgid ();
153 if (!S_ISDIR (tempstat
.st_mode
)){
155 if ((!my_uid
&& (tempstat
.st_mode
& 0111)) ||
156 (my_uid
== tempstat
.st_uid
&& (tempstat
.st_mode
& 0100)) ||
157 (my_gid
== tempstat
.st_gid
&& (tempstat
.st_mode
& 0010)) ||
158 (tempstat
.st_mode
& 0001))
164 if ((flags
& INPUT_COMPLETE_COMMANDS
)
165 && (isexec
|| isdir
))
167 if ((flags
& INPUT_COMPLETE_CD
)
170 if (flags
& (INPUT_COMPLETE_FILENAMES
))
177 mc_closedir (directory
);
184 g_free (users_dirname
);
185 users_dirname
= NULL
;
190 if (users_dirname
&& (users_dirname
[0] != '.' || users_dirname
[1])){
191 int dirlen
= strlen (users_dirname
);
192 temp
= g_malloc (3 + dirlen
+ NLENGTH (entry
));
193 strcpy (temp
, users_dirname
);
194 /* We need a `/' at the end. */
195 if (users_dirname
[dirlen
- 1] != PATH_SEP
){
196 temp
[dirlen
] = PATH_SEP
;
197 temp
[dirlen
+ 1] = 0;
199 strcat (temp
, entry
->d_name
);
201 temp
= g_malloc (2 + NLENGTH (entry
));
202 strcpy (temp
, entry
->d_name
);
205 strcat (temp
, PATH_SEP_STR
);
207 if (temp
&& (flags
& INPUT_COMPLETE_SHELL_ESC
))
209 temp
= mhl_shell_escape_dup(temp
);
215 /* We assume here that text[0] == '~' , if you want to call it in another way,
216 you have to change the code */
218 username_completion_function (char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
220 static struct passwd
*entry
;
223 SHOW_C_CTX("username_completion_function");
225 if (text
[0] == '\\' && text
[1] == '~') text
++;
226 if (!state
){ /* Initialization stuff */
228 userlen
= strlen (text
+ 1);
230 while ((entry
= getpwent ()) != NULL
){
231 /* Null usernames should result in all users as possible completions. */
234 else if (text
[1] == entry
->pw_name
[0] &&
235 !strncmp (text
+ 1, entry
->pw_name
, userlen
))
243 char *temp
= g_malloc (3 + strlen (entry
->pw_name
));
246 strcpy (temp
+ 1, entry
->pw_name
);
247 strcat (temp
, PATH_SEP_STR
);
252 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
253 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
254 extern char **environ
;
257 /* We assume text [0] == '$' and want to have a look at text [1], if it is
258 equal to '{', so that we should append '}' at the end */
260 variable_completion_function (char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
263 static int varlen
, isbrace
;
264 const char *p
= NULL
;
266 SHOW_C_CTX("variable_completion_function");
268 if (!state
){ /* Initialization stuff */
269 isbrace
= (text
[1] == '{');
270 varlen
= strlen (text
+ 1 + isbrace
);
275 p
= strchr (*env_p
, '=');
276 if (p
&& p
- *env_p
>= varlen
&& !strncmp (text
+ 1 + isbrace
, *env_p
, varlen
))
284 char *temp
= g_malloc (2 + 2 * isbrace
+ p
- *env_p
);
289 memcpy (temp
+ 1 + isbrace
, *env_p
, p
- *env_p
);
291 strcpy (temp
+ 2 + (p
- *env_p
), "}");
293 temp
[1 + p
- *env_p
] = 0;
299 #define whitespace(c) ((c) == ' ' || (c) == '\t')
300 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
302 static char **hosts
= NULL
;
303 static char **hosts_p
= NULL
;
304 static int hosts_alloclen
= 0;
305 static void fetch_hosts (const char *filename
)
307 FILE *file
= fopen (filename
, "r");
308 char buffer
[256], *name
;
309 register int i
, start
;
314 while (fgets (buffer
, 255, file
) != NULL
){
315 /* Skip to first character. */
316 for (i
= 0; buffer
[i
] && cr_whitespace (buffer
[i
]); i
++);
317 /* Ignore comments... */
318 if (buffer
[i
] == '#')
320 /* Handle $include. */
321 if (!strncmp (buffer
+ i
, "$include ", 9)){
322 char *includefile
= buffer
+ i
+ 9;
325 /* Find start of filename. */
326 while (*includefile
&& whitespace (*includefile
))
330 /* Find end of filename. */
331 while (*t
&& !cr_whitespace (*t
))
335 fetch_hosts (includefile
);
340 while (buffer
[i
] && !cr_whitespace (buffer
[i
]))
343 /* Get the host names separated by white space. */
344 while (buffer
[i
] && buffer
[i
] != '#'){
345 while (buffer
[i
] && cr_whitespace (buffer
[i
]))
347 if (buffer
[i
] == '#')
349 for (start
= i
; buffer
[i
] && !cr_whitespace (buffer
[i
]); i
++);
352 name
= g_strndup (buffer
+ start
, i
- start
);
356 if (hosts_p
- hosts
>= hosts_alloclen
){
357 int j
= hosts_p
- hosts
;
359 hosts
= g_realloc ((void *)hosts
, ((hosts_alloclen
+= 30) + 1) * sizeof (char *));
362 for (host_p
= hosts
; host_p
< hosts_p
; host_p
++)
363 if (!strcmp (name
, *host_p
))
364 break; /* We do not want any duplicates */
365 if (host_p
== hosts_p
){
377 hostname_completion_function (char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
379 static char **host_p
;
380 static int textstart
, textlen
;
382 SHOW_C_CTX("hostname_completion_function");
384 if (!state
){ /* Initialization stuff */
388 for (host_p
= hosts
; *host_p
; host_p
++)
392 hosts
= g_new (char *, (hosts_alloclen
= 30) + 1);
395 fetch_hosts ((p
= getenv ("HOSTFILE")) ? p
: "/etc/hosts");
397 textstart
= (*text
== '@') ? 1 : 0;
398 textlen
= strlen (text
+ textstart
);
403 break; /* Match all of them */
404 else if (!strncmp (text
+ textstart
, *host_p
, textlen
))
410 for (host_p
= hosts
; *host_p
; host_p
++)
416 char *temp
= g_malloc (2 + strlen (*host_p
));
420 strcpy (temp
+ textstart
, *host_p
);
427 * This is the function to call when the word to complete is in a position
428 * where a command word can be found. It looks around $PATH, looking for
429 * commands that match. It also scans aliases, function names, and the
430 * table of shell built-ins.
433 command_completion_function (char *text
, int state
, INPUT_COMPLETE_FLAGS flags
)
435 static const char *path_end
;
436 static int isabsolute
;
439 static const char *const *words
;
441 static char *cur_path
;
442 static char *cur_word
;
443 static int init_state
;
444 static const char *const bash_reserved
[] = {
445 "if", "then", "else", "elif", "fi", "case", "esac", "for",
446 "select", "while", "until", "do", "done", "in", "function", 0
448 static const char *const bash_builtins
[] = {
449 "alias", "bg", "bind", "break", "builtin", "cd", "command",
450 "continue", "declare", "dirs", "echo", "enable", "eval",
451 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
452 "help", "history", "jobs", "kill", "let", "local", "logout",
453 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
454 "shift", "source", "suspend", "test", "times", "trap", "type",
455 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
459 SHOW_C_CTX("command_completion_function");
461 if (!(flags
& INPUT_COMPLETE_COMMANDS
))
464 text
= mhl_shell_unescape_buf(text
);
465 flags
&= ~INPUT_COMPLETE_SHELL_ESC
;
467 if (!state
) { /* Initialize us a little bit */
468 isabsolute
= strchr (text
, PATH_SEP
) != 0;
470 words
= bash_reserved
;
472 text_len
= strlen (text
);
473 if (!path
&& (path
= g_strdup (getenv ("PATH"))) != NULL
) {
475 path_end
= strchr (p
, 0);
476 while ((p
= strchr (p
, PATH_ENV_SEP
))) {
484 p
= filename_completion_function (text
, state
, flags
);
487 p
= mhl_shell_escape_dup(p
);
493 case 0: /* Reserved words */
495 if (!strncmp (*words
, text
, text_len
))
496 return g_strdup (*(words
++));
500 words
= bash_builtins
;
501 case 1: /* Builtin commands */
503 if (!strncmp (*words
, text
, text_len
))
504 return g_strdup (*(words
++));
512 case 2: /* And looking through the $PATH */
517 if (cur_path
>= path_end
)
519 expanded
= tilde_expand (*cur_path
? cur_path
: ".");
520 cur_word
= mhl_str_dir_plus_file (expanded
, text
);
522 canonicalize_pathname (cur_word
);
523 cur_path
= strchr (cur_path
, 0) + 1;
527 filename_completion_function (cur_word
,
528 state
- init_state
, flags
);
541 if ((p
= strrchr (found
, PATH_SEP
)) != NULL
) {
543 p
= mhl_shell_escape_dup(p
);
552 match_compare (const void *a
, const void *b
)
554 return strcmp (*(char **)a
, *(char **)b
);
557 /* Returns an array of char * matches with the longest common denominator
558 in the 1st entry. Then a NULL terminated list of different possible
560 You have to supply your own CompletionFunction with the word you
561 want to complete as the first argument and an count of previous matches
563 In case no matches were found we return NULL. */
565 completion_matches (char *text
, CompletionFunction entry_function
, INPUT_COMPLETE_FLAGS flags
)
567 /* Number of slots in match_list. */
570 /* The list of matches. */
571 char **match_list
= g_new (char *, (match_list_size
= 30) + 1);
573 /* Number of matches actually found. */
576 /* Temporary string binder. */
579 match_list
[1] = NULL
;
581 while ((string
= (*entry_function
) (text
, matches
, flags
)) != NULL
){
582 if (matches
+ 1 == match_list_size
)
583 match_list
= (char **) g_realloc (match_list
, ((match_list_size
+= 30) + 1) * sizeof (char *));
584 match_list
[++matches
] = string
;
585 match_list
[matches
+ 1] = NULL
;
588 /* If there were any matches, then look through them finding out the
589 lowest common denominator. That then becomes match_list[0]. */
593 int low
= 4096; /* Count of max-matched characters. */
595 /* If only one match, just use that. */
597 match_list
[0] = match_list
[1];
598 match_list
[1] = NULL
;
602 qsort (match_list
+ 1, matches
, sizeof (char *), match_compare
);
604 /* And compare each member of the list with
605 the next, finding out where they stop matching.
606 If we find two equal strings, we have to put one away... */
609 while (j
< matches
+ 1)
611 register int c1
, c2
, si
;
613 for (si
= 0;(c1
= match_list
[i
][si
]) && (c2
= match_list
[j
][si
]); si
++)
616 if (!c1
&& !match_list
[j
][si
]){ /* Two equal strings */
617 g_free (match_list
[j
]);
621 continue; /* Look for a run of equal strings */
623 if (low
> si
) low
= si
;
624 if (i
+ 1 != j
) /* So there's some gap */
625 match_list
[i
+ 1] = match_list
[j
];
629 match_list
[matches
+ 1] = NULL
;
630 match_list
[0] = g_strndup(match_list
[1], low
);
632 } else { /* There were no matches. */
639 /* Check if directory completion is needed */
641 check_is_cd (const char *text
, int start
, INPUT_COMPLETE_FLAGS flags
)
645 SHOW_C_CTX("check_is_cd");
646 if (!(flags
& INPUT_COMPLETE_CD
))
649 /* Skip initial spaces */
652 while (p
< q
&& *p
&& isspace ((unsigned char) *p
))
655 /* Check if the command is "cd" and the cursor is after it */
656 if (p
[0] == 'c' && p
[1] == 'd' && isspace ((unsigned char) p
[2])
663 /* Returns an array of matches, or NULL if none. */
665 try_complete (char *text
, int *start
, int *end
, INPUT_COMPLETE_FLAGS flags
)
667 int in_command_position
= 0;
669 char **matches
= NULL
;
670 const char *command_separator_chars
= ";|&{(`";
671 char *p
= NULL
, *q
= NULL
, *r
= NULL
;
672 int is_cd
= check_is_cd (text
, *start
, flags
);
674 SHOW_C_CTX("try_complete");
678 word
= g_strdup (text
+ *start
);
681 /* Determine if this could be a command word. It is if it appears at
682 the start of the line (ignoring preceding whitespace), or if it
683 appears after a character that separates commands. And we have to
684 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
685 if (!is_cd
&& (flags
& INPUT_COMPLETE_COMMANDS
)){
687 for (i
= *start
- 1; i
> -1; i
--) {
688 if (text
[i
] == ' ' || text
[i
] == '\t'){
689 if (i
== 0 ) continue;
690 if (text
[i
-1] != '\\') {
697 in_command_position
++;
698 else if (strchr (command_separator_chars
, text
[i
])){
699 register int this_char
, prev_char
;
701 in_command_position
++;
704 /* Handle the two character tokens `>&', `<&', and `>|'.
705 We are not in a command position after one of these. */
707 prev_char
= text
[i
- 1];
709 if ((this_char
== '&' && (prev_char
== '<' || prev_char
== '>')) ||
710 (this_char
== '|' && prev_char
== '>'))
711 in_command_position
= 0;
712 else if (i
> 0 && text
[i
-1] == '\\') /* Quoted */
713 in_command_position
= 0;
718 if (flags
& INPUT_COMPLETE_COMMANDS
)
719 p
= strrchr (word
, '`');
720 if (flags
& (INPUT_COMPLETE_COMMANDS
| INPUT_COMPLETE_VARIABLES
))
721 q
= strrchr (word
, '$');
722 if (flags
& INPUT_COMPLETE_HOSTNAMES
)
723 r
= strrchr (word
, '@');
724 if (q
&& q
[1] == '(' && INPUT_COMPLETE_COMMANDS
){
730 /* Command substitution? */
732 SHOW_C_CTX("try_complete:cmd_backq_subst");
733 matches
= completion_matches (p
+ 1, command_completion_function
, flags
& (~INPUT_COMPLETE_FILENAMES
));
735 *start
+= p
+ 1 - word
;
739 else if (q
> p
&& q
> r
){
740 SHOW_C_CTX("try_complete:var_subst");
741 matches
= completion_matches (q
, variable_completion_function
, flags
);
746 /* Starts with '@', then look through the known hostnames for
748 else if (r
> p
&& r
> q
){
749 SHOW_C_CTX("try_complete:host_subst");
750 matches
= completion_matches (r
, hostname_completion_function
, flags
);
755 /* Starts with `~' and there is no slash in the word, then
756 try completing this word as a username. */
757 if (!matches
&& *word
== '~' && (flags
& INPUT_COMPLETE_USERNAMES
) && !strchr (word
, PATH_SEP
))
759 SHOW_C_CTX("try_complete:user_subst");
760 matches
= completion_matches (word
, username_completion_function
, flags
);
764 /* And finally if this word is in a command position, then
765 complete over possible command names, including aliases, functions,
766 and command names. */
767 if (!matches
&& in_command_position
)
769 SHOW_C_CTX("try_complete:cmd_subst");
770 matches
= completion_matches (word
, command_completion_function
, flags
& (~INPUT_COMPLETE_FILENAMES
));
773 else if (!matches
&& (flags
& INPUT_COMPLETE_FILENAMES
)){
775 flags
&= ~(INPUT_COMPLETE_FILENAMES
| INPUT_COMPLETE_COMMANDS
);
776 SHOW_C_CTX("try_complete:filename_subst_1");
777 matches
= completion_matches (word
, filename_completion_function
, flags
);
778 if (!matches
&& is_cd
&& *word
!= PATH_SEP
&& *word
!= '~'){
779 char *p
, *q
= text
+ *start
;
781 for (p
= text
; *p
&& p
< q
; p
++){
782 if (*p
== ' ' || *p
== '\t') {
783 if (p
== text
) continue;
784 if (*(p
-1) == '\\') {
790 if (!strncmp (p
, "cd", 2))
791 for (p
+= 2; *p
&& p
< q
&& (*p
== ' ' || *p
== '\t'); p
++){
792 if (p
== text
) continue;
793 if (*(p
-1) == '\\') {
799 char * const cdpath_ref
= g_strdup (getenv ("CDPATH"));
800 char *cdpath
= cdpath_ref
;
807 while (!matches
&& c
== ':'){
808 s
= strchr (cdpath
, ':');
810 s
= strchr (cdpath
, 0);
814 r
= mhl_str_dir_plus_file (cdpath
, word
);
815 SHOW_C_CTX("try_complete:filename_subst_2");
816 matches
= completion_matches (r
, filename_completion_function
, flags
);
832 void free_completions (WInput
*in
)
836 if (!in
->completions
)
838 for (p
=in
->completions
; *p
; p
++)
840 g_free (in
->completions
);
841 in
->completions
= NULL
;
844 static int query_height
, query_width
;
845 static WInput
*input
;
847 static int start
, end
;
849 static int insert_text (WInput
*in
, char *text
, ssize_t len
)
851 len
= min (len
, (ssize_t
) strlen (text
)) + start
- end
;
852 if (strlen (in
->buffer
) + len
>= (size_t) in
->current_max_len
){
853 /* Expand the buffer */
854 char *narea
= g_realloc (in
->buffer
, in
->current_max_len
+ len
+ in
->field_len
);
857 in
->current_max_len
+= len
+ in
->field_len
;
860 if (strlen (in
->buffer
)+1 < (size_t) in
->current_max_len
){
862 int i
= strlen (&in
->buffer
[end
]);
864 in
->buffer
[end
+ len
+ i
] = in
->buffer
[end
+ i
];
866 char *p
= in
->buffer
+ end
+ len
, *q
= in
->buffer
+ end
;
871 memcpy (in
->buffer
+ start
, text
, len
- start
+ end
);
873 update_input (in
, 1);
880 query_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
892 if (end
== min_end
) {
899 e1
= e
= ((WListbox
*) (h
->current
))->list
;
902 (input
->buffer
+ start
, e1
->text
,
904 listbox_select_entry ((WListbox
*) (h
->current
),
906 handle_char (input
, parm
);
908 send_message (h
->current
, WIDGET_DRAW
, 0);
917 if (parm
> 0xff || !is_printable (parm
)) {
918 if (is_in_input_map (input
, parm
) == 2) {
921 h
->ret_value
= B_USER
; /* This means we want to refill the
922 list box and start again */
926 return MSG_NOT_HANDLED
;
931 char *last_text
= NULL
;
933 e1
= e
= ((WListbox
*) (h
->current
))->list
;
936 (input
->buffer
+ start
, e1
->text
, end
- start
)) {
937 if (e1
->text
[end
- start
] == parm
) {
939 register int c1
, c2
, si
;
941 for (si
= end
- start
+ 1;
943 && (c2
= e1
->text
[si
]); si
++)
948 last_text
= e1
->text
;
952 listbox_select_entry ((WListbox
*) (h
->
955 last_text
= e1
->text
;
961 if (need_redraw
== 2) {
962 insert_text (input
, last_text
, low
);
963 send_message (h
->current
, WIDGET_DRAW
, 0);
964 } else if (need_redraw
== 1) {
965 h
->ret_value
= B_ENTER
;
974 return default_dlg_callback (h
, msg
, parm
);
978 #define DO_INSERTION 1
980 /* Returns 1 if the user would like to see us again */
982 complete_engine (WInput
*in
, int what_to_do
)
984 if (in
->completions
&& in
->point
!= end
)
985 free_completions (in
);
986 if (!in
->completions
){
988 for (start
= end
? end
- 1 : 0; start
> -1; start
--)
989 if (strchr (" \t;|<>", in
->buffer
[start
])){
990 if (start
> 0 && in
->buffer
[start
-1] == '\\')
997 in
->completions
= try_complete (in
->buffer
, &start
, &end
, in
->completion_flags
);
1000 if (in
->completions
){
1001 if (what_to_do
& DO_INSERTION
|| ((what_to_do
& DO_QUERY
) && !in
->completions
[1])) {
1002 char * complete
= in
->completions
[0];
1003 if (insert_text (in
, complete
, strlen (complete
))){
1004 if (in
->completions
[1])
1007 free_completions (in
);
1011 if ((what_to_do
& DO_QUERY
) && in
->completions
&& in
->completions
[1]) {
1012 int maxlen
= 0, i
, count
= 0;
1014 int start_x
, start_y
;
1016 Dlg_head
*query_dlg
;
1017 WListbox
*query_list
;
1019 for (p
=in
->completions
+ 1; *p
; count
++, p
++) {
1020 if ((i
= strlen (*p
)) > maxlen
)
1023 start_x
= in
->widget
.x
;
1024 start_y
= in
->widget
.y
;
1025 if (start_y
- 2 >= count
) {
1026 y
= start_y
- 2 - count
;
1029 if (start_y
>= LINES
- start_y
- 1) {
1034 h
= LINES
- start_y
- 1;
1037 x
= start
- in
->first_shown
- 2 + start_x
;
1049 query_dlg
= create_dlg (y
, x
, query_height
, query_width
,
1050 dialog_colors
, query_callback
,
1051 "[Completion]", NULL
, DLG_COMPACT
);
1052 query_list
= listbox_new (1, 1, w
- 2, h
- 2, NULL
);
1053 add_widget (query_dlg
, query_list
);
1054 for (p
= in
->completions
+ 1; *p
; p
++)
1055 listbox_add_item (query_list
, 0, 0, *p
, NULL
);
1056 run_dlg (query_dlg
);
1058 if (query_dlg
->ret_value
== B_ENTER
){
1059 listbox_get_current (query_list
, &q
, NULL
);
1061 insert_text (in
, q
, strlen (q
));
1063 if (q
|| end
!= min_end
)
1064 free_completions (in
);
1065 i
= query_dlg
->ret_value
; /* B_USER if user wants to start over again */
1066 destroy_dlg (query_dlg
);
1075 void complete (WInput
*in
)
1079 if (in
->completions
)
1080 engine_flags
= DO_QUERY
;
1083 engine_flags
= DO_INSERTION
;
1085 if (show_all_if_ambiguous
)
1086 engine_flags
|= DO_QUERY
;
1089 while (complete_engine (in
, engine_flags
));