Add $ and ` for escaping and reorder it according to the ascii values
[midnight-commander.git] / src / complete.c
bloba11b9c9559cc967af49c09d5302c2d34005c185a
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. */
23 #include <config.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
34 #include "global.h"
35 #include "tty.h"
36 #include "win.h"
37 #include "color.h"
38 #include "dialog.h"
39 #include "widget.h"
40 #include "wtools.h"
41 #include "complete.h"
42 #include "main.h"
43 #include "util.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;
55 static char *
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. */
68 if (!state){
69 const char *temp;
71 g_free (dirname);
72 g_free (filename);
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);
78 } else {
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
91 and `command`.
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. */
103 if (!filename_len){
104 if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
105 continue;
106 } else {
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))
112 continue;
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);
123 /* Unix version */
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)){
129 isdir = 0;
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))
134 isexec = 1;
137 g_free (tmp);
139 switch (look_for_executables)
141 case 2: if (!isexec)
142 continue;
143 break;
144 case 1: if (!isexec && !isdir)
145 continue;
146 break;
148 if (ignore_filenames && !isdir)
149 continue;
150 break;
153 if (!entry){
154 if (directory){
155 mc_closedir (directory);
156 directory = NULL;
158 g_free (dirname);
159 dirname = NULL;
160 g_free (filename);
161 filename = NULL;
162 g_free (users_dirname);
163 users_dirname = NULL;
164 return NULL;
165 } else {
166 char *temp;
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);
178 } else {
179 temp = g_malloc (2 + NLENGTH (entry));
180 strcpy (temp, entry->d_name);
182 if (isdir)
183 strcat (temp, PATH_SEP_STR);
184 return temp;
188 /* We assume here that text[0] == '~' , if you want to call it in another way,
189 you have to change the code */
190 static char *
191 username_completion_function (char *text, int state)
193 static struct passwd *entry;
194 static int userlen;
196 if (!state){ /* Initialization stuff */
197 setpwent ();
198 userlen = strlen (text + 1);
200 while ((entry = getpwent ()) != NULL){
201 /* Null usernames should result in all users as possible completions. */
202 if (!userlen)
203 break;
204 else if (text[1] == entry->pw_name[0] &&
205 !strncmp (text + 1, entry->pw_name, userlen))
206 break;
209 if (!entry){
210 endpwent ();
211 return NULL;
212 } else {
213 char *temp = g_malloc (3 + strlen (entry->pw_name));
215 *temp = '~';
216 strcpy (temp + 1, entry->pw_name);
217 strcat (temp, PATH_SEP_STR);
218 return temp;
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;
225 #endif
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 */
229 static char *
230 variable_completion_function (char *text, int state)
232 static char **env_p;
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);
239 env_p = environ;
242 while (*env_p){
243 p = strchr (*env_p, '=');
244 if (p && p - *env_p >= varlen && !strncmp (text + 1 + isbrace, *env_p, varlen))
245 break;
246 env_p++;
249 if (!*env_p)
250 return NULL;
251 else {
252 char *temp = g_malloc (2 + 2 * isbrace + p - *env_p);
254 *temp = '$';
255 if (isbrace)
256 temp [1] = '{';
257 memcpy (temp + 1 + isbrace, *env_p, p - *env_p);
258 if (isbrace)
259 strcpy (temp + 2 + (p - *env_p), "}");
260 else
261 temp [1 + p - *env_p] = 0;
262 env_p++;
263 return temp;
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;
279 if (!file)
280 return;
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] == '#')
287 continue;
288 /* Handle $include. */
289 if (!strncmp (buffer + i, "$include ", 9)){
290 char *includefile = buffer + i + 9;
291 char *t;
293 /* Find start of filename. */
294 while (*includefile && whitespace (*includefile))
295 includefile++;
296 t = includefile;
298 /* Find end of filename. */
299 while (*t && !cr_whitespace (*t))
300 t++;
301 *t = '\0';
303 fetch_hosts (includefile);
304 continue;
307 /* Skip IP #s. */
308 while (buffer[i] && !cr_whitespace (buffer[i]))
309 i++;
311 /* Get the host names separated by white space. */
312 while (buffer[i] && buffer[i] != '#'){
313 while (buffer[i] && cr_whitespace (buffer[i]))
314 i++;
315 if (buffer[i] == '#')
316 continue;
317 for (start = i; buffer[i] && !cr_whitespace (buffer[i]); i++);
318 if (i - start == 0)
319 continue;
320 name = g_strndup (buffer + start, i - start);
322 char **host_p;
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 *));
328 hosts_p = hosts + j;
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){
334 *(hosts_p++) = name;
335 *hosts_p = NULL;
336 } else
337 g_free (name);
341 fclose (file);
344 static char *
345 hostname_completion_function (char *text, int state)
347 static char **host_p;
348 static int textstart, textlen;
350 if (!state){ /* Initialization stuff */
351 const char *p;
353 if (hosts != NULL){
354 for (host_p = hosts; *host_p; host_p++)
355 g_free (*host_p);
356 g_free (hosts);
358 hosts = g_new (char *, (hosts_alloclen = 30) + 1);
359 *hosts = NULL;
360 hosts_p = hosts;
361 fetch_hosts ((p = getenv ("HOSTFILE")) ? p : "/etc/hosts");
362 host_p = hosts;
363 textstart = (*text == '@') ? 1 : 0;
364 textlen = strlen (text + textstart);
367 while (*host_p){
368 if (!textlen)
369 break; /* Match all of them */
370 else if (!strncmp (text + textstart, *host_p, textlen))
371 break;
372 host_p++;
375 if (!*host_p){
376 for (host_p = hosts; *host_p; host_p++)
377 g_free (*host_p);
378 g_free (hosts);
379 hosts = NULL;
380 return NULL;
381 } else {
382 char *temp = g_malloc (2 + strlen (*host_p));
384 if (textstart)
385 *temp = '@';
386 strcpy (temp + textstart, *host_p);
387 host_p++;
388 return temp;
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.
398 static char *
399 command_completion_function (char *text, int state)
401 static const char *path_end;
402 static int isabsolute;
403 static int phase;
404 static int text_len;
405 static const char *const *words;
406 static char *path;
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
423 char *p, *found;
425 if (!state) { /* Initialize us a little bit */
426 isabsolute = strchr (text, PATH_SEP) != 0;
427 look_for_executables = isabsolute ? 1 : 2;
428 if (!isabsolute) {
429 words = bash_reserved;
430 phase = 0;
431 text_len = strlen (text);
432 if (!path && (path = g_strdup (getenv ("PATH"))) != NULL) {
433 p = path;
434 path_end = strchr (p, 0);
435 while ((p = strchr (p, PATH_ENV_SEP))) {
436 *p++ = 0;
442 if (isabsolute) {
443 p = filename_completion_function (text, state);
444 if (!p)
445 look_for_executables = 0;
446 return p;
449 found = NULL;
450 switch (phase) {
451 case 0: /* Reserved words */
452 while (*words) {
453 if (!strncmp (*words, text, text_len))
454 return g_strdup (*(words++));
455 words++;
457 phase++;
458 words = bash_builtins;
459 case 1: /* Builtin commands */
460 while (*words) {
461 if (!strncmp (*words, text, text_len))
462 return g_strdup (*(words++));
463 words++;
465 phase++;
466 if (!path)
467 break;
468 cur_path = path;
469 cur_word = NULL;
470 case 2: /* And looking through the $PATH */
471 while (!found) {
472 if (!cur_word) {
473 char *expanded;
475 if (cur_path >= path_end)
476 break;
477 expanded = tilde_expand (*cur_path ? cur_path : ".");
478 cur_word = concat_dir_and_file (expanded, text);
479 g_free (expanded);
480 canonicalize_pathname (cur_word);
481 cur_path = strchr (cur_path, 0) + 1;
482 init_state = state;
484 found =
485 filename_completion_function (cur_word,
486 state - init_state);
487 if (!found) {
488 g_free (cur_word);
489 cur_word = NULL;
494 if (!found) {
495 look_for_executables = 0;
496 g_free (path);
497 path = NULL;
498 return NULL;
500 if ((p = strrchr (found, PATH_SEP)) != NULL) {
501 p++;
502 p = g_strdup (p);
503 g_free (found);
504 return p;
506 return found;
510 static int
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
518 completions follows.
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
521 as the second.
522 In case no matches were found we return NULL. */
523 static char **
524 completion_matches (char *text, CompletionFunction entry_function)
526 /* Number of slots in match_list. */
527 int match_list_size;
529 /* The list of matches. */
530 char **match_list = g_new (char *, (match_list_size = 30) + 1);
532 /* Number of matches actually found. */
533 int matches = 0;
535 /* Temporary string binder. */
536 char *string;
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]. */
549 if (matches)
551 register int i = 1;
552 int low = 4096; /* Count of max-matched characters. */
554 /* If only one match, just use that. */
555 if (matches == 1){
556 match_list[0] = match_list[1];
557 match_list[1] = NULL;
558 } else {
559 int j;
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... */
567 j = i + 1;
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++)
573 if (c1 != c2) break;
575 if (!c1 && !match_list [j][si]){ /* Two equal strings */
576 g_free (match_list [j]);
577 j++;
578 if (j > matches)
579 break;
580 continue; /* Look for a run of equal strings */
581 } else
582 if (low > si) low = si;
583 if (i + 1 != j) /* So there's some gap */
584 match_list [i + 1] = match_list [j];
585 i++; j++;
587 matches = i;
588 match_list [matches + 1] = NULL;
589 match_list[0] = g_strndup(match_list[1], low);
591 } else { /* There were no matches. */
592 g_free (match_list);
593 match_list = NULL;
595 return match_list;
598 /* Check if directory completion is needed */
599 static int
600 check_is_cd (const char *text, int start, int flags)
602 const char *p, *q;
604 if (flags & INPUT_COMPLETE_CD)
605 return 1;
607 if (!(flags & INPUT_COMPLETE_COMMANDS))
608 return 0;
610 /* Skip initial spaces */
611 p = text;
612 q = text + start;
613 while (p < q && *p && isspace ((unsigned char) *p))
614 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])
618 && (p + 2 < q))
619 return 1;
621 return 0;
624 /* Returns an array of matches, or NULL if none. */
625 static char **
626 try_complete (char *text, int *start, int *end, int flags)
628 int in_command_position = 0, i;
629 char *word, c;
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;
636 c = text [*end];
637 text [*end] = 0;
638 word = g_strdup (text + *start);
639 text [*end] = c;
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)){
646 i = *start - 1;
647 while (i > -1 && (text[i] == ' ' || text[i] == '\t'))
648 i--;
649 if (i < 0)
650 in_command_position++;
651 else if (strchr (command_separator_chars, text[i])){
652 register int this_char, prev_char;
654 in_command_position++;
656 if (i){
657 /* Handle the two character tokens `>&', `<&', and `>|'.
658 We are not in a command position after one of these. */
659 this_char = text[i];
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){
678 if (q > p)
679 p = q + 1;
680 q = NULL;
683 /* Command substitution? */
684 if (p > q && p > r){
685 matches = completion_matches (p + 1, command_completion_function);
686 if (matches)
687 *start += p + 1 - word;
690 /* Variable name? */
691 else if (q > p && q > r){
692 matches = completion_matches (q, variable_completion_function);
693 if (matches)
694 *start += q - word;
697 /* Starts with '@', then look through the known hostnames for
698 completion first. */
699 else if (r > p && r > q){
700 matches = completion_matches (r, hostname_completion_function);
701 if (matches)
702 *start += r - word;
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)){
718 if (is_cd)
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++);
728 if (p == q){
729 char * const cdpath_ref = g_strdup (getenv ("CDPATH"));
730 char *cdpath = cdpath_ref;
731 char c, *s, *r;
733 if (cdpath == NULL)
734 c = 0;
735 else
736 c = ':';
737 while (!matches && c == ':'){
738 s = strchr (cdpath, ':');
739 if (s == NULL)
740 s = strchr (cdpath, 0);
741 c = *s;
742 *s = 0;
743 if (*cdpath){
744 r = concat_dir_and_file (cdpath, word);
745 ignore_filenames = 1;
746 matches = completion_matches (r, filename_completion_function);
747 ignore_filenames = 0;
748 g_free (r);
750 *s = c;
751 cdpath = s + 1;
753 g_free (cdpath_ref);
758 g_free (word);
760 return matches;
763 void free_completions (WInput *in)
765 char **p;
767 if (!in->completions)
768 return;
769 for (p=in->completions; *p; p++)
770 g_free (*p);
771 g_free (in->completions);
772 in->completions = NULL;
775 static int query_height, query_width;
776 static WInput *input;
777 static int min_end;
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);
786 if (narea){
787 in->buffer = narea;
788 in->current_max_len += len + in->field_len;
791 if (strlen (in->buffer)+1 < (size_t) in->current_max_len){
792 if (len > 0){
793 int i = strlen (&in->buffer [end]);
794 for (; i >= 0; i--)
795 in->buffer [end + len + i] = in->buffer [end + i];
796 } else if (len < 0){
797 char *p = in->buffer + end + len, *q = in->buffer + end;
798 while (*q)
799 *(p++) = *(q++);
800 *p = 0;
802 memcpy (in->buffer + start, text, len - start + end);
803 in->point += len;
804 update_input (in, 1);
805 end += len;
807 return len != 0;
810 static cb_ret_t
811 query_callback (Dlg_head *h, dlg_msg_t msg, int parm)
813 switch (msg) {
814 case DLG_KEY:
815 switch (parm) {
816 case KEY_LEFT:
817 case KEY_RIGHT:
818 h->ret_value = 0;
819 dlg_stop (h);
820 return MSG_HANDLED;
822 case KEY_BACKSPACE:
823 if (end == min_end) {
824 h->ret_value = 0;
825 dlg_stop (h);
826 return MSG_HANDLED;
827 } else {
828 WLEntry *e, *e1;
830 e1 = e = ((WListbox *) (h->current))->list;
831 do {
832 if (!strncmp
833 (input->buffer + start, e1->text,
834 end - start - 1)) {
835 listbox_select_entry ((WListbox *) (h->current),
836 e1);
837 handle_char (input, parm);
838 end--;
839 send_message (h->current, WIDGET_DRAW, 0);
840 break;
842 e1 = e1->next;
843 } while (e != e1);
845 return MSG_HANDLED;
847 default:
848 if (parm > 0xff || !is_printable (parm)) {
849 if (is_in_input_map (input, parm) == 2) {
850 if (end == min_end)
851 return MSG_HANDLED;
852 h->ret_value = B_USER; /* This means we want to refill the
853 list box and start again */
854 dlg_stop (h);
855 return MSG_HANDLED;
856 } else
857 return MSG_NOT_HANDLED;
858 } else {
859 WLEntry *e, *e1;
860 int need_redraw = 0;
861 int low = 4096;
862 char *last_text = NULL;
864 e1 = e = ((WListbox *) (h->current))->list;
865 do {
866 if (!strncmp
867 (input->buffer + start, e1->text, end - start)) {
868 if (e1->text[end - start] == parm) {
869 if (need_redraw) {
870 register int c1, c2, si;
872 for (si = end - start + 1;
873 (c1 = last_text[si])
874 && (c2 = e1->text[si]); si++)
875 if (c1 != c2)
876 break;
877 if (low > si)
878 low = si;
879 last_text = e1->text;
880 need_redraw = 2;
881 } else {
882 need_redraw = 1;
883 listbox_select_entry ((WListbox *) (h->
884 current),
885 e1);
886 last_text = e1->text;
890 e1 = e1->next;
891 } while (e != e1);
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;
897 dlg_stop (h);
900 return MSG_HANDLED;
902 break;
904 default:
905 return default_dlg_callback (h, msg, parm);
909 #define DO_INSERTION 1
910 #define DO_QUERY 2
911 /* Returns 1 if the user would like to see us again */
912 static int
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){
919 end = in->point;
920 for (start = end ? end - 1 : 0; start > -1; start--)
921 if (strchr (" \t;|<>", in->buffer [start]))
922 break;
923 if (start < end)
924 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])
932 beep ();
933 else
934 free_completions (in);
935 } else
936 beep ();
938 if ((what_to_do & DO_QUERY) && in->completions && in->completions [1]) {
939 int maxlen = 0, i, count = 0;
940 int x, y, w, h;
941 int start_x, start_y;
942 char **p, *q;
943 Dlg_head *query_dlg;
944 WListbox *query_list;
946 for (p=in->completions + 1; *p; count++, p++) {
947 *p = escape_string(*p);
948 if ((i = strlen (*p)) > maxlen)
949 maxlen = i;
951 start_x = in->widget.x;
952 start_y = in->widget.y;
953 if (start_y - 2 >= count) {
954 y = start_y - 2 - count;
955 h = 2 + count;
956 } else {
957 if (start_y >= LINES - start_y - 1) {
958 y = 0;
959 h = start_y;
960 } else {
961 y = start_y + 1;
962 h = LINES - start_y - 1;
965 x = start - in->first_shown - 2 + start_x;
966 w = maxlen + 4;
967 if (x + w > COLS)
968 x = COLS - w;
969 if (x < 0)
970 x = 0;
971 if (x + w > COLS)
972 w = COLS;
973 input = in;
974 min_end = end;
975 query_height = h;
976 query_width = w;
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);
984 run_dlg (query_dlg);
985 q = NULL;
986 if (query_dlg->ret_value == B_ENTER){
987 listbox_get_current (query_list, &q, NULL);
988 if (q)
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);
995 if (i == B_USER)
996 return 1;
998 } else
999 beep ();
1000 return 0;
1003 void complete (WInput *in)
1005 int engine_flags;
1007 if (in->completions)
1008 engine_flags = DO_QUERY;
1009 else
1011 engine_flags = DO_INSERTION;
1013 if (show_all_if_ambiguous)
1014 engine_flags |= DO_QUERY;
1017 while (complete_engine (in, engine_flags));