Merge branch '2087_standalone_mcedit_crash'
[kaloumi3.git] / src / complete.c
blobabe066858cb0037371410c906bc17722d7ba8563
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 "lib/global.h"
41 #include "lib/tty/tty.h"
42 #include "lib/tty/key.h" /* XCTRL and ALT macros */
43 #include "lib/vfs/mc-vfs/vfs.h"
44 #include "lib/strescape.h"
45 #include "lib/strutil.h"
47 #include "dialog.h"
48 #include "widget.h"
49 #include "wtools.h"
50 #include "main.h" /* show_all_if_ambiguous */
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
870 insert_text (WInput *in, char *text, ssize_t size)
872 int buff_len = str_length (in->buffer);
874 size = min (size, (ssize_t) strlen (text)) + start - end;
875 if (strlen (in->buffer) + size >= (size_t) in->current_max_size){
876 /* Expand the buffer */
877 char *narea = g_try_realloc (in->buffer, in->current_max_size
878 + size + in->field_width);
879 if (narea != NULL) {
880 in->buffer = narea;
881 in->current_max_size += size + in->field_width;
884 if (strlen (in->buffer)+1 < (size_t) in->current_max_size){
885 if (size > 0){
886 int i = strlen (&in->buffer [end]);
887 for (; i >= 0; i--)
888 in->buffer [end + size + i] = in->buffer [end + i];
889 } else if (size < 0){
890 char *p = in->buffer + end + size, *q = in->buffer + end;
891 while (*q)
892 *(p++) = *(q++);
893 *p = 0;
895 memcpy (in->buffer + start, text, size - start + end);
896 in->point+= str_length (in->buffer) - buff_len;
897 update_input (in, 1);
898 end+= size;
900 return size != 0;
903 static cb_ret_t
904 query_callback (Dlg_head *h, Widget *sender,
905 dlg_msg_t msg, int parm, void *data)
907 static char buff[MB_LEN_MAX] = "";
908 static int bl = 0;
910 switch (msg) {
911 case DLG_KEY:
912 switch (parm) {
913 case KEY_LEFT:
914 case KEY_RIGHT:
915 bl = 0;
916 h->ret_value = 0;
917 dlg_stop (h);
918 return MSG_HANDLED;
920 case KEY_BACKSPACE:
921 bl = 0;
922 if (end == min_end) {
923 h->ret_value = 0;
924 dlg_stop (h);
925 } else {
926 int i;
927 GList *e;
929 for (i = 0, e = ((WListbox *) h->current)->list;
930 e != NULL;
931 i++, e = g_list_next (e)) {
932 WLEntry *le = (WLEntry *) e->data;
934 if (strncmp (input->buffer + start, le->text, end - start - 1) == 0) {
935 listbox_select_entry ((WListbox *) h->current, i);
936 end = str_get_prev_char (&(input->buffer[end])) - input->buffer;
937 handle_char (input, parm);
938 send_message (h->current, WIDGET_DRAW, 0);
939 break;
943 return MSG_HANDLED;
945 default:
946 if (parm < 32 || parm > 256) {
947 bl = 0;
948 if (is_in_input_map (input, parm) == 2) {
949 if (end == min_end)
950 return MSG_HANDLED;
951 h->ret_value = B_USER; /* This means we want to refill the
952 list box and start again */
953 dlg_stop (h);
954 return MSG_HANDLED;
955 } else
956 return MSG_NOT_HANDLED;
957 } else {
958 GList *e;
959 int i;
960 int need_redraw = 0;
961 int low = 4096;
962 char *last_text = NULL;
964 buff[bl] = (char) parm;
965 bl++;
966 buff[bl] = '\0';
967 switch (str_is_valid_char (buff, bl)) {
968 case -1:
969 bl = 0;
970 case -2:
971 return MSG_HANDLED;
974 for (i = 0, e = ((WListbox *) h->current)->list;
975 e != NULL;
976 i++, e = g_list_next (e)) {
977 WLEntry *le = (WLEntry *) e->data;
979 if (strncmp (input->buffer + start, le->text, end - start) == 0) {
980 if (strncmp (&le->text[end - start], buff, bl) == 0) {
981 if (need_redraw) {
982 char *si, *sl;
983 char *nexti, *nextl;
985 si = &(le->text[end - start]);
986 sl = &(last_text[end - start]);
988 for (; si[0] != '\0' && sl[0] != '\0';) {
989 nexti = str_get_next_char (si);
990 nextl = str_get_next_char (sl);
992 if (nexti - si != nextl - sl)
993 break;
994 if (strncmp (si, sl, nexti - si) != 0)
995 break;
997 si = nexti;
998 sl = nextl;
1001 if (low > si - &le->text[end - start])
1002 low = si - &le->text[end - start];
1004 last_text = le->text;
1005 need_redraw = 2;
1006 } else {
1007 need_redraw = 1;
1008 listbox_select_entry ((WListbox *) h->current, i);
1009 last_text = le->text;
1015 if (need_redraw == 2) {
1016 insert_text (input, last_text, low);
1017 send_message (h->current, WIDGET_DRAW, 0);
1018 } else if (need_redraw == 1) {
1019 h->ret_value = B_ENTER;
1020 dlg_stop (h);
1022 bl = 0;
1024 return MSG_HANDLED;
1026 break;
1028 default:
1029 return default_dlg_callback (h, sender, msg, parm, data);
1033 #define DO_INSERTION 1
1034 #define DO_QUERY 2
1035 /* Returns 1 if the user would like to see us again */
1036 static int
1037 complete_engine (WInput *in, int what_to_do)
1039 int s;
1041 if (in->completions && (str_offset_to_pos (in->buffer, in->point)) != end)
1042 free_completions (in);
1043 if (!in->completions){
1044 end = str_offset_to_pos (in->buffer, in->point);
1045 for (s = in->point ? in->point - 1 : 0; s >= 0; s--) {
1046 start = str_offset_to_pos (in->buffer, s);
1047 if (strchr (" \t;|<>", in->buffer [start])) {
1048 if (start < end) start = str_offset_to_pos (in->buffer, s + 1);
1049 /* FIXME: maybe need check '\\' prev char
1050 if (start > 0 && in->buffer [start-1] == '\\')
1052 break;
1055 in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
1058 if (in->completions){
1059 if (what_to_do & DO_INSERTION || ((what_to_do & DO_QUERY) && !in->completions[1])) {
1060 char * lc_complete = in->completions [0];
1061 if (insert_text (in, lc_complete, strlen (lc_complete))){
1062 if (in->completions [1])
1063 tty_beep ();
1064 else
1065 free_completions (in);
1066 } else
1067 tty_beep ();
1069 if ((what_to_do & DO_QUERY) && in->completions && in->completions [1]) {
1070 int maxlen = 0, i, count = 0;
1071 int x, y, w, h;
1072 int start_x, start_y;
1073 char **p, *q;
1074 Dlg_head *query_dlg;
1075 WListbox *query_list;
1077 for (p=in->completions + 1; *p; count++, p++)
1078 if ((i = str_term_width1 (*p)) > maxlen)
1079 maxlen = i;
1080 start_x = in->widget.x;
1081 start_y = in->widget.y;
1082 if (start_y - 2 >= count) {
1083 y = start_y - 2 - count;
1084 h = 2 + count;
1085 } else {
1086 if (start_y >= LINES - start_y - 1) {
1087 y = 0;
1088 h = start_y;
1089 } else {
1090 y = start_y + 1;
1091 h = LINES - start_y - 1;
1094 x = start - in->term_first_shown - 2 + start_x;
1095 w = maxlen + 4;
1096 if (x + w > COLS)
1097 x = COLS - w;
1098 if (x < 0)
1099 x = 0;
1100 if (x + w > COLS)
1101 w = COLS;
1102 input = in;
1103 min_end = end;
1104 query_height = h;
1105 query_width = w;
1106 query_dlg = create_dlg (y, x, query_height, query_width,
1107 dialog_colors, query_callback,
1108 "[Completion]", NULL, DLG_COMPACT);
1109 query_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1110 add_widget (query_dlg, query_list);
1111 for (p = in->completions + 1; *p; p++)
1112 listbox_add_item (query_list, LISTBOX_APPEND_AT_END, 0, *p, NULL);
1113 run_dlg (query_dlg);
1114 q = NULL;
1115 if (query_dlg->ret_value == B_ENTER){
1116 listbox_get_current (query_list, &q, NULL);
1117 if (q)
1118 insert_text (in, q, strlen (q));
1120 if (q || end != min_end)
1121 free_completions (in);
1122 i = query_dlg->ret_value; /* B_USER if user wants to start over again */
1123 destroy_dlg (query_dlg);
1124 if (i == B_USER)
1125 return 1;
1127 } else
1128 tty_beep ();
1129 return 0;
1132 void complete (WInput *in)
1134 int engine_flags;
1136 if (!str_is_valid_string (in->buffer)) return;
1138 if (in->completions)
1139 engine_flags = DO_QUERY;
1140 else
1142 engine_flags = DO_INSERTION;
1144 if (show_all_if_ambiguous)
1145 engine_flags |= DO_QUERY;
1148 while (complete_engine (in, engine_flags));