completion: added escapes in command line on completion
[kaloumi3.git] / src / complete.c
blob6fde78a0bbd0663636154c4a9ccd156bcda607c7
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 "mhl/memory.h"
35 #include "mhl/escape.h"
37 #include "global.h"
38 #include "tty.h"
39 #include "win.h"
40 #include "color.h"
41 #include "dialog.h"
42 #include "widget.h"
43 #include "wtools.h"
44 #include "main.h"
45 #include "util.h"
46 #include "key.h" /* XCTRL and ALT macros */
48 typedef char *CompletionFunction (char *, int);
50 /* This flag is used in filename_completion_function */
51 static int ignore_filenames = 0;
53 /* This flag is used by command_completion_function */
54 /* to hint the filename_completion_function */
55 static int look_for_executables = 0;
57 static char *
58 filename_completion_function (char *text, int state)
60 static DIR *directory;
61 static char *filename = NULL;
62 static char *dirname = NULL;
63 static char *users_dirname = NULL;
64 static size_t filename_len;
65 int isdir = 1, isexec = 0;
67 struct dirent *entry = NULL;
69 /* If we're starting the match process, initialize us a bit. */
70 if (!state){
71 const char *temp;
73 g_free (dirname);
74 g_free (filename);
75 g_free (users_dirname);
77 if ((*text) && (temp = strrchr (text, PATH_SEP))){
78 filename = g_strdup (++temp);
79 dirname = g_strndup (text, temp - text);
80 } else {
81 dirname = g_strdup (".");
82 filename = g_strdup (text);
85 /* We aren't done yet. We also support the "~user" syntax. */
87 /* Save the version of the directory that the user typed. */
88 users_dirname = dirname;
90 // FIXME: memleak ?
91 dirname = tilde_expand (dirname);
92 canonicalize_pathname (dirname);
93 /* Here we should do something with variable expansion
94 and `command`.
95 Maybe a dream - UNIMPLEMENTED yet. */
97 directory = mc_opendir (dirname);
98 filename_len = strlen (filename);
101 /* Now that we have some state, we can read the directory. */
103 while (directory && (entry = mc_readdir (directory))){
104 /* Special case for no filename.
105 All entries except "." and ".." match. */
106 if (!filename_len){
107 if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
108 continue;
109 } else {
110 /* Otherwise, if these match up to the length of filename, then
111 it may be a match. */
112 if ((entry->d_name[0] != filename[0]) ||
113 ((NLENGTH (entry)) < filename_len) ||
114 strncmp (filename, entry->d_name, filename_len))
115 continue;
117 isdir = 1; isexec = 0;
119 char *tmp = g_malloc (3 + strlen (dirname) + NLENGTH (entry));
120 struct stat tempstat;
122 strcpy (tmp, dirname);
123 strcat (tmp, PATH_SEP_STR);
124 strcat (tmp, entry->d_name);
125 canonicalize_pathname (tmp);
126 /* Unix version */
127 if (!mc_stat (tmp, &tempstat)){
128 uid_t my_uid = getuid ();
129 gid_t my_gid = getgid ();
131 if (!S_ISDIR (tempstat.st_mode)){
132 isdir = 0;
133 if ((!my_uid && (tempstat.st_mode & 0111)) ||
134 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100)) ||
135 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010)) ||
136 (tempstat.st_mode & 0001))
137 isexec = 1;
140 g_free (tmp);
142 switch (look_for_executables)
144 case 2: if (!isexec)
145 continue;
146 break;
147 case 1: if (!isexec && !isdir)
148 continue;
149 break;
151 if (ignore_filenames && !isdir)
152 continue;
153 break;
156 if (!entry){
157 if (directory){
158 mc_closedir (directory);
159 directory = NULL;
161 g_free (dirname);
162 dirname = NULL;
163 g_free (filename);
164 filename = NULL;
165 g_free (users_dirname);
166 users_dirname = NULL;
167 return NULL;
168 } else {
169 char *temp;
171 if (users_dirname && (users_dirname[0] != '.' || users_dirname[1])){
172 int dirlen = strlen (users_dirname);
173 temp = g_malloc (3 + dirlen + NLENGTH (entry));
174 strcpy (temp, users_dirname);
175 /* We need a `/' at the end. */
176 if (users_dirname[dirlen - 1] != PATH_SEP){
177 temp[dirlen] = PATH_SEP;
178 temp[dirlen + 1] = 0;
180 strcat (temp, entry->d_name);
181 } else {
182 temp = g_malloc (2 + NLENGTH (entry));
183 strcpy (temp, entry->d_name);
185 if (isdir)
186 strcat (temp, PATH_SEP_STR);
187 return temp;
191 /* We assume here that text[0] == '~' , if you want to call it in another way,
192 you have to change the code */
193 static char *
194 username_completion_function (char *text, int state)
196 static struct passwd *entry;
197 static int userlen;
199 if (text[0] == '\\' && text[1] == '~') text++;
200 if (!state){ /* Initialization stuff */
201 setpwent ();
202 userlen = strlen (text + 1);
204 while ((entry = getpwent ()) != NULL){
205 /* Null usernames should result in all users as possible completions. */
206 if (!userlen)
207 break;
208 else if (text[1] == entry->pw_name[0] &&
209 !strncmp (text + 1, entry->pw_name, userlen))
210 break;
213 if (!entry){
214 endpwent ();
215 return NULL;
216 } else {
217 char *temp = g_malloc (3 + strlen (entry->pw_name));
219 *temp = '~';
220 strcpy (temp + 1, entry->pw_name);
221 strcat (temp, PATH_SEP_STR);
222 return temp;
226 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
227 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
228 extern char **environ;
229 #endif
231 /* We assume text [0] == '$' and want to have a look at text [1], if it is
232 equal to '{', so that we should append '}' at the end */
233 static char *
234 variable_completion_function (char *text, int state)
236 static char **env_p;
237 static int varlen, isbrace;
238 const char *p = NULL;
240 if (!state){ /* Initialization stuff */
241 isbrace = (text [1] == '{');
242 varlen = strlen (text + 1 + isbrace);
243 env_p = environ;
246 while (*env_p){
247 p = strchr (*env_p, '=');
248 if (p && p - *env_p >= varlen && !strncmp (text + 1 + isbrace, *env_p, varlen))
249 break;
250 env_p++;
253 if (!*env_p)
254 return NULL;
255 else {
256 char *temp = g_malloc (2 + 2 * isbrace + p - *env_p);
258 *temp = '$';
259 if (isbrace)
260 temp [1] = '{';
261 memcpy (temp + 1 + isbrace, *env_p, p - *env_p);
262 if (isbrace)
263 strcpy (temp + 2 + (p - *env_p), "}");
264 else
265 temp [1 + p - *env_p] = 0;
266 env_p++;
267 return temp;
271 #define whitespace(c) ((c) == ' ' || (c) == '\t')
272 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
274 static char **hosts = NULL;
275 static char **hosts_p = NULL;
276 static int hosts_alloclen = 0;
277 static void fetch_hosts (const char *filename)
279 FILE *file = fopen (filename, "r");
280 char buffer[256], *name;
281 register int i, start;
283 if (!file)
284 return;
286 while (fgets (buffer, 255, file) != NULL){
287 /* Skip to first character. */
288 for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++);
289 /* Ignore comments... */
290 if (buffer[i] == '#')
291 continue;
292 /* Handle $include. */
293 if (!strncmp (buffer + i, "$include ", 9)){
294 char *includefile = buffer + i + 9;
295 char *t;
297 /* Find start of filename. */
298 while (*includefile && whitespace (*includefile))
299 includefile++;
300 t = includefile;
302 /* Find end of filename. */
303 while (*t && !cr_whitespace (*t))
304 t++;
305 *t = '\0';
307 fetch_hosts (includefile);
308 continue;
311 /* Skip IP #s. */
312 while (buffer[i] && !cr_whitespace (buffer[i]))
313 i++;
315 /* Get the host names separated by white space. */
316 while (buffer[i] && buffer[i] != '#'){
317 while (buffer[i] && cr_whitespace (buffer[i]))
318 i++;
319 if (buffer[i] == '#')
320 continue;
321 for (start = i; buffer[i] && !cr_whitespace (buffer[i]); i++);
322 if (i - start == 0)
323 continue;
324 name = g_strndup (buffer + start, i - start);
326 char **host_p;
328 if (hosts_p - hosts >= hosts_alloclen){
329 int j = hosts_p - hosts;
331 hosts = g_realloc ((void *)hosts, ((hosts_alloclen += 30) + 1) * sizeof (char *));
332 hosts_p = hosts + j;
334 for (host_p = hosts; host_p < hosts_p; host_p++)
335 if (!strcmp (name, *host_p))
336 break; /* We do not want any duplicates */
337 if (host_p == hosts_p){
338 *(hosts_p++) = name;
339 *hosts_p = NULL;
340 } else
341 g_free (name);
345 fclose (file);
348 static char *
349 hostname_completion_function (char *text, int state)
351 static char **host_p;
352 static int textstart, textlen;
354 if (!state){ /* Initialization stuff */
355 const char *p;
357 if (hosts != NULL){
358 for (host_p = hosts; *host_p; host_p++)
359 g_free (*host_p);
360 g_free (hosts);
362 hosts = g_new (char *, (hosts_alloclen = 30) + 1);
363 *hosts = NULL;
364 hosts_p = hosts;
365 fetch_hosts ((p = getenv ("HOSTFILE")) ? p : "/etc/hosts");
366 host_p = hosts;
367 textstart = (*text == '@') ? 1 : 0;
368 textlen = strlen (text + textstart);
371 while (*host_p){
372 if (!textlen)
373 break; /* Match all of them */
374 else if (!strncmp (text + textstart, *host_p, textlen))
375 break;
376 host_p++;
379 if (!*host_p){
380 for (host_p = hosts; *host_p; host_p++)
381 g_free (*host_p);
382 g_free (hosts);
383 hosts = NULL;
384 return NULL;
385 } else {
386 char *temp = g_malloc (2 + strlen (*host_p));
388 if (textstart)
389 *temp = '@';
390 strcpy (temp + textstart, *host_p);
391 host_p++;
392 return temp;
397 * This is the function to call when the word to complete is in a position
398 * where a command word can be found. It looks around $PATH, looking for
399 * commands that match. It also scans aliases, function names, and the
400 * table of shell built-ins.
402 static char *
403 command_completion_function (char *text, int state)
405 text = mhl_shell_unescape_buf(text);
406 static const char *path_end;
407 static int isabsolute;
408 static int phase;
409 static int text_len;
410 static const char *const *words;
411 static char *path;
412 static char *cur_path;
413 static char *cur_word;
414 static int init_state;
415 static const char *const bash_reserved[] = {
416 "if", "then", "else", "elif", "fi", "case", "esac", "for",
417 "select", "while", "until", "do", "done", "in", "function", 0
419 static const char *const bash_builtins[] = {
420 "alias", "bg", "bind", "break", "builtin", "cd", "command",
421 "continue", "declare", "dirs", "echo", "enable", "eval",
422 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
423 "help", "history", "jobs", "kill", "let", "local", "logout",
424 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
425 "shift", "source", "suspend", "test", "times", "trap", "type",
426 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
428 char *p, *found;
430 if (!state) { /* Initialize us a little bit */
431 isabsolute = strchr (text, PATH_SEP) != 0;
432 look_for_executables = isabsolute ? 1 : 2;
433 if (!isabsolute) {
434 words = bash_reserved;
435 phase = 0;
436 text_len = strlen (text);
437 if (!path && (path = g_strdup (getenv ("PATH"))) != NULL) {
438 p = path;
439 path_end = strchr (p, 0);
440 while ((p = strchr (p, PATH_ENV_SEP))) {
441 *p++ = 0;
447 if (isabsolute) {
448 p = filename_completion_function (text, state);
449 if (!p)
451 look_for_executables = 0;
452 return 0;
455 SHELL_ESCAPED_STR e_p = mhl_shell_escape_dup(p);
456 mhl_mem_free(p);
457 return e_p.s;
460 found = NULL;
461 switch (phase) {
462 case 0: /* Reserved words */
463 while (*words) {
464 if (!strncmp (*words, text, text_len))
465 return g_strdup (*(words++));
466 words++;
468 phase++;
469 words = bash_builtins;
470 case 1: /* Builtin commands */
471 while (*words) {
472 if (!strncmp (*words, text, text_len))
473 return g_strdup (*(words++));
474 words++;
476 phase++;
477 if (!path)
478 break;
479 cur_path = path;
480 cur_word = NULL;
481 case 2: /* And looking through the $PATH */
482 while (!found) {
483 if (!cur_word) {
484 char *expanded;
486 if (cur_path >= path_end)
487 break;
488 expanded = tilde_expand (*cur_path ? cur_path : ".");
489 cur_word = concat_dir_and_file (expanded, text);
490 g_free (expanded);
491 canonicalize_pathname (cur_word);
492 cur_path = strchr (cur_path, 0) + 1;
493 init_state = state;
495 found =
496 filename_completion_function (cur_word,
497 state - init_state);
498 if (!found) {
499 g_free (cur_word);
500 cur_word = NULL;
505 if (!found) {
506 look_for_executables = 0;
507 g_free (path);
508 path = NULL;
509 return NULL;
511 if ((p = strrchr (found, PATH_SEP)) != NULL) {
512 p++;
513 p = g_strdup (p);
514 g_free (found);
515 /* TODO: shell escape? */
516 return p;
518 return found;
522 static int
523 match_compare (const void *a, const void *b)
525 return strcmp (*(char **)a, *(char **)b);
528 /* Returns an array of char * matches with the longest common denominator
529 in the 1st entry. Then a NULL terminated list of different possible
530 completions follows.
531 You have to supply your own CompletionFunction with the word you
532 want to complete as the first argument and an count of previous matches
533 as the second.
534 In case no matches were found we return NULL. */
535 static char **
536 completion_matches (char *text, CompletionFunction entry_function)
538 /* Number of slots in match_list. */
539 int match_list_size;
541 /* The list of matches. */
542 char **match_list = g_new (char *, (match_list_size = 30) + 1);
544 /* Number of matches actually found. */
545 int matches = 0;
547 /* Temporary string binder. */
548 char *string;
550 match_list[1] = NULL;
552 while ((string = (*entry_function) (text, matches)) != NULL){
553 if (matches + 1 == match_list_size)
554 match_list = (char **) g_realloc (match_list, ((match_list_size += 30) + 1) * sizeof (char *));
555 match_list[++matches] = string;
556 match_list[matches + 1] = NULL;
559 /* If there were any matches, then look through them finding out the
560 lowest common denominator. That then becomes match_list[0]. */
561 if (matches)
563 register int i = 1;
564 int low = 4096; /* Count of max-matched characters. */
566 /* If only one match, just use that. */
567 if (matches == 1){
568 match_list[0] = match_list[1];
569 match_list[1] = NULL;
570 } else {
571 int j;
573 qsort (match_list + 1, matches, sizeof (char *), match_compare);
575 /* And compare each member of the list with
576 the next, finding out where they stop matching.
577 If we find two equal strings, we have to put one away... */
579 j = i + 1;
580 while (j < matches + 1)
582 register int c1, c2, si;
584 for (si = 0;(c1 = match_list [i][si]) && (c2 = match_list [j][si]); si++)
585 if (c1 != c2) break;
587 if (!c1 && !match_list [j][si]){ /* Two equal strings */
588 g_free (match_list [j]);
589 j++;
590 if (j > matches)
591 break;
592 continue; /* Look for a run of equal strings */
593 } else
594 if (low > si) low = si;
595 if (i + 1 != j) /* So there's some gap */
596 match_list [i + 1] = match_list [j];
597 i++; j++;
599 matches = i;
600 match_list [matches + 1] = NULL;
601 match_list[0] = g_strndup(match_list[1], low);
603 } else { /* There were no matches. */
604 g_free (match_list);
605 match_list = NULL;
607 return match_list;
610 /* Check if directory completion is needed */
611 static int
612 check_is_cd (const char *text, int start, INPUT_COMPLETE_FLAGS flags)
614 const char *p, *q;
616 if (flags & INPUT_COMPLETE_CD)
617 return 1;
619 if (!(flags & INPUT_COMPLETE_COMMANDS))
620 return 0;
622 /* Skip initial spaces */
623 p = text;
624 q = text + start;
625 while (p < q && *p && isspace ((unsigned char) *p))
626 p++;
628 /* Check if the command is "cd" and the cursor is after it */
629 if (p[0] == 'c' && p[1] == 'd' && isspace ((unsigned char) p[2])
630 && (p + 2 < q))
631 return 1;
633 return 0;
636 /* Returns an array of matches, or NULL if none. */
637 static char **
638 try_complete (char *text, int *start, int *end, int flags)
640 int in_command_position = 0;
641 char *word, c;
642 char **matches = NULL;
643 const char *command_separator_chars = ";|&{(`";
644 char *p = NULL, *q = NULL, *r = NULL;
645 int is_cd = check_is_cd (text, *start, flags);
648 ignore_filenames = 0;
649 c = text [*end];
650 text [*end] = 0;
651 word = g_strdup (text + *start);
652 text [*end] = c;
654 /* Determine if this could be a command word. It is if it appears at
655 the start of the line (ignoring preceding whitespace), or if it
656 appears after a character that separates commands. And we have to
657 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
658 if (!is_cd && (flags & INPUT_COMPLETE_COMMANDS)){
659 int i = *start - 1;
660 for (i = *start - 1; i > -1; i--) {
661 if (text[i] == ' ' || text[i] == '\t'){
662 if (i == 0 ) continue;
663 if (text[i-1] == '\\') {
664 i--;
665 break;
669 if (i < 0)
670 in_command_position++;
671 else if (strchr (command_separator_chars, text[i])){
672 register int this_char, prev_char;
674 in_command_position++;
676 if (i){
677 /* Handle the two character tokens `>&', `<&', and `>|'.
678 We are not in a command position after one of these. */
679 this_char = text[i];
680 prev_char = text[i - 1];
682 if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
683 (this_char == '|' && prev_char == '>'))
684 in_command_position = 0;
685 else if (i > 0 && text [i-1] == '\\') /* Quoted */
686 in_command_position = 0;
691 if (flags & INPUT_COMPLETE_COMMANDS)
692 p = strrchr (word, '`');
693 if (flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
694 q = strrchr (word, '$');
695 if (flags & INPUT_COMPLETE_HOSTNAMES)
696 r = strrchr (word, '@');
697 if (q && q [1] == '(' && INPUT_COMPLETE_COMMANDS){
698 if (q > p)
699 p = q + 1;
700 q = NULL;
703 /* Command substitution? */
704 if (p > q && p > r){
705 matches = completion_matches (p + 1, command_completion_function);
706 if (matches)
707 *start += p + 1 - word;
710 /* Variable name? */
711 else if (q > p && q > r){
712 matches = completion_matches (q, variable_completion_function);
713 if (matches)
714 *start += q - word;
717 /* Starts with '@', then look through the known hostnames for
718 completion first. */
719 else if (r > p && r > q){
720 matches = completion_matches (r, hostname_completion_function);
721 if (matches)
722 *start += r - word;
725 /* Starts with `~' and there is no slash in the word, then
726 try completing this word as a username. */
727 if (!matches && *word == '~' && (flags & INPUT_COMPLETE_USERNAMES) && !strchr (word, PATH_SEP))
728 matches = completion_matches (word, username_completion_function);
731 /* And finally if this word is in a command position, then
732 complete over possible command names, including aliases, functions,
733 and command names. */
734 if (!matches && in_command_position)
735 matches = completion_matches (word, command_completion_function);
737 else if (!matches && (flags & INPUT_COMPLETE_FILENAMES)){
738 if (is_cd)
739 ignore_filenames = 1;
740 matches = completion_matches (word, filename_completion_function);
741 ignore_filenames = 0;
742 if (!matches && is_cd && *word != PATH_SEP && *word != '~'){
743 char *p, *q = text + *start;
745 for (p = text; *p && p < q; p++){
746 if (*p == ' ' || *p == '\t') {
747 if (p == text) continue;
748 if (*(p-1) == '\\') {
749 p--;
750 break;
754 if (!strncmp (p, "cd", 2))
755 for (p += 2; *p && p < q && (*p == ' ' || *p == '\t'); p++){
756 if (p == text) continue;
757 if (*(p-1) == '\\') {
758 p--;
759 break;
762 if (p == q){
763 char * const cdpath_ref = g_strdup (getenv ("CDPATH"));
764 char *cdpath = cdpath_ref;
765 char c, *s, *r;
767 if (cdpath == NULL)
768 c = 0;
769 else
770 c = ':';
771 while (!matches && c == ':'){
772 s = strchr (cdpath, ':');
773 if (s == NULL)
774 s = strchr (cdpath, 0);
775 c = *s;
776 *s = 0;
777 if (*cdpath){
778 r = concat_dir_and_file (cdpath, word);
779 ignore_filenames = 1;
780 matches = completion_matches (r, filename_completion_function);
781 ignore_filenames = 0;
782 g_free (r);
784 *s = c;
785 cdpath = s + 1;
787 g_free (cdpath_ref);
792 g_free (word);
794 return matches;
797 void free_completions (WInput *in)
799 char **p;
801 if (!in->completions)
802 return;
803 for (p=in->completions; *p; p++)
804 g_free (*p);
805 g_free (in->completions);
806 in->completions = NULL;
809 static int query_height, query_width;
810 static WInput *input;
811 static int min_end;
812 static int start, end;
814 static int insert_text (WInput *in, char *text, ssize_t len)
816 len = min (len, (ssize_t) strlen (text)) + start - end;
817 if (strlen (in->buffer) + len >= (size_t) in->current_max_len){
818 /* Expand the buffer */
819 char *narea = g_realloc (in->buffer, in->current_max_len + len + in->field_len);
820 if (narea){
821 in->buffer = narea;
822 in->current_max_len += len + in->field_len;
825 if (strlen (in->buffer)+1 < (size_t) in->current_max_len){
826 if (len > 0){
827 int i = strlen (&in->buffer [end]);
828 for (; i >= 0; i--)
829 in->buffer [end + len + i] = in->buffer [end + i];
830 } else if (len < 0){
831 char *p = in->buffer + end + len, *q = in->buffer + end;
832 while (*q)
833 *(p++) = *(q++);
834 *p = 0;
836 memcpy (in->buffer + start, text, len - start + end);
837 in->point += len;
838 update_input (in, 1);
839 end += len;
841 return len != 0;
844 static cb_ret_t
845 query_callback (Dlg_head *h, dlg_msg_t msg, int parm)
847 switch (msg) {
848 case DLG_KEY:
849 switch (parm) {
850 case KEY_LEFT:
851 case KEY_RIGHT:
852 h->ret_value = 0;
853 dlg_stop (h);
854 return MSG_HANDLED;
856 case KEY_BACKSPACE:
857 if (end == min_end) {
858 h->ret_value = 0;
859 dlg_stop (h);
860 return MSG_HANDLED;
861 } else {
862 WLEntry *e, *e1;
864 e1 = e = ((WListbox *) (h->current))->list;
865 do {
866 if (!strncmp
867 (input->buffer + start, e1->text,
868 end - start - 1)) {
869 listbox_select_entry ((WListbox *) (h->current),
870 e1);
871 handle_char (input, parm);
872 end--;
873 send_message (h->current, WIDGET_DRAW, 0);
874 break;
876 e1 = e1->next;
877 } while (e != e1);
879 return MSG_HANDLED;
881 default:
882 if (parm > 0xff || !is_printable (parm)) {
883 if (is_in_input_map (input, parm) == 2) {
884 if (end == min_end)
885 return MSG_HANDLED;
886 h->ret_value = B_USER; /* This means we want to refill the
887 list box and start again */
888 dlg_stop (h);
889 return MSG_HANDLED;
890 } else
891 return MSG_NOT_HANDLED;
892 } else {
893 WLEntry *e, *e1;
894 int need_redraw = 0;
895 int low = 4096;
896 char *last_text = NULL;
898 e1 = e = ((WListbox *) (h->current))->list;
899 do {
900 if (!strncmp
901 (input->buffer + start, e1->text, end - start)) {
902 if (e1->text[end - start] == parm) {
903 if (need_redraw) {
904 register int c1, c2, si;
906 for (si = end - start + 1;
907 (c1 = last_text[si])
908 && (c2 = e1->text[si]); si++)
909 if (c1 != c2)
910 break;
911 if (low > si)
912 low = si;
913 last_text = e1->text;
914 need_redraw = 2;
915 } else {
916 need_redraw = 1;
917 listbox_select_entry ((WListbox *) (h->
918 current),
919 e1);
920 last_text = e1->text;
924 e1 = e1->next;
925 } while (e != e1);
926 if (need_redraw == 2) {
927 insert_text (input, last_text, low);
928 send_message (h->current, WIDGET_DRAW, 0);
929 } else if (need_redraw == 1) {
930 h->ret_value = B_ENTER;
931 dlg_stop (h);
934 return MSG_HANDLED;
936 break;
938 default:
939 return default_dlg_callback (h, msg, parm);
943 #define DO_INSERTION 1
944 #define DO_QUERY 2
945 /* Returns 1 if the user would like to see us again */
946 static int
947 complete_engine (WInput *in, int what_to_do)
949 char *complete = NULL;
950 if (in->completions && in->point != end)
951 free_completions (in);
952 if (!in->completions){
953 end = in->point;
954 for (start = end ? end - 1 : 0; start > -1; start--)
955 if (strchr (" \t;|<>", in->buffer [start])){
956 if (start > 0 && in->buffer [start-1] == '\\')
957 continue;
958 else
959 break;
961 if (start < end)
962 start++;
963 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
966 if (in->completions){
967 if (what_to_do & DO_INSERTION || ((what_to_do & DO_QUERY) && !in->completions[1])) {
968 char * complete = in->completions [0];
969 if (insert_text (in, complete, strlen (complete))){
970 if (in->completions [1])
971 beep ();
972 else
973 free_completions (in);
974 } else
975 beep ();
977 if ((what_to_do & DO_QUERY) && in->completions && in->completions [1]) {
978 int maxlen = 0, i, count = 0;
979 int x, y, w, h;
980 int start_x, start_y;
981 char **p, *q;
982 Dlg_head *query_dlg;
983 WListbox *query_list;
985 for (p=in->completions + 1; *p; count++, p++) {
986 if ((i = strlen (*p)) > maxlen)
987 maxlen = i;
989 start_x = in->widget.x;
990 start_y = in->widget.y;
991 if (start_y - 2 >= count) {
992 y = start_y - 2 - count;
993 h = 2 + count;
994 } else {
995 if (start_y >= LINES - start_y - 1) {
996 y = 0;
997 h = start_y;
998 } else {
999 y = start_y + 1;
1000 h = LINES - start_y - 1;
1003 x = start - in->first_shown - 2 + start_x;
1004 w = maxlen + 4;
1005 if (x + w > COLS)
1006 x = COLS - w;
1007 if (x < 0)
1008 x = 0;
1009 if (x + w > COLS)
1010 w = COLS;
1011 input = in;
1012 min_end = end;
1013 query_height = h;
1014 query_width = w;
1015 query_dlg = create_dlg (y, x, query_height, query_width,
1016 dialog_colors, query_callback,
1017 "[Completion]", NULL, DLG_COMPACT);
1018 query_list = listbox_new (1, 1, w - 2, h - 2, NULL);
1019 add_widget (query_dlg, query_list);
1020 for (p = in->completions + 1; *p; p++)
1021 listbox_add_item (query_list, 0, 0, *p, NULL);
1022 run_dlg (query_dlg);
1023 q = NULL;
1024 if (query_dlg->ret_value == B_ENTER){
1025 listbox_get_current (query_list, &q, NULL);
1026 if (q)
1027 insert_text (in, q, strlen (q));
1029 if (q || end != min_end)
1030 free_completions (in);
1031 i = query_dlg->ret_value; /* B_USER if user wants to start over again */
1032 destroy_dlg (query_dlg);
1033 if (i == B_USER)
1034 return 1;
1036 } else
1037 beep ();
1038 return 0;
1041 //void complete (WInput *in, COMPLETION_STYLE style)
1042 void complete (WInput *in)
1044 int engine_flags;
1046 if (in->completions)
1047 engine_flags = DO_QUERY;
1048 else
1050 engine_flags = DO_INSERTION;
1052 if (show_all_if_ambiguous)
1053 engine_flags |= DO_QUERY;
1056 while (complete_engine (in, engine_flags));