1999-09-09 Federico Mena Quintero <federico@redhat.com>
[midnight-commander.git] / src / complete.c
blobc59a584d6b27c204df2f778fd24a671b7517005c
1 /* Input line filename/username/hostname/variable/command completion.
2 (Let mc type for you...)
4 Copyright (C) 1995 The Free Software Foundation
6 Written by: 1995 Jakub Jelinek
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
22 #include <config.h>
23 #include "tty.h"
24 #include <stdio.h>
25 #include <string.h>
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
30 /* unistd.h defines _POSIX_VERSION on POSIX.1 systems. */
31 #if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION)
32 # ifdef NEEDS_LOCAL_DIRENT_H
33 # include "dirent.h"
34 # else
35 # include <dirent.h>
36 # endif
37 # define NLENGTH(dirent) (strlen ((dirent)->d_name))
38 #else
39 # define dirent direct
40 # define NLENGTH(dirent) ((dirent)->d_namlen)
42 # ifdef HAVE_SYS_NDIR_H
43 # include <sys/ndir.h>
44 # endif /* HAVE_SYS_NDIR_H */
46 # ifdef HAVE_SYS_DIR_H
47 # include <sys/dir.h>
48 # endif /* HAVE_SYS_DIR_H */
50 # ifdef HAVE_NDIR_H
51 # include <ndir.h>
52 # endif /* HAVE_NDIR_H */
53 #endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */
54 #include <sys/types.h>
55 #include <sys/stat.h>
56 #ifndef HAS_NO_GRP_PWD_H
57 # include <pwd.h>
58 #endif
60 #include "global.h"
61 #include "win.h"
62 #include "color.h"
63 #include "dlg.h"
64 #include "widget.h"
65 #include "dialog.h"
66 #include "wtools.h"
67 #include "complete.h"
68 #include "main.h"
69 #include "key.h" /* XCTRL and ALT macros */
70 #include "../vfs/vfs.h"
72 /* This flag is used in filename_completion_function */
73 int ignore_filenames = 0;
75 /* This flag is used by command_completion_function */
76 /* to hint the filename_completion_function */
77 int look_for_executables = 0;
79 static char *
80 filename_completion_function (char *text, int state)
82 static DIR *directory;
83 static char *filename = NULL;
84 static char *dirname = NULL;
85 static char *users_dirname = NULL;
86 static int filename_len;
87 int isdir = 1, isexec = 0;
89 struct dirent *entry = NULL;
91 /* If we're starting the match process, initialize us a bit. */
92 if (!state){
93 char *temp;
95 if (dirname)
96 g_free (dirname);
97 if (filename)
98 g_free (filename);
99 if (users_dirname)
100 g_free (users_dirname);
102 filename = g_strdup (text);
103 if (!*text)
104 text = ".";
105 dirname = g_strdup (text);
107 temp = strrchr (dirname, PATH_SEP);
109 if (temp){
110 strcpy (filename, ++temp);
111 *temp = 0;
113 else
114 strcpy (dirname, ".");
116 /* We aren't done yet. We also support the "~user" syntax. */
118 /* Save the version of the directory that the user typed. */
119 users_dirname = g_strdup (dirname);
121 char *temp_dirname;
123 temp_dirname = tilde_expand (dirname);
124 if (!temp_dirname){
125 g_free (dirname);
126 g_free (users_dirname);
127 g_free (filename);
128 dirname = users_dirname = filename = NULL;
129 return NULL;
131 g_free (dirname);
132 dirname = temp_dirname;
133 canonicalize_pathname (dirname);
134 /* Here we should do something with variable expansion
135 and `command`.
136 Maybe a dream - UNIMPLEMENTED yet. */
138 directory = mc_opendir (dirname);
139 filename_len = strlen (filename);
142 /* Now that we have some state, we can read the directory. */
144 while (directory && (entry = mc_readdir (directory))){
145 /* Special case for no filename.
146 All entries except "." and ".." match. */
147 if (!filename_len){
148 if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
149 continue;
150 } else {
151 /* Otherwise, if these match up to the length of filename, then
152 it may be a match. */
153 if ((entry->d_name[0] != filename[0]) ||
154 ((NLENGTH (entry)) < filename_len) ||
155 strncmp (filename, entry->d_name, filename_len))
156 continue;
158 isdir = 1; isexec = 0;
160 char *tmp = g_malloc (3 + strlen (dirname) + NLENGTH (entry));
161 struct stat tempstat;
163 strcpy (tmp, dirname);
164 strcat (tmp, PATH_SEP_STR);
165 strcat (tmp, entry->d_name);
166 canonicalize_pathname (tmp);
167 /* Unix version */
168 if (!mc_stat (tmp, &tempstat)){
169 uid_t my_uid = getuid ();
170 gid_t my_gid = getgid ();
172 if (!S_ISDIR (tempstat.st_mode)){
173 isdir = 0;
174 if ((!my_uid && (tempstat.st_mode & 0111)) ||
175 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100)) ||
176 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010)) ||
177 (tempstat.st_mode & 0001))
178 isexec = 1;
181 g_free (tmp);
183 switch (look_for_executables)
185 case 2: if (!isexec)
186 continue;
187 break;
188 case 1: if (!isexec && !isdir)
189 continue;
190 break;
192 if (ignore_filenames && !isdir)
193 continue;
194 break;
197 if (!entry){
198 if (directory){
199 mc_closedir (directory);
200 directory = NULL;
202 if (dirname){
203 g_free (dirname);
204 dirname = NULL;
206 if (filename){
207 g_free (filename);
208 filename = NULL;
210 if (users_dirname){
211 g_free (users_dirname);
212 users_dirname = NULL;
214 return NULL;
215 } else {
216 char *temp;
218 if (users_dirname && (users_dirname[0] != '.' || users_dirname[1])){
219 int dirlen = strlen (users_dirname);
220 temp = g_malloc (3 + dirlen + NLENGTH (entry));
221 strcpy (temp, users_dirname);
222 /* We need a `/' at the end. */
223 if (users_dirname[dirlen - 1] != PATH_SEP){
224 temp[dirlen] = PATH_SEP;
225 temp[dirlen + 1] = 0;
227 strcat (temp, entry->d_name);
228 } else {
229 temp = g_malloc (2 + NLENGTH (entry));
230 strcpy (temp, entry->d_name);
232 if (isdir)
233 strcat (temp, PATH_SEP_STR);
234 return temp;
238 /* We assume here that text[0] == '~' , if you want to call it in another way,
239 you have to change the code */
240 #ifdef OS2_NT
241 char *username_completion_function (char *text, int state)
243 return NULL;
245 #else
246 static char *
247 username_completion_function (char *text, int state)
249 static struct passwd *entry;
250 static int userlen;
252 if (!state){ /* Initialization stuff */
253 setpwent ();
254 userlen = strlen (text + 1);
256 while ((entry = getpwent ()) != NULL){
257 /* Null usernames should result in all users as possible completions. */
258 if (!userlen)
259 break;
260 else if (text[1] == entry->pw_name[0] &&
261 !strncmp (text + 1, entry->pw_name, userlen))
262 break;
265 if (!entry){
266 endpwent ();
267 return NULL;
268 } else {
269 char *temp = g_malloc (3 + strlen (entry->pw_name));
271 *temp = '~';
272 strcpy (temp + 1, entry->pw_name);
273 strcat (temp, PATH_SEP_STR);
274 return temp;
278 extern char **environ;
279 #endif /* OS2_NT */
281 /* We assume text [0] == '$' and want to have a look at text [1], if it is
282 equal to '{', so that we should append '}' at the end */
283 static char *
284 variable_completion_function (char *text, int state)
286 static char **env_p;
287 static int varlen, isbrace;
288 char *p = 0;
290 if (!state){ /* Initialization stuff */
291 isbrace = (text [1] == '{');
292 varlen = strlen (text + 1 + isbrace);
293 env_p = environ;
296 while (*env_p){
297 p = strchr (*env_p, '=');
298 if (p && p - *env_p >= varlen && !strncmp (text + 1 + isbrace, *env_p, varlen))
299 break;
300 env_p++;
303 if (!*env_p)
304 return NULL;
305 else {
306 char *temp = g_malloc (2 + 2 * isbrace + p - *env_p);
308 *temp = '$';
309 if (isbrace)
310 temp [1] = '{';
311 strncpy (temp + 1 + isbrace, *env_p, p - *env_p);
312 if (isbrace)
313 strcpy (temp + 2 + (p - *env_p), "}");
314 else
315 temp [1 + p - *env_p] = 0;
316 env_p++;
317 return temp;
321 #define whitespace(c) ((c) == ' ' || (c) == '\t')
322 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
324 static char **hosts = NULL;
325 static char **hosts_p = NULL;
326 static int hosts_alloclen = 0;
327 static void fetch_hosts (char *filename)
329 FILE *file = fopen (filename, "r");
330 char *temp, buffer[256], *name;
331 register int i, start;
333 if (!file)
334 return;
336 while ((temp = fgets (buffer, 255, file)) != NULL){
337 /* Skip to first character. */
338 for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++);
339 /* Ignore comments... */
340 if (buffer[i] == '#')
341 continue;
342 /* Handle $include. */
343 if (!strncmp (buffer + i, "$include ", 9)){
344 char *includefile = buffer + i + 9;
345 char *t;
347 /* Find start of filename. */
348 while (*includefile && whitespace (*includefile))
349 includefile++;
350 t = includefile;
352 /* Find end of filename. */
353 while (*t && !cr_whitespace (*t))
354 t++;
355 *t = '\0';
357 fetch_hosts (includefile);
358 continue;
361 /* Skip IP #s. */
362 for (; buffer[i] && !cr_whitespace (buffer[i]); i++);
364 /* Get the host names separated by white space. */
365 while (buffer[i] && buffer[i] != '#'){
366 for (; i && cr_whitespace (buffer[i]); i++);
367 if (buffer[i] == '#')
368 continue;
369 for (start = i; buffer[i] && !cr_whitespace (buffer[i]); i++);
370 if (i - start == 0)
371 continue;
372 name = (char *) g_malloc (i - start + 1);
373 strncpy (name, buffer + start, i - start);
374 name [i - start] = 0;
376 char **host_p;
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 *));
382 hosts_p = hosts + j;
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){
388 *(hosts_p++) = name;
389 *hosts_p = NULL;
390 } else
391 g_free (name);
395 fclose (file);
398 static char *
399 hostname_completion_function (char *text, int state)
401 static char **host_p;
402 static int textstart, textlen;
404 if (!state){ /* Initialization stuff */
405 char *p;
407 if (hosts != NULL){
408 for (host_p = hosts; *host_p; host_p++)
409 g_free (*host_p);
410 g_free (hosts);
412 hosts = g_new (char *, (hosts_alloclen = 30) + 1);
413 *hosts = NULL;
414 hosts_p = hosts;
415 fetch_hosts ((p = getenv ("HOSTFILE")) ? p : "/etc/hosts");
416 host_p = hosts;
417 textstart = (*text == '@') ? 1 : 0;
418 textlen = strlen (text + textstart);
421 while (*host_p){
422 if (!textlen)
423 break; /* Match all of them */
424 else if (!strncmp (text + textstart, *host_p, textlen))
425 break;
426 host_p++;
429 if (!*host_p){
430 for (host_p = hosts; *host_p; host_p++)
431 g_free (*host_p);
432 g_free (hosts);
433 hosts = NULL;
434 return NULL;
435 } else {
436 char *temp = g_malloc (2 + strlen (*host_p));
438 if (textstart)
439 *temp = '@';
440 strcpy (temp + textstart, *host_p);
441 host_p++;
442 return temp;
447 * This is the function to call when the word to complete is in a position
448 * where a command word can be found. It looks around $PATH, looking for
449 * commands that match. It also scans aliases, function names, and the
450 * table of shell built-ins.
452 static char *
453 command_completion_function (char *text, int state)
455 static int isabsolute;
456 static int phase;
457 static int text_len;
458 static char **words;
459 static char *path;
460 static char *cur_path;
461 static char *cur_word;
462 static int init_state;
463 static char *bash_reserved [] = { "if", "then", "else", "elif", "fi",
464 "case", "esac", "for", "select", "while",
465 "until", "do", "done", "in", "function" , 0};
466 static char *bash_builtins [] = { "alias", "bg", "bind", "break", "builtin",
467 "cd", "command", "continue", "declare",
468 "dirs", "echo", "enable", "eval", "exec",
469 "exit", "export", "fc", "fg", "getopts",
470 "hash", "help", "history", "jobs", "kill",
471 "let", "local", "logout", "popd", "pushd",
472 "pwd", "read", "readonly", "return", "set",
473 "shift", "source", "suspend", "test",
474 "times", "trap", "type", "typeset",
475 "ulimit", "umask", "unalias", "unset",
476 "wait" , 0};
477 char *p, *found;
479 if (!state){ /* Initialize us a little bit */
480 isabsolute = strchr (text, PATH_SEP) != 0;
481 look_for_executables = isabsolute ? 1 : 2;
482 if (!isabsolute){
483 words = bash_reserved;
484 phase = 0;
485 text_len = strlen (text);
486 p = getenv ("PATH");
487 if (!p)
488 path = NULL;
489 else {
490 path = g_malloc (strlen (p) + 2);
491 strcpy (path, p);
492 path [strlen (p) + 1] = 0;
493 p = strchr (path, PATH_ENV_SEP);
494 while (p){
495 *p = 0;
496 p = strchr (p + 1, PATH_ENV_SEP);
502 if (isabsolute){
503 p = filename_completion_function (text, state);
504 if (!p)
505 look_for_executables = 0;
506 return p;
509 found = NULL;
510 switch (phase){
511 case 0: /* Reserved words */
512 while (*words){
513 if (!strncmp (*words, text, text_len))
514 return g_strdup (*(words++));
515 words++;
517 phase++;
518 words = bash_builtins;
519 case 1: /* Builtin commands */
520 while (*words){
521 if (!strncmp (*words, text, text_len))
522 return g_strdup (*(words++));
523 words++;
525 phase++;
526 if (!path)
527 break;
528 cur_path = path;
529 cur_word = NULL;
530 case 2: /* And looking through the $PATH */
531 while (!found){
532 if (!cur_word){
533 char *expanded;
535 if (!*cur_path)
536 break;
537 expanded = tilde_expand (cur_path);
538 if (!expanded){
539 g_free (path);
540 path = NULL;
541 return NULL;
543 p = canonicalize_pathname (expanded);
544 cur_word = g_malloc (strlen (p) + 2 + text_len);
545 strcpy (cur_word, p);
546 if (cur_word [strlen (cur_word) - 1] != PATH_SEP)
547 strcat (cur_word, PATH_SEP_STR);
548 strcat (cur_word, text);
549 g_free (p);
550 cur_path = strchr (cur_path, 0) + 1;
551 init_state = state;
553 found = filename_completion_function (cur_word, state - init_state);
554 if (!found){
555 g_free (cur_word);
556 cur_word = NULL;
561 if (!found){
562 look_for_executables = 0;
563 if (path)
564 g_free (path);
565 return NULL;
567 if ((p = strrchr (found, PATH_SEP)) != NULL){
568 p++;
569 p = g_strdup (p);
570 g_free (found);
571 return p;
573 return found;
577 static int
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
585 completions follows.
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
588 as the second.
589 In case no matches were found we return NULL. */
590 static char **
591 completion_matches (char *text, CompletionFunction entry_function)
593 /* Number of slots in match_list. */
594 int match_list_size;
596 /* The list of matches. */
597 char **match_list = g_new (char *, (match_list_size = 30) + 1);
599 /* Number of matches actually found. */
600 int matches = 0;
602 /* Temporary string binder. */
603 char *string;
605 match_list[1] = NULL;
607 while ((string = (*entry_function) (text, matches)) != 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]. */
616 if (matches)
618 register int i = 1;
619 int low = 4096; /* Count of max-matched characters. */
621 /* If only one match, just use that. */
622 if (matches == 1){
623 match_list[0] = match_list[1];
624 match_list[1] = (char *)NULL;
625 } else {
626 int j;
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... */
634 j = i + 1;
635 while (j < matches + 1)
637 register int c1, c2, si;
639 for (si = 0;(c1 = match_list [i][si]) && (c2 = match_list [j][si]); si++)
640 if (c1 != c2) break;
642 if (!c1 && !match_list [j][si]){ /* Two equal strings */
643 g_free (match_list [j]);
644 j++;
645 if (j > matches)
646 break;
647 } else
648 if (low > si) low = si;
649 if (i + 1 != j) /* So there's some gap */
650 match_list [i + 1] = match_list [j];
651 i++; j++;
653 matches = i;
654 match_list [matches + 1] = NULL;
655 match_list[0] = g_malloc (low + 1);
656 strncpy (match_list[0], match_list[1], low);
657 match_list[0][low] = 0;
659 } else { /* There were no matches. */
660 g_free (match_list);
661 match_list = NULL;
663 return match_list;
666 static int
667 check_is_cd (char *text, int start, int flags)
669 char *p, *q = text + start;
671 for (p = text; *p && p < q && (*p == ' ' || *p == '\t'); p++);
672 if (((flags & INPUT_COMPLETE_COMMANDS) &&
673 !strncmp (p, "cd", 2) && (p [2] == ' ' || p [2] == '\t') &&
674 p + 2 < q) ||
675 (flags & INPUT_COMPLETE_CD))
676 return 1;
677 return 0;
680 /* Returns an array of matches, or NULL if none. */
681 static char **
682 try_complete (char *text, int *start, int *end, int flags)
684 int in_command_position = 0, i;
685 char *word, c;
686 char **matches = NULL;
687 char *command_separator_chars = ";|&{(`";
688 char *p = NULL, *q = NULL, *r = NULL;
689 int is_cd = check_is_cd (text, *start, flags);
691 ignore_filenames = 0;
692 c = text [*end];
693 text [*end] = 0;
694 word = g_strdup (text + *start);
695 text [*end] = c;
697 /* Determine if this could be a command word. It is if it appears at
698 the start of the line (ignoring preceding whitespace), or if it
699 appears after a character that separates commands. And we have to
700 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
701 if (!is_cd && (flags & INPUT_COMPLETE_COMMANDS)){
702 i = *start - 1;
703 while (i > -1 && (text[i] == ' ' || text[i] == '\t'))
704 i--;
705 if (i < 0)
706 in_command_position++;
707 else if (strchr (command_separator_chars, text[i])){
708 register int this_char, prev_char;
710 in_command_position++;
712 if (i){
713 /* Handle the two character tokens `>&', `<&', and `>|'.
714 We are not in a command position after one of these. */
715 this_char = text[i];
716 prev_char = text[i - 1];
718 if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
719 (this_char == '|' && prev_char == '>'))
720 in_command_position = 0;
721 else if (i > 0 && text [i-1] == '\\') /* Quoted */
722 in_command_position = 0;
727 if (flags & INPUT_COMPLETE_COMMANDS)
728 p = strrchr (word, '`');
729 if (flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
730 q = strrchr (word, '$');
731 if (flags & INPUT_COMPLETE_HOSTNAMES)
732 r = strrchr (word, '@');
733 if (q && q [1] == '(' && INPUT_COMPLETE_COMMANDS){
734 if (q > p)
735 p = q + 1;
736 q = NULL;
739 /* Command substitution? */
740 if (p > q && p > r){
741 matches = completion_matches (p + 1, command_completion_function);
742 if (matches)
743 *start += p + 1 - word;
746 /* Variable name? */
747 else if (q > p && q > r){
748 matches = completion_matches (q, variable_completion_function);
749 if (matches)
750 *start += q - word;
753 /* Starts with '@', then look through the known hostnames for
754 completion first. */
755 else if (r > p && r > q){
756 matches = completion_matches (r, hostname_completion_function);
757 if (matches)
758 *start += r - word;
761 /* Starts with `~' and there is no slash in the word, then
762 try completing this word as a username. */
763 if (!matches && *word == '~' && (flags & INPUT_COMPLETE_USERNAMES) && !strchr (word, PATH_SEP))
764 matches = completion_matches (word, username_completion_function);
767 /* And finally if this word is in a command position, then
768 complete over possible command names, including aliases, functions,
769 and command names. */
770 if (!matches && in_command_position)
771 matches = completion_matches (word, command_completion_function);
773 else if (!matches && (flags & INPUT_COMPLETE_FILENAMES)){
774 if (is_cd)
775 ignore_filenames = 1;
776 matches = completion_matches (word, filename_completion_function);
777 ignore_filenames = 0;
778 if (!matches && is_cd && *word != PATH_SEP && *word != '~'){
779 char *p, *q = text + *start;
781 for (p = text; *p && p < q && (*p == ' ' || *p == '\t'); p++);
782 if (!strncmp (p, "cd", 2))
783 for (p += 2; *p && p < q && (*p == ' ' || *p == '\t'); p++);
784 if (p == q){
785 char *cdpath = getenv ("CDPATH");
786 char c, *s, *r;
788 if (cdpath == NULL)
789 c = 0;
790 else
791 c = ':';
792 while (!matches && c == ':'){
793 s = strchr (cdpath, ':');
794 if (s == NULL)
795 s = strchr (cdpath, 0);
796 c = *s;
797 *s = 0;
798 if (*cdpath){
799 r = concat_dir_and_file (cdpath, word);
800 ignore_filenames = 1;
801 matches = completion_matches (r, filename_completion_function);
802 ignore_filenames = 0;
803 g_free (r);
805 *s = c;
806 cdpath = s + 1;
812 if (word)
813 g_free (word);
815 return matches;
818 void free_completions (WInput *in)
820 char **p;
822 if (!in->completions)
823 return;
824 for (p=in->completions; *p; p++)
825 g_free (*p);
826 g_free (in->completions);
827 in->completions = NULL;
830 static int query_height, query_width;
831 static WInput *input;
832 static int start, end, min_end;
834 static int insert_text (WInput *in, char *text, int len)
836 len = min (len, strlen (text)) + start - end;
837 if (strlen (in->buffer) + len >= in->current_max_len){
838 /* Expand the buffer */
839 char *narea = g_realloc (in->buffer, in->current_max_len + len + in->field_len);
840 if (narea){
841 in->buffer = narea;
842 in->current_max_len += len + in->field_len;
845 if (strlen (in->buffer)+1 < in->current_max_len){
846 if (len > 0){
847 int i, l = strlen (&in->buffer [end]);
848 for (i = l + 1; i >= 0; i--)
849 in->buffer [end + len + i] = in->buffer [end + i];
850 } else if (len < 0){
851 char *p = in->buffer + end + len, *q = in->buffer + end;
852 while (*q)
853 *(p++) = *(q++);
854 *p = 0;
856 strncpy (in->buffer + start, text, len - start + end);
857 in->point += len;
858 update_input (in, 1);
859 end += len;
861 return len != 0;
864 static int query_callback (Dlg_head * h, int Par, int Msg)
866 switch (Msg) {
867 case DLG_DRAW:
868 #ifndef HAVE_X
869 attrset (COLOR_NORMAL);
870 dlg_erase (h);
871 draw_box (h, 0, 0, query_height, query_width);
872 #endif
873 break;
875 case DLG_KEY:
876 switch (Par) {
877 case KEY_LEFT:
878 case KEY_RIGHT:
879 h->ret_value = 0;
880 dlg_stop (h);
881 return 1;
883 case 0177:
884 case KEY_BACKSPACE:
885 case XCTRL('h'):
886 if (end == min_end){
887 h->ret_value = 0;
888 dlg_stop (h);
889 return 1;
890 } else {
891 WLEntry *e, *e1;
893 e1 = e = ((WListbox *)(h->current->widget))->list;
894 do {
895 if (!strncmp (input->buffer + start, e1->text, end - start - 1)){
896 listbox_select_entry((WListbox *)(h->current->widget), e1);
897 handle_char (input, Par);
898 end--;
899 send_message (h, h->current->widget,
900 WIDGET_DRAW, 0);
901 break;
903 e1 = e1->next;
904 } while (e != e1);
906 return 1;
908 default:
909 if (Par > 0xff || !is_printable (Par)){
910 if (is_in_input_map (input, Par) == 2){
911 if (end == min_end)
912 return 1;
913 h->ret_value = B_USER; /* This means we want to refill the
914 list box and start again */
915 dlg_stop (h);
916 return 1;
917 } else
918 return 0;
919 } else {
920 WLEntry *e, *e1;
921 int need_redraw = 0;
922 int low = 4096;
923 char *last_text = NULL;
925 e1 = e = ((WListbox *)(h->current->widget))->list;
926 do {
927 if (!strncmp (input->buffer + start, e1->text, end - start)){
928 if (e1->text [end - start] == Par){
929 if (need_redraw){
930 register int c1, c2, si;
932 for (si = end - start + 1;
933 (c1 = last_text [si]) &&
934 (c2 = e1->text [si]); si++)
935 if (c1 != c2)
936 break;
937 if (low > si)
938 low = si;
939 last_text = e1->text;
940 need_redraw = 2;
941 } else {
942 need_redraw = 1;
943 listbox_select_entry((WListbox *)(h->current->widget), e1);
944 last_text = e1->text;
948 e1 = e1->next;
949 } while (e != e1);
950 if (need_redraw == 2){
951 insert_text (input, last_text, low);
952 send_message (h, h->current->widget,WIDGET_DRAW,0);
953 } else if (need_redraw == 1){
954 h->ret_value = B_ENTER;
955 dlg_stop (h);
958 return 1;
960 break;
962 return 0;
965 static int querylist_callback (void *data)
967 return 1;
970 #define DO_INSERTION 1
971 #define DO_QUERY 2
972 /* Returns 1 if the user would like to see us again */
973 static int
974 complete_engine (WInput *in, int what_to_do)
976 if (in->completions && in->point != end)
977 free_completions (in);
978 if (!in->completions){
979 end = in->point;
980 for (start = end ? end - 1 : 0; start > -1; start--)
981 if (strchr (" \t;|<>", in->buffer [start]))
982 break;
983 if (start < end)
984 start++;
985 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
987 if (in->completions){
988 if (what_to_do & DO_INSERTION) {
989 if (insert_text (in, in->completions [0], strlen (in->completions [0]))){
990 if (in->completions [1])
991 beep ();
992 else
993 free_completions (in);
994 } else
995 beep ();
997 /* FIXME: evil evil evil. We do not go into the query completion engine
998 * because we do not have a Gtk dialog for it. Gtk-ted does not like
999 * this; if we enable this code, it will crash.
1001 #ifndef HAVE_GNOME
1002 if ((what_to_do & DO_QUERY) && in->completions [1]) {
1003 int maxlen = 0, i, count = 0;
1004 int x, y, w, h;
1005 int start_x, start_y;
1006 char **p, *q;
1007 Dlg_head *query_dlg;
1008 WListbox *query_list;
1010 for (p=in->completions + 1; *p; count++, p++)
1011 if ((i = strlen (*p)) > maxlen)
1012 maxlen = i;
1013 start_x = in->widget.x;
1014 start_y = in->widget.y;
1015 if (start_y - 2 >= count) {
1016 y = start_y - 2 - count;
1017 h = 2 + count;
1018 } else {
1019 if (start_y >= LINES - start_y - 1) {
1020 y = 0;
1021 h = start_y;
1022 } else {
1023 y = start_y + 1;
1024 h = LINES - start_y - 1;
1027 x = start - in->first_shown - 2 + start_x;
1028 w = maxlen + 4;
1029 if (x + w > COLS)
1030 x = COLS - w;
1031 if (x < 0)
1032 x = 0;
1033 if (x + w > COLS)
1034 w = COLS;
1035 input = in;
1036 min_end = end;
1037 query_height = h;
1038 query_width = w;
1039 query_dlg = create_dlg (y, x, query_height, query_width,
1040 dialog_colors, query_callback,
1041 "[Completion-query]", "complete", DLG_NONE);
1042 query_list = listbox_new (1, 1, w - 2, h - 2, 0, querylist_callback, NULL);
1043 add_widget (query_dlg, query_list);
1044 for (p = in->completions + 1; *p; p++)
1045 listbox_add_item (query_list, 0, 0, *p, NULL);
1046 run_dlg (query_dlg);
1047 q = NULL;
1048 if (query_dlg->ret_value == B_ENTER){
1049 listbox_get_current (query_list, &q, NULL);
1050 if (q)
1051 insert_text (in, q, strlen (q));
1053 if (q || end != min_end)
1054 free_completions (in);
1055 i = query_dlg->ret_value; /* B_USER if user wants to start over again */
1056 destroy_dlg (query_dlg);
1057 if (i == B_USER)
1058 return 1;
1060 #else
1061 if (what_to_do & DO_QUERY)
1062 beep ();
1063 #endif
1064 } else
1065 beep ();
1066 return 0;
1069 void complete (WInput *in)
1071 if (in->completions)
1072 while (complete_engine (in, DO_QUERY));
1073 else if (show_all_if_ambiguous){
1074 complete_engine (in, DO_INSERTION);
1075 while (complete_engine (in, DO_QUERY));
1076 } else
1077 complete_engine (in, DO_INSERTION);