Ticket #1790: mc crashes on start
[midnight-commander.git] / src / complete.c
blob15ac7ed1ad712756332d2954634cf4b0a5b28d38
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 /** \file complete.c
24 * \brief Source: Input line filename/username/hostname/variable/command completion
27 #include <config.h>
29 #include <ctype.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <dirent.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <pwd.h>
37 #include <unistd.h>
39 #include "global.h"
41 #include "../src/tty/tty.h"
42 #include "../src/tty/key.h" /* XCTRL and ALT macros */
44 #include "dialog.h"
45 #include "widget.h"
46 #include "wtools.h"
47 #include "main.h" /* show_all_if_ambiguous */
48 #include "util.h"
49 #include "../src/strescape.h"
50 #include "strutil.h"
52 typedef char *CompletionFunction (const char * text, int state, INPUT_COMPLETE_FLAGS flags);
54 /* #define DO_COMPLETION_DEBUG */
55 #ifdef DO_COMPLETION_DEBUG
57 * Useful to print/debug completion flags
59 static const char * show_c_flags(INPUT_COMPLETE_FLAGS flags)
61 static char s_cf[] = "FHCVUDS";
63 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) ? 'F' : ' ';
64 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) ? 'H' : ' ';
65 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) ? 'C' : ' ';
66 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) ? 'V' : ' ';
67 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) ? 'U' : ' ';
68 s_cf[5] = (flags & INPUT_COMPLETE_CD) ? 'D' : ' ';
69 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) ? 'S' : ' ';
71 return s_cf;
73 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
74 #else
75 #define SHOW_C_CTX(func)
76 #endif /* DO_CMPLETION_DEBUG */
78 static char *
79 filename_completion_function (const char * text, int state, INPUT_COMPLETE_FLAGS flags)
81 static DIR *directory;
82 static char *filename = NULL;
83 static char *dirname = NULL;
84 static char *users_dirname = NULL;
85 static size_t filename_len;
86 int isdir = 1, isexec = 0;
88 struct dirent *entry = NULL;
90 SHOW_C_CTX("filename_completion_function");
92 if (text && (flags & INPUT_COMPLETE_SHELL_ESC))
94 char * u_text;
95 char * result;
96 char * e_result;
98 u_text = strutils_shell_unescape (text);
100 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
101 g_free (u_text);
103 e_result = strutils_shell_escape (result);
104 g_free (result);
106 return e_result;
109 /* If we're starting the match process, initialize us a bit. */
110 if (!state){
111 const char *temp;
113 g_free (dirname);
114 g_free (filename);
115 g_free (users_dirname);
117 if ((*text) && (temp = strrchr (text, PATH_SEP))){
118 filename = g_strdup (++temp);
119 dirname = g_strndup (text, temp - text);
120 } else {
121 dirname = g_strdup (".");
122 filename = g_strdup (text);
125 /* We aren't done yet. We also support the "~user" syntax. */
127 /* Save the version of the directory that the user typed. */
128 users_dirname = dirname;
129 dirname = tilde_expand (dirname);
130 canonicalize_pathname (dirname);
132 /* Here we should do something with variable expansion
133 and `command`.
134 Maybe a dream - UNIMPLEMENTED yet. */
136 directory = mc_opendir (dirname);
137 filename_len = strlen (filename);
140 /* Now that we have some state, we can read the directory. */
142 while (directory && (entry = mc_readdir (directory))){
143 if (!str_is_valid_string (entry->d_name))
144 continue;
146 /* Special case for no filename.
147 All entries except "." and ".." match. */
148 if (filename_len == 0) {
149 if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
150 continue;
151 } else {
152 /* Otherwise, if these match up to the length of filename, then
153 it may be a match. */
154 if ((entry->d_name[0] != filename[0]) ||
155 ((NLENGTH (entry)) < filename_len) ||
156 strncmp (filename, entry->d_name, filename_len))
157 continue;
159 isdir = 1; isexec = 0;
161 char *tmp;
162 struct stat tempstat;
164 tmp = g_strconcat (dirname, PATH_SEP_STR, entry->d_name, (char *) NULL);
165 canonicalize_pathname (tmp);
166 /* Unix version */
167 if (!mc_stat (tmp, &tempstat)){
168 uid_t my_uid = getuid ();
169 gid_t my_gid = getgid ();
171 if (!S_ISDIR (tempstat.st_mode)){
172 isdir = 0;
173 if ((!my_uid && (tempstat.st_mode & 0111)) ||
174 (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100)) ||
175 (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010)) ||
176 (tempstat.st_mode & 0001))
177 isexec = 1;
180 else
182 /* stat failed, strange. not a dir in any case */
183 isdir = 0;
185 g_free (tmp);
187 if ((flags & INPUT_COMPLETE_COMMANDS)
188 && (isexec || isdir))
189 break;
190 if ((flags & INPUT_COMPLETE_CD)
191 && isdir)
192 break;
193 if (flags & (INPUT_COMPLETE_FILENAMES))
194 break;
197 if (!entry){
198 if (directory){
199 mc_closedir (directory);
200 directory = NULL;
202 g_free (dirname);
203 dirname = NULL;
204 g_free (filename);
205 filename = NULL;
206 g_free (users_dirname);
207 users_dirname = NULL;
208 return NULL;
209 } else {
210 char *temp;
212 if (users_dirname && (users_dirname[0] != '.' || users_dirname[1])){
213 size_t dirlen = strlen (users_dirname);
214 temp = g_malloc (3 + dirlen + NLENGTH (entry));
215 strcpy (temp, users_dirname);
216 /* We need a `/' at the end. */
217 if (users_dirname[dirlen - 1] != PATH_SEP){
218 temp[dirlen] = PATH_SEP;
219 temp[dirlen + 1] = 0;
221 strcat (temp, entry->d_name);
222 } else {
223 temp = g_malloc (2 + NLENGTH (entry));
224 strcpy (temp, entry->d_name);
226 if (isdir)
227 strcat (temp, PATH_SEP_STR);
229 return temp;
233 /* We assume here that text[0] == '~' , if you want to call it in another way,
234 you have to change the code */
235 static char *
236 username_completion_function (const char *text, int state, INPUT_COMPLETE_FLAGS flags)
238 static struct passwd *entry;
239 static size_t userlen;
241 (void) flags;
242 SHOW_C_CTX("username_completion_function");
244 if (text[0] == '\\' && text[1] == '~')
245 text++;
246 if (!state){ /* Initialization stuff */
247 setpwent ();
248 userlen = strlen (text + 1);
250 while ((entry = getpwent ()) != NULL){
251 /* Null usernames should result in all users as possible completions. */
252 if (userlen == 0)
253 break;
254 if (text[1] == entry->pw_name[0]
255 && !strncmp (text + 1, entry->pw_name, userlen))
256 break;
259 if (entry)
260 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
262 endpwent ();
263 return NULL;
266 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
267 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
268 extern char **environ;
269 #endif
271 /* We assume text [0] == '$' and want to have a look at text [1], if it is
272 equal to '{', so that we should append '}' at the end */
273 static char *
274 variable_completion_function (const char *text, int state, INPUT_COMPLETE_FLAGS flags)
276 static char **env_p;
277 static int varlen, isbrace;
278 const char *p = NULL;
280 (void) flags;
281 SHOW_C_CTX("variable_completion_function");
283 if (!state){ /* Initialization stuff */
284 isbrace = (text [1] == '{');
285 varlen = strlen (text + 1 + isbrace);
286 env_p = environ;
289 while (*env_p){
290 p = strchr (*env_p, '=');
291 if (p && p - *env_p >= varlen && !strncmp (text + 1 + isbrace, *env_p, varlen))
292 break;
293 env_p++;
296 if (!*env_p)
297 return NULL;
298 else {
299 char *temp = g_malloc (2 + 2 * isbrace + p - *env_p);
301 *temp = '$';
302 if (isbrace)
303 temp [1] = '{';
304 memcpy (temp + 1 + isbrace, *env_p, p - *env_p);
305 if (isbrace)
306 strcpy (temp + 2 + (p - *env_p), "}");
307 else
308 temp [1 + p - *env_p] = 0;
309 env_p++;
310 return temp;
314 #define whitespace(c) ((c) == ' ' || (c) == '\t')
315 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
317 static char **hosts = NULL;
318 static char **hosts_p = NULL;
319 static int hosts_alloclen = 0;
320 static void fetch_hosts (const char *filename)
322 FILE *file = fopen (filename, "r");
323 char buffer[256], *name;
324 char *start;
325 char *bi;
327 if (!file)
328 return;
330 while (fgets (buffer, 255, file) != NULL){
331 /* Skip to first character. */
332 for (bi = buffer;
333 bi[0] != '\0' && str_isspace (bi);
334 str_next_char (&bi));
336 /* Ignore comments... */
337 if (bi[0] == '#')
338 continue;
339 /* Handle $include. */
340 if (!strncmp (bi, "$include ", 9)){
341 char *includefile = bi + 9;
342 char *t;
344 /* Find start of filename. */
345 while (*includefile && whitespace (*includefile))
346 includefile++;
347 t = includefile;
349 /* Find end of filename. */
350 while (t[0] != '\0' && !str_isspace (t))
351 str_next_char (&t);
352 *t = '\0';
354 fetch_hosts (includefile);
355 continue;
358 /* Skip IP #s. */
359 while (bi[0] != '\0' && !str_isspace (bi))
360 str_next_char (&bi);
362 /* Get the host names separated by white space. */
363 while (bi[0] != '\0' && bi[0] != '#'){
364 while (bi[0] != '\0' && str_isspace (bi))
365 str_next_char (&bi);
366 if (bi[0] == '#')
367 continue;
368 for (start = bi;
369 bi[0] != '\0' && !str_isspace (bi);
370 str_next_char (&bi));
372 if (bi - start == 0) continue;
374 name = g_strndup (start, bi - start);
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 (const char *text, int state, INPUT_COMPLETE_FLAGS flags)
401 static char **host_p;
402 static int textstart, textlen;
404 (void) flags;
405 SHOW_C_CTX("hostname_completion_function");
407 if (!state){ /* Initialization stuff */
408 const char *p;
410 if (hosts != NULL){
411 for (host_p = hosts; *host_p; host_p++)
412 g_free (*host_p);
413 g_free (hosts);
415 hosts = g_new (char *, (hosts_alloclen = 30) + 1);
416 *hosts = NULL;
417 hosts_p = hosts;
418 fetch_hosts ((p = getenv ("HOSTFILE")) ? p : "/etc/hosts");
419 host_p = hosts;
420 textstart = (*text == '@') ? 1 : 0;
421 textlen = strlen (text + textstart);
424 while (*host_p){
425 if (!textlen)
426 break; /* Match all of them */
427 else if (!strncmp (text + textstart, *host_p, textlen))
428 break;
429 host_p++;
432 if (!*host_p){
433 for (host_p = hosts; *host_p; host_p++)
434 g_free (*host_p);
435 g_free (hosts);
436 hosts = NULL;
437 return NULL;
438 } else {
439 char *temp = g_malloc (2 + strlen (*host_p));
441 if (textstart)
442 *temp = '@';
443 strcpy (temp + textstart, *host_p);
444 host_p++;
445 return temp;
450 * This is the function to call when the word to complete is in a position
451 * where a command word can be found. It looks around $PATH, looking for
452 * commands that match. It also scans aliases, function names, and the
453 * table of shell built-ins.
455 static char *
456 command_completion_function (const char *_text, int state, INPUT_COMPLETE_FLAGS flags)
458 char *text;
459 static const char *path_end;
460 static gboolean isabsolute;
461 static int phase;
462 static int text_len;
463 static const char *const *words;
464 static char *path;
465 static char *cur_path;
466 static char *cur_word;
467 static int init_state;
468 static const char *const bash_reserved[] = {
469 "if", "then", "else", "elif", "fi", "case", "esac", "for",
470 "select", "while", "until", "do", "done", "in", "function", 0
472 static const char *const bash_builtins[] = {
473 "alias", "bg", "bind", "break", "builtin", "cd", "command",
474 "continue", "declare", "dirs", "echo", "enable", "eval",
475 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
476 "help", "history", "jobs", "kill", "let", "local", "logout",
477 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
478 "shift", "source", "suspend", "test", "times", "trap", "type",
479 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
481 char *p, *found;
483 SHOW_C_CTX("command_completion_function");
485 if (!(flags & INPUT_COMPLETE_COMMANDS))
486 return 0;
487 text = strutils_shell_unescape(_text);
488 flags &= ~INPUT_COMPLETE_SHELL_ESC;
490 if (!state) { /* Initialize us a little bit */
491 isabsolute = strchr (text, PATH_SEP) != NULL;
492 if (!isabsolute) {
493 words = bash_reserved;
494 phase = 0;
495 text_len = strlen (text);
496 if (!path && (path = g_strdup (getenv ("PATH"))) != NULL) {
497 p = path;
498 path_end = strchr (p, 0);
499 while ((p = strchr (p, PATH_ENV_SEP))) {
500 *p++ = 0;
506 if (isabsolute) {
507 p = filename_completion_function (text, state, flags);
509 if (p) {
510 char *temp_p = p;
511 p = strutils_shell_escape (p);
512 g_free (temp_p);
515 g_free (text);
516 return p;
519 found = NULL;
520 switch (phase) {
521 case 0: /* Reserved words */
522 while (*words) {
523 if (!strncmp (*words, text, text_len))
524 return g_strdup (*(words++));
525 words++;
527 phase++;
528 words = bash_builtins;
529 case 1: /* Builtin commands */
530 while (*words) {
531 if (!strncmp (*words, text, text_len))
532 return g_strdup (*(words++));
533 words++;
535 phase++;
536 if (!path)
537 break;
538 cur_path = path;
539 cur_word = NULL;
540 case 2: /* And looking through the $PATH */
541 while (!found) {
542 if (!cur_word) {
543 char *expanded;
545 if (cur_path >= path_end)
546 break;
547 expanded = tilde_expand (*cur_path ? cur_path : ".");
548 cur_word = concat_dir_and_file (expanded, text);
549 g_free (expanded);
550 canonicalize_pathname (cur_word);
551 cur_path = strchr (cur_path, 0) + 1;
552 init_state = state;
554 found =
555 filename_completion_function (cur_word,
556 state - init_state, flags);
557 if (!found) {
558 g_free (cur_word);
559 cur_word = NULL;
564 if (found == NULL) {
565 g_free (path);
566 path = NULL;
567 } else if ((p = strrchr (found, PATH_SEP)) != NULL) {
568 char *tmp = found;
569 found = strutils_shell_escape (p + 1);
570 g_free (tmp);
573 g_free(text);
574 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 (const char *text, CompletionFunction entry_function, INPUT_COMPLETE_FLAGS flags)
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, flags)) != NULL){
608 if (matches + 1 == match_list_size)
609 match_list = (char **) g_realloc (match_list, ((match_list_size += 30) + 1) * sizeof (char *));
610 match_list[++matches] = string;
611 match_list[matches + 1] = NULL;
614 /* If there were any matches, then look through them finding out the
615 lowest common denominator. That then becomes match_list[0]. */
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] = 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 char *si, *sj;
638 char *ni, *nj;
640 for (si = match_list[i], sj = match_list[j];
641 si[0] && sj[0];) {
643 ni = str_get_next_char (si);
644 nj = str_get_next_char (sj);
646 if (ni - si != nj - sj) break;
647 if (strncmp (si, sj, ni - si) != 0) break;
649 si = ni;
650 sj = nj;
653 if (si[0] == '\0' && sj[0] == '\0'){ /* Two equal strings */
654 g_free (match_list [j]);
655 j++;
656 if (j > matches)
657 break;
658 continue; /* Look for a run of equal strings */
659 } else
660 if (low > si - match_list[i]) low = si - match_list[i];
661 if (i + 1 != j) /* So there's some gap */
662 match_list [i + 1] = match_list [j];
663 i++; j++;
665 matches = i;
666 match_list [matches + 1] = NULL;
667 match_list[0] = g_strndup(match_list[1], low);
669 } else { /* There were no matches. */
670 g_free (match_list);
671 match_list = NULL;
673 return match_list;
676 /* Check if directory completion is needed */
677 static int
678 check_is_cd (const char *text, int start, INPUT_COMPLETE_FLAGS flags)
680 char *p, *q;
681 int test = 0;
683 SHOW_C_CTX("check_is_cd");
684 if (!(flags & INPUT_COMPLETE_CD))
685 return 0;
687 /* Skip initial spaces */
688 p = (char*)text;
689 q = (char*)text + start;
690 while (p < q && p[0] != '\0' && str_isspace (p))
691 str_next_char (&p);
693 /* Check if the command is "cd" and the cursor is after it */
694 text+= p[0] == 'c';
695 str_next_char (&p);
696 text+= p[0] == 'd';
697 str_next_char (&p);
698 text+= str_isspace (p);
699 if (test == 3 && (p < q))
700 return 1;
702 return 0;
705 /* Returns an array of matches, or NULL if none. */
706 static char **
707 try_complete (char *text, int *start, int *end, INPUT_COMPLETE_FLAGS flags)
709 int in_command_position = 0;
710 char *word;
711 char **matches = NULL;
712 const char *command_separator_chars = ";|&{(`";
713 char *p = NULL, *q = NULL, *r = NULL;
714 int is_cd = check_is_cd (text, *start, flags);
715 char *ti;
717 SHOW_C_CTX("try_complete");
718 word = g_strndup (text + *start, *end - *start);
720 /* Determine if this could be a command word. It is if it appears at
721 the start of the line (ignoring preceding whitespace), or if it
722 appears after a character that separates commands. And we have to
723 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
724 if (!is_cd && (flags & INPUT_COMPLETE_COMMANDS)){
725 ti = str_get_prev_char (&text[*start]);
726 while (ti > text && (ti[0] == ' ' || ti[0] == '\t'))
727 str_prev_char (&ti);
728 if (ti <= text&& (ti[0] == ' ' || ti[0] == '\t'))
729 in_command_position++;
730 else if (strchr (command_separator_chars, ti[0])){
731 register int this_char, prev_char;
733 in_command_position++;
735 if (ti > text){
736 /* Handle the two character tokens `>&', `<&', and `>|'.
737 We are not in a command position after one of these. */
738 this_char = ti[0];
739 prev_char = str_get_prev_char (ti)[0];
741 if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
742 (this_char == '|' && prev_char == '>'))
743 in_command_position = 0;
745 else if (ti > text && str_get_prev_char (ti)[0] == '\\') /* Quoted */
746 in_command_position = 0;
751 if (flags & INPUT_COMPLETE_COMMANDS)
752 p = strrchr (word, '`');
753 if (flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
754 q = strrchr (word, '$');
755 if (flags & INPUT_COMPLETE_HOSTNAMES)
756 r = strrchr (word, '@');
757 if (q && q [1] == '(' && INPUT_COMPLETE_COMMANDS){
758 if (q > p)
759 p = str_get_next_char (q);
760 q = NULL;
763 /* Command substitution? */
764 if (p > q && p > r){
765 SHOW_C_CTX("try_complete:cmd_backq_subst");
766 matches = completion_matches (str_cget_next_char (p),
767 command_completion_function,
768 flags & (~INPUT_COMPLETE_FILENAMES));
769 if (matches)
770 *start += str_get_next_char (p) - word;
773 /* Variable name? */
774 else if (q > p && q > r){
775 SHOW_C_CTX("try_complete:var_subst");
776 matches = completion_matches (q, variable_completion_function, flags);
777 if (matches)
778 *start += q - word;
781 /* Starts with '@', then look through the known hostnames for
782 completion first. */
783 else if (r > p && r > q){
784 SHOW_C_CTX("try_complete:host_subst");
785 matches = completion_matches (r, hostname_completion_function, flags);
786 if (matches)
787 *start += r - word;
790 /* Starts with `~' and there is no slash in the word, then
791 try completing this word as a username. */
792 if (!matches && *word == '~' && (flags & INPUT_COMPLETE_USERNAMES) && !strchr (word, PATH_SEP))
794 SHOW_C_CTX("try_complete:user_subst");
795 matches = completion_matches (word, username_completion_function, flags);
799 /* And finally if this word is in a command position, then
800 complete over possible command names, including aliases, functions,
801 and command names. */
802 if (!matches && in_command_position)
804 SHOW_C_CTX("try_complete:cmd_subst");
805 matches = completion_matches (word, command_completion_function, flags & (~INPUT_COMPLETE_FILENAMES));
808 else if (!matches && (flags & INPUT_COMPLETE_FILENAMES)){
809 if (is_cd)
810 flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
811 SHOW_C_CTX("try_complete:filename_subst_1");
812 matches = completion_matches (word, filename_completion_function, flags);
813 if (!matches && is_cd && *word != PATH_SEP && *word != '~'){
814 q = text + *start;
815 for (p = text; *p && p < q && (*p == ' ' || *p == '\t'); str_next_char (&p));
816 if (!strncmp (p, "cd", 2))
817 for (p += 2; *p && p < q && (*p == ' ' || *p == '\t'); str_next_char (&p));
818 if (p == q){
819 char * const cdpath_ref = g_strdup (getenv ("CDPATH"));
820 char *cdpath = cdpath_ref;
821 char c, *s;
823 if (cdpath == NULL)
824 c = 0;
825 else
826 c = ':';
827 while (!matches && c == ':'){
828 s = strchr (cdpath, ':');
829 if (s == NULL)
830 s = strchr (cdpath, 0);
831 c = *s;
832 *s = 0;
833 if (*cdpath){
834 r = concat_dir_and_file (cdpath, word);
835 SHOW_C_CTX("try_complete:filename_subst_2");
836 matches = completion_matches (r, filename_completion_function, flags);
837 g_free (r);
839 *s = c;
840 cdpath = str_get_next_char (s);
842 g_free (cdpath_ref);
847 g_free (word);
849 return matches;
852 void free_completions (WInput *in)
854 char **p;
856 if (!in->completions)
857 return;
858 for (p=in->completions; *p; p++)
859 g_free (*p);
860 g_free (in->completions);
861 in->completions = NULL;
864 static int query_height, query_width;
865 static WInput *input;
866 static int min_end;
867 static int start, end;
869 static int insert_text (WInput *in, char *text, ssize_t size)
871 int buff_len = str_length (in->buffer);
873 size = min (size, (ssize_t) strlen (text)) + start - end;
874 if (strlen (in->buffer) + size >= (size_t) in->current_max_size){
875 /* Expand the buffer */
876 char *narea = g_realloc (in->buffer, in->current_max_size
877 + size + in->field_width);
878 if (narea){
879 in->buffer = narea;
880 in->current_max_size += size + in->field_width;
883 if (strlen (in->buffer)+1 < (size_t) in->current_max_size){
884 if (size > 0){
885 int i = strlen (&in->buffer [end]);
886 for (; i >= 0; i--)
887 in->buffer [end + size + i] = in->buffer [end + i];
888 } else if (size < 0){
889 char *p = in->buffer + end + size, *q = in->buffer + end;
890 while (*q)
891 *(p++) = *(q++);
892 *p = 0;
894 memcpy (in->buffer + start, text, size - start + end);
895 in->point+= str_length (in->buffer) - buff_len;
896 update_input (in, 1);
897 end+= size;
899 return size != 0;
902 static cb_ret_t
903 query_callback (Dlg_head *h, dlg_msg_t msg, int parm)
905 static char buff[MB_LEN_MAX] = "";
906 static int bl = 0;
908 switch (msg) {
909 case DLG_KEY:
910 switch (parm) {
911 case KEY_LEFT:
912 case KEY_RIGHT:
913 bl = 0;
914 h->ret_value = 0;
915 dlg_stop (h);
916 return MSG_HANDLED;
918 case KEY_BACKSPACE:
919 bl = 0;
920 if (end == min_end) {
921 h->ret_value = 0;
922 dlg_stop (h);
923 return MSG_HANDLED;
924 } else {
925 WLEntry *e, *e1;
927 e1 = e = ((WListbox *) (h->current))->list;
928 do {
929 if (!strncmp (input->buffer + start,
930 e1->text, end - start - 1)) {
932 listbox_select_entry ((WListbox *) (h->current), e1);
933 end = str_get_prev_char (&(input->buffer[end]))
934 - input->buffer;
935 handle_char (input, parm);
936 send_message (h->current, WIDGET_DRAW, 0);
937 break;
939 e1 = e1->next;
940 } while (e != e1);
942 return MSG_HANDLED;
944 default:
945 if (parm < 32 || parm > 256) {
946 bl = 0;
947 if (is_in_input_map (input, parm) == 2) {
948 if (end == min_end)
949 return MSG_HANDLED;
950 h->ret_value = B_USER; /* This means we want to refill the
951 list box and start again */
952 dlg_stop (h);
953 return MSG_HANDLED;
954 } else
955 return MSG_NOT_HANDLED;
956 } else {
957 WLEntry *e, *e1;
958 int need_redraw = 0;
959 int low = 4096;
960 char *last_text = NULL;
962 buff[bl] = (char) parm;
963 bl++;
964 buff[bl] = '\0';
965 switch (str_is_valid_char (buff, bl)) {
966 case -1:
967 bl = 0;
968 case -2:
969 return MSG_HANDLED;
972 e1 = e = ((WListbox *) (h->current))->list;
973 do {
974 if (!strncmp (input->buffer + start,
975 e1->text, end - start)) {
977 if (strncmp (&e1->text[end - start], buff, bl) == 0) {
978 if (need_redraw) {
979 char *si, *sl;
980 char *ni, *nl;
981 si = &(e1->text[end - start]);
982 sl = &(last_text[end - start]);
984 for (; si[0] != '\0' && sl[0] != '\0';) {
986 ni = str_get_next_char (si);
987 nl = str_get_next_char (sl);
989 if (ni - si != nl - sl) break;
990 if (strncmp (si, sl, ni - si) != 0) break;
992 si = ni;
993 sl = nl;
996 if (low > si - &e1->text[end - start])
997 low = si - &e1->text[end - start];
999 last_text = e1->text;
1000 need_redraw = 2;
1001 } else {
1002 need_redraw = 1;
1003 listbox_select_entry ((WListbox *) (h->
1004 current),
1005 e1);
1006 last_text = e1->text;
1010 e1 = e1->next;
1011 } while (e != e1);
1012 if (need_redraw == 2) {
1013 insert_text (input, last_text, low);
1014 send_message (h->current, WIDGET_DRAW, 0);
1015 } else if (need_redraw == 1) {
1016 h->ret_value = B_ENTER;
1017 dlg_stop (h);
1019 bl = 0;
1021 return MSG_HANDLED;
1023 break;
1025 default:
1026 return default_dlg_callback (h, msg, parm);
1030 #define DO_INSERTION 1
1031 #define DO_QUERY 2
1032 /* Returns 1 if the user would like to see us again */
1033 static int
1034 complete_engine (WInput *in, int what_to_do)
1036 int s;
1038 if (in->completions && (str_offset_to_pos (in->buffer, in->point)) != end)
1039 free_completions (in);
1040 if (!in->completions){
1041 end = str_offset_to_pos (in->buffer, in->point);
1042 for (s = in->point ? in->point - 1 : 0; s >= 0; s--) {
1043 start = str_offset_to_pos (in->buffer, s);
1044 if (strchr (" \t;|<>", in->buffer [start])) {
1045 if (start < end) start = str_offset_to_pos (in->buffer, s + 1);
1046 /* FIXME: maybe need check '\\' prev char
1047 if (start > 0 && in->buffer [start-1] == '\\')
1049 break;
1052 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
1055 if (in->completions){
1056 if (what_to_do & DO_INSERTION || ((what_to_do & DO_QUERY) && !in->completions[1])) {
1057 char * lc_complete = in->completions [0];
1058 if (insert_text (in, lc_complete, strlen (lc_complete))){
1059 if (in->completions [1])
1060 tty_beep ();
1061 else
1062 free_completions (in);
1063 } else
1064 tty_beep ();
1066 if ((what_to_do & DO_QUERY) && in->completions && in->completions [1]) {
1067 int maxlen = 0, i, count = 0;
1068 int x, y, w, h;
1069 int start_x, start_y;
1070 char **p, *q;
1071 Dlg_head *query_dlg;
1072 WListbox *query_list;
1074 for (p=in->completions + 1; *p; count++, p++)
1075 if ((i = str_term_width1 (*p)) > maxlen)
1076 maxlen = i;
1077 start_x = in->widget.x;
1078 start_y = in->widget.y;
1079 if (start_y - 2 >= count) {
1080 y = start_y - 2 - count;
1081 h = 2 + count;
1082 } else {
1083 if (start_y >= LINES - start_y - 1) {
1084 y = 0;
1085 h = start_y;
1086 } else {
1087 y = start_y + 1;
1088 h = LINES - start_y - 1;
1091 x = start - in->term_first_shown - 2 + start_x;
1092 w = maxlen + 4;
1093 if (x + w > COLS)
1094 x = COLS - w;
1095 if (x < 0)
1096 x = 0;
1097 if (x + w > COLS)
1098 w = COLS;
1099 input = in;
1100 min_end = end;
1101 query_height = h;
1102 query_width = w;
1103 query_dlg = create_dlg (y, x, query_height, query_width,
1104 dialog_colors, query_callback,
1105 "[Completion]", NULL, DLG_COMPACT);
1106 query_list = listbox_new (1, 1, h - 2, w - 2, NULL);
1107 add_widget (query_dlg, query_list);
1108 for (p = in->completions + 1; *p; p++)
1109 listbox_add_item (query_list, 0, 0, *p, NULL);
1110 run_dlg (query_dlg);
1111 q = NULL;
1112 if (query_dlg->ret_value == B_ENTER){
1113 listbox_get_current (query_list, &q, NULL);
1114 if (q)
1115 insert_text (in, q, strlen (q));
1117 if (q || end != min_end)
1118 free_completions (in);
1119 i = query_dlg->ret_value; /* B_USER if user wants to start over again */
1120 destroy_dlg (query_dlg);
1121 if (i == B_USER)
1122 return 1;
1124 } else
1125 tty_beep ();
1126 return 0;
1129 void complete (WInput *in)
1131 int engine_flags;
1133 if (!str_is_valid_string (in->buffer)) return;
1135 if (in->completions)
1136 engine_flags = DO_QUERY;
1137 else
1139 engine_flags = DO_INSERTION;
1141 if (show_all_if_ambiguous)
1142 engine_flags |= DO_QUERY;
1145 while (complete_engine (in, engine_flags));