Ticket #1930: src/strutil8bit.c: fixed missing include of fs.h
[free-mc.git] / src / complete.c
blob143d5c3c7d06b350c2fa11a468eb04b0a6f2ace1
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 */
43 #include "../vfs/vfs.h"
45 #include "dialog.h"
46 #include "widget.h"
47 #include "wtools.h"
48 #include "main.h" /* show_all_if_ambiguous */
49 #include "util.h"
50 #include "../src/strescape.h"
51 #include "strutil.h"
53 typedef char *CompletionFunction (const char * text, int state, INPUT_COMPLETE_FLAGS flags);
55 /* #define DO_COMPLETION_DEBUG */
56 #ifdef DO_COMPLETION_DEBUG
58 * Useful to print/debug completion flags
60 static const char * show_c_flags(INPUT_COMPLETE_FLAGS flags)
62 static char s_cf[] = "FHCVUDS";
64 s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) ? 'F' : ' ';
65 s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) ? 'H' : ' ';
66 s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) ? 'C' : ' ';
67 s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) ? 'V' : ' ';
68 s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) ? 'U' : ' ';
69 s_cf[5] = (flags & INPUT_COMPLETE_CD) ? 'D' : ' ';
70 s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) ? 'S' : ' ';
72 return s_cf;
74 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
75 #else
76 #define SHOW_C_CTX(func)
77 #endif /* DO_CMPLETION_DEBUG */
79 static char *
80 filename_completion_function (const char * text, int state, INPUT_COMPLETE_FLAGS flags)
82 static DIR *directory;
83 static char *filename = NULL;
84 static char *dirname = NULL;
85 static char *users_dirname = NULL;
86 static size_t filename_len;
87 int isdir = 1, isexec = 0;
89 struct dirent *entry = NULL;
91 SHOW_C_CTX("filename_completion_function");
93 if (text && (flags & INPUT_COMPLETE_SHELL_ESC))
95 char * u_text;
96 char * result;
97 char * e_result;
99 u_text = strutils_shell_unescape (text);
101 result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
102 g_free (u_text);
104 e_result = strutils_shell_escape (result);
105 g_free (result);
107 return e_result;
110 /* If we're starting the match process, initialize us a bit. */
111 if (!state){
112 const char *temp;
114 g_free (dirname);
115 g_free (filename);
116 g_free (users_dirname);
118 if ((*text) && (temp = strrchr (text, PATH_SEP))){
119 filename = g_strdup (++temp);
120 dirname = g_strndup (text, temp - text);
121 } else {
122 dirname = g_strdup (".");
123 filename = g_strdup (text);
126 /* We aren't done yet. We also support the "~user" syntax. */
128 /* Save the version of the directory that the user typed. */
129 users_dirname = dirname;
130 dirname = tilde_expand (dirname);
131 canonicalize_pathname (dirname);
133 /* Here we should do something with variable expansion
134 and `command`.
135 Maybe a dream - UNIMPLEMENTED yet. */
137 directory = mc_opendir (dirname);
138 filename_len = strlen (filename);
141 /* Now that we have some state, we can read the directory. */
143 while (directory && (entry = mc_readdir (directory))){
144 if (!str_is_valid_string (entry->d_name))
145 continue;
147 /* Special case for no filename.
148 All entries except "." and ".." match. */
149 if (filename_len == 0) {
150 if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
151 continue;
152 } else {
153 /* Otherwise, if these match up to the length of filename, then
154 it may be a match. */
155 if ((entry->d_name[0] != filename[0]) ||
156 ((NLENGTH (entry)) < filename_len) ||
157 strncmp (filename, entry->d_name, filename_len))
158 continue;
160 isdir = 1; isexec = 0;
162 char *tmp;
163 struct stat tempstat;
165 tmp = g_strconcat (dirname, PATH_SEP_STR, entry->d_name, (char *) NULL);
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 else
183 /* stat failed, strange. not a dir in any case */
184 isdir = 0;
186 g_free (tmp);
188 if ((flags & INPUT_COMPLETE_COMMANDS)
189 && (isexec || isdir))
190 break;
191 if ((flags & INPUT_COMPLETE_CD)
192 && isdir)
193 break;
194 if (flags & (INPUT_COMPLETE_FILENAMES))
195 break;
198 if (!entry){
199 if (directory){
200 mc_closedir (directory);
201 directory = NULL;
203 g_free (dirname);
204 dirname = NULL;
205 g_free (filename);
206 filename = NULL;
207 g_free (users_dirname);
208 users_dirname = NULL;
209 return NULL;
210 } else {
211 char *temp;
213 if (users_dirname && (users_dirname[0] != '.' || users_dirname[1])){
214 size_t dirlen = strlen (users_dirname);
215 temp = g_malloc (3 + dirlen + NLENGTH (entry));
216 strcpy (temp, users_dirname);
217 /* We need a `/' at the end. */
218 if (users_dirname[dirlen - 1] != PATH_SEP){
219 temp[dirlen] = PATH_SEP;
220 temp[dirlen + 1] = 0;
222 strcat (temp, entry->d_name);
223 } else {
224 temp = g_malloc (2 + NLENGTH (entry));
225 strcpy (temp, entry->d_name);
227 if (isdir)
228 strcat (temp, PATH_SEP_STR);
230 return temp;
234 /* We assume here that text[0] == '~' , if you want to call it in another way,
235 you have to change the code */
236 static char *
237 username_completion_function (const char *text, int state, INPUT_COMPLETE_FLAGS flags)
239 static struct passwd *entry;
240 static size_t userlen;
242 (void) flags;
243 SHOW_C_CTX("username_completion_function");
245 if (text[0] == '\\' && text[1] == '~')
246 text++;
247 if (!state){ /* Initialization stuff */
248 setpwent ();
249 userlen = strlen (text + 1);
251 while ((entry = getpwent ()) != NULL){
252 /* Null usernames should result in all users as possible completions. */
253 if (userlen == 0)
254 break;
255 if (text[1] == entry->pw_name[0]
256 && !strncmp (text + 1, entry->pw_name, userlen))
257 break;
260 if (entry)
261 return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
263 endpwent ();
264 return NULL;
267 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
268 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
269 extern char **environ;
270 #endif
272 /* We assume text [0] == '$' and want to have a look at text [1], if it is
273 equal to '{', so that we should append '}' at the end */
274 static char *
275 variable_completion_function (const char *text, int state, INPUT_COMPLETE_FLAGS flags)
277 static char **env_p;
278 static int varlen, isbrace;
279 const char *p = NULL;
281 (void) flags;
282 SHOW_C_CTX("variable_completion_function");
284 if (!state){ /* Initialization stuff */
285 isbrace = (text [1] == '{');
286 varlen = strlen (text + 1 + isbrace);
287 env_p = environ;
290 while (*env_p){
291 p = strchr (*env_p, '=');
292 if (p && p - *env_p >= varlen && !strncmp (text + 1 + isbrace, *env_p, varlen))
293 break;
294 env_p++;
297 if (!*env_p)
298 return NULL;
299 else {
300 char *temp = g_malloc (2 + 2 * isbrace + p - *env_p);
302 *temp = '$';
303 if (isbrace)
304 temp [1] = '{';
305 memcpy (temp + 1 + isbrace, *env_p, p - *env_p);
306 if (isbrace)
307 strcpy (temp + 2 + (p - *env_p), "}");
308 else
309 temp [1 + p - *env_p] = 0;
310 env_p++;
311 return temp;
315 #define whitespace(c) ((c) == ' ' || (c) == '\t')
316 #define cr_whitespace(c) (whitespace (c) || (c) == '\n' || (c) == '\r')
318 static char **hosts = NULL;
319 static char **hosts_p = NULL;
320 static int hosts_alloclen = 0;
321 static void fetch_hosts (const char *filename)
323 FILE *file = fopen (filename, "r");
324 char buffer[256], *name;
325 char *start;
326 char *bi;
328 if (!file)
329 return;
331 while (fgets (buffer, 255, file) != NULL){
332 /* Skip to first character. */
333 for (bi = buffer;
334 bi[0] != '\0' && str_isspace (bi);
335 str_next_char (&bi));
337 /* Ignore comments... */
338 if (bi[0] == '#')
339 continue;
340 /* Handle $include. */
341 if (!strncmp (bi, "$include ", 9)){
342 char *includefile = bi + 9;
343 char *t;
345 /* Find start of filename. */
346 while (*includefile && whitespace (*includefile))
347 includefile++;
348 t = includefile;
350 /* Find end of filename. */
351 while (t[0] != '\0' && !str_isspace (t))
352 str_next_char (&t);
353 *t = '\0';
355 fetch_hosts (includefile);
356 continue;
359 /* Skip IP #s. */
360 while (bi[0] != '\0' && !str_isspace (bi))
361 str_next_char (&bi);
363 /* Get the host names separated by white space. */
364 while (bi[0] != '\0' && bi[0] != '#'){
365 while (bi[0] != '\0' && str_isspace (bi))
366 str_next_char (&bi);
367 if (bi[0] == '#')
368 continue;
369 for (start = bi;
370 bi[0] != '\0' && !str_isspace (bi);
371 str_next_char (&bi));
373 if (bi - start == 0) continue;
375 name = g_strndup (start, bi - start);
377 char **host_p;
379 if (hosts_p - hosts >= hosts_alloclen){
380 int j = hosts_p - hosts;
382 hosts = g_realloc ((void *)hosts, ((hosts_alloclen += 30) + 1) * sizeof (char *));
383 hosts_p = hosts + j;
385 for (host_p = hosts; host_p < hosts_p; host_p++)
386 if (!strcmp (name, *host_p))
387 break; /* We do not want any duplicates */
388 if (host_p == hosts_p){
389 *(hosts_p++) = name;
390 *hosts_p = NULL;
391 } else
392 g_free (name);
396 fclose (file);
399 static char *
400 hostname_completion_function (const char *text, int state, INPUT_COMPLETE_FLAGS flags)
402 static char **host_p;
403 static int textstart, textlen;
405 (void) flags;
406 SHOW_C_CTX("hostname_completion_function");
408 if (!state){ /* Initialization stuff */
409 const char *p;
411 if (hosts != NULL){
412 for (host_p = hosts; *host_p; host_p++)
413 g_free (*host_p);
414 g_free (hosts);
416 hosts = g_new (char *, (hosts_alloclen = 30) + 1);
417 *hosts = NULL;
418 hosts_p = hosts;
419 fetch_hosts ((p = getenv ("HOSTFILE")) ? p : "/etc/hosts");
420 host_p = hosts;
421 textstart = (*text == '@') ? 1 : 0;
422 textlen = strlen (text + textstart);
425 while (*host_p){
426 if (!textlen)
427 break; /* Match all of them */
428 else if (!strncmp (text + textstart, *host_p, textlen))
429 break;
430 host_p++;
433 if (!*host_p){
434 for (host_p = hosts; *host_p; host_p++)
435 g_free (*host_p);
436 g_free (hosts);
437 hosts = NULL;
438 return NULL;
439 } else {
440 char *temp = g_malloc (2 + strlen (*host_p));
442 if (textstart)
443 *temp = '@';
444 strcpy (temp + textstart, *host_p);
445 host_p++;
446 return temp;
451 * This is the function to call when the word to complete is in a position
452 * where a command word can be found. It looks around $PATH, looking for
453 * commands that match. It also scans aliases, function names, and the
454 * table of shell built-ins.
456 static char *
457 command_completion_function (const char *_text, int state, INPUT_COMPLETE_FLAGS flags)
459 char *text;
460 static const char *path_end;
461 static gboolean isabsolute;
462 static int phase;
463 static int text_len;
464 static const char *const *words;
465 static char *path;
466 static char *cur_path;
467 static char *cur_word;
468 static int init_state;
469 static const char *const bash_reserved[] = {
470 "if", "then", "else", "elif", "fi", "case", "esac", "for",
471 "select", "while", "until", "do", "done", "in", "function", 0
473 static const char *const bash_builtins[] = {
474 "alias", "bg", "bind", "break", "builtin", "cd", "command",
475 "continue", "declare", "dirs", "echo", "enable", "eval",
476 "exec", "exit", "export", "fc", "fg", "getopts", "hash",
477 "help", "history", "jobs", "kill", "let", "local", "logout",
478 "popd", "pushd", "pwd", "read", "readonly", "return", "set",
479 "shift", "source", "suspend", "test", "times", "trap", "type",
480 "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
482 char *p, *found;
484 SHOW_C_CTX("command_completion_function");
486 if (!(flags & INPUT_COMPLETE_COMMANDS))
487 return 0;
488 text = strutils_shell_unescape(_text);
489 flags &= ~INPUT_COMPLETE_SHELL_ESC;
491 if (!state) { /* Initialize us a little bit */
492 isabsolute = strchr (text, PATH_SEP) != NULL;
493 if (!isabsolute) {
494 words = bash_reserved;
495 phase = 0;
496 text_len = strlen (text);
497 if (!path && (path = g_strdup (getenv ("PATH"))) != NULL) {
498 p = path;
499 path_end = strchr (p, 0);
500 while ((p = strchr (p, PATH_ENV_SEP))) {
501 *p++ = 0;
507 if (isabsolute) {
508 p = filename_completion_function (text, state, flags);
510 if (p) {
511 char *temp_p = p;
512 p = strutils_shell_escape (p);
513 g_free (temp_p);
516 g_free (text);
517 return p;
520 found = NULL;
521 switch (phase) {
522 case 0: /* Reserved words */
523 while (*words) {
524 if (!strncmp (*words, text, text_len))
525 return g_strdup (*(words++));
526 words++;
528 phase++;
529 words = bash_builtins;
530 case 1: /* Builtin commands */
531 while (*words) {
532 if (!strncmp (*words, text, text_len))
533 return g_strdup (*(words++));
534 words++;
536 phase++;
537 if (!path)
538 break;
539 cur_path = path;
540 cur_word = NULL;
541 case 2: /* And looking through the $PATH */
542 while (!found) {
543 if (!cur_word) {
544 char *expanded;
546 if (cur_path >= path_end)
547 break;
548 expanded = tilde_expand (*cur_path ? cur_path : ".");
549 cur_word = concat_dir_and_file (expanded, text);
550 g_free (expanded);
551 canonicalize_pathname (cur_word);
552 cur_path = strchr (cur_path, 0) + 1;
553 init_state = state;
555 found =
556 filename_completion_function (cur_word,
557 state - init_state, flags);
558 if (!found) {
559 g_free (cur_word);
560 cur_word = NULL;
565 if (found == NULL) {
566 g_free (path);
567 path = NULL;
568 } else if ((p = strrchr (found, PATH_SEP)) != NULL) {
569 char *tmp = found;
570 found = strutils_shell_escape (p + 1);
571 g_free (tmp);
574 g_free(text);
575 return found;
578 static int
579 match_compare (const void *a, const void *b)
581 return strcmp (*(char **)a, *(char **)b);
584 /* Returns an array of char * matches with the longest common denominator
585 in the 1st entry. Then a NULL terminated list of different possible
586 completions follows.
587 You have to supply your own CompletionFunction with the word you
588 want to complete as the first argument and an count of previous matches
589 as the second.
590 In case no matches were found we return NULL. */
591 static char **
592 completion_matches (const char *text, CompletionFunction entry_function, INPUT_COMPLETE_FLAGS flags)
594 /* Number of slots in match_list. */
595 int match_list_size;
597 /* The list of matches. */
598 char **match_list = g_new (char *, (match_list_size = 30) + 1);
600 /* Number of matches actually found. */
601 int matches = 0;
603 /* Temporary string binder. */
604 char *string;
606 match_list[1] = NULL;
608 while ((string = (*entry_function) (text, matches, flags)) != NULL){
609 if (matches + 1 == match_list_size)
610 match_list = (char **) g_realloc (match_list, ((match_list_size += 30) + 1) * sizeof (char *));
611 match_list[++matches] = string;
612 match_list[matches + 1] = NULL;
615 /* If there were any matches, then look through them finding out the
616 lowest common denominator. That then becomes match_list[0]. */
617 if (matches)
619 register int i = 1;
620 int low = 4096; /* Count of max-matched characters. */
622 /* If only one match, just use that. */
623 if (matches == 1){
624 match_list[0] = match_list[1];
625 match_list[1] = NULL;
626 } else {
627 int j;
629 qsort (match_list + 1, matches, sizeof (char *), match_compare);
631 /* And compare each member of the list with
632 the next, finding out where they stop matching.
633 If we find two equal strings, we have to put one away... */
635 j = i + 1;
636 while (j < matches + 1)
638 char *si, *sj;
639 char *ni, *nj;
641 for (si = match_list[i], sj = match_list[j];
642 si[0] && sj[0];) {
644 ni = str_get_next_char (si);
645 nj = str_get_next_char (sj);
647 if (ni - si != nj - sj) break;
648 if (strncmp (si, sj, ni - si) != 0) break;
650 si = ni;
651 sj = nj;
654 if (si[0] == '\0' && sj[0] == '\0'){ /* Two equal strings */
655 g_free (match_list [j]);
656 j++;
657 if (j > matches)
658 break;
659 continue; /* Look for a run of equal strings */
660 } else
661 if (low > si - match_list[i]) low = si - match_list[i];
662 if (i + 1 != j) /* So there's some gap */
663 match_list [i + 1] = match_list [j];
664 i++; j++;
666 matches = i;
667 match_list [matches + 1] = NULL;
668 match_list[0] = g_strndup(match_list[1], low);
670 } else { /* There were no matches. */
671 g_free (match_list);
672 match_list = NULL;
674 return match_list;
677 /* Check if directory completion is needed */
678 static int
679 check_is_cd (const char *text, int start, INPUT_COMPLETE_FLAGS flags)
681 char *p, *q;
682 int test = 0;
684 SHOW_C_CTX("check_is_cd");
685 if (!(flags & INPUT_COMPLETE_CD))
686 return 0;
688 /* Skip initial spaces */
689 p = (char*)text;
690 q = (char*)text + start;
691 while (p < q && p[0] != '\0' && str_isspace (p))
692 str_next_char (&p);
694 /* Check if the command is "cd" and the cursor is after it */
695 text+= p[0] == 'c';
696 str_next_char (&p);
697 text+= p[0] == 'd';
698 str_next_char (&p);
699 text+= str_isspace (p);
700 if (test == 3 && (p < q))
701 return 1;
703 return 0;
706 /* Returns an array of matches, or NULL if none. */
707 static char **
708 try_complete (char *text, int *start, int *end, INPUT_COMPLETE_FLAGS flags)
710 int in_command_position = 0;
711 char *word;
712 char **matches = NULL;
713 const char *command_separator_chars = ";|&{(`";
714 char *p = NULL, *q = NULL, *r = NULL;
715 int is_cd = check_is_cd (text, *start, flags);
716 char *ti;
718 SHOW_C_CTX("try_complete");
719 word = g_strndup (text + *start, *end - *start);
721 /* Determine if this could be a command word. It is if it appears at
722 the start of the line (ignoring preceding whitespace), or if it
723 appears after a character that separates commands. And we have to
724 be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
725 if (!is_cd && (flags & INPUT_COMPLETE_COMMANDS)){
726 ti = str_get_prev_char (&text[*start]);
727 while (ti > text && (ti[0] == ' ' || ti[0] == '\t'))
728 str_prev_char (&ti);
729 if (ti <= text&& (ti[0] == ' ' || ti[0] == '\t'))
730 in_command_position++;
731 else if (strchr (command_separator_chars, ti[0])){
732 register int this_char, prev_char;
734 in_command_position++;
736 if (ti > text){
737 /* Handle the two character tokens `>&', `<&', and `>|'.
738 We are not in a command position after one of these. */
739 this_char = ti[0];
740 prev_char = str_get_prev_char (ti)[0];
742 if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
743 (this_char == '|' && prev_char == '>'))
744 in_command_position = 0;
746 else if (ti > text && str_get_prev_char (ti)[0] == '\\') /* Quoted */
747 in_command_position = 0;
752 if (flags & INPUT_COMPLETE_COMMANDS)
753 p = strrchr (word, '`');
754 if (flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
755 q = strrchr (word, '$');
756 if (flags & INPUT_COMPLETE_HOSTNAMES)
757 r = strrchr (word, '@');
758 if (q && q [1] == '(' && INPUT_COMPLETE_COMMANDS){
759 if (q > p)
760 p = str_get_next_char (q);
761 q = NULL;
764 /* Command substitution? */
765 if (p > q && p > r){
766 SHOW_C_CTX("try_complete:cmd_backq_subst");
767 matches = completion_matches (str_cget_next_char (p),
768 command_completion_function,
769 flags & (~INPUT_COMPLETE_FILENAMES));
770 if (matches)
771 *start += str_get_next_char (p) - word;
774 /* Variable name? */
775 else if (q > p && q > r){
776 SHOW_C_CTX("try_complete:var_subst");
777 matches = completion_matches (q, variable_completion_function, flags);
778 if (matches)
779 *start += q - word;
782 /* Starts with '@', then look through the known hostnames for
783 completion first. */
784 else if (r > p && r > q){
785 SHOW_C_CTX("try_complete:host_subst");
786 matches = completion_matches (r, hostname_completion_function, flags);
787 if (matches)
788 *start += r - word;
791 /* Starts with `~' and there is no slash in the word, then
792 try completing this word as a username. */
793 if (!matches && *word == '~' && (flags & INPUT_COMPLETE_USERNAMES) && !strchr (word, PATH_SEP))
795 SHOW_C_CTX("try_complete:user_subst");
796 matches = completion_matches (word, username_completion_function, flags);
800 /* And finally if this word is in a command position, then
801 complete over possible command names, including aliases, functions,
802 and command names. */
803 if (!matches && in_command_position)
805 SHOW_C_CTX("try_complete:cmd_subst");
806 matches = completion_matches (word, command_completion_function, flags & (~INPUT_COMPLETE_FILENAMES));
809 else if (!matches && (flags & INPUT_COMPLETE_FILENAMES)){
810 if (is_cd)
811 flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
812 SHOW_C_CTX("try_complete:filename_subst_1");
813 matches = completion_matches (word, filename_completion_function, flags);
814 if (!matches && is_cd && *word != PATH_SEP && *word != '~'){
815 q = text + *start;
816 for (p = text; *p && p < q && (*p == ' ' || *p == '\t'); str_next_char (&p));
817 if (!strncmp (p, "cd", 2))
818 for (p += 2; *p && p < q && (*p == ' ' || *p == '\t'); str_next_char (&p));
819 if (p == q){
820 char * const cdpath_ref = g_strdup (getenv ("CDPATH"));
821 char *cdpath = cdpath_ref;
822 char c, *s;
824 if (cdpath == NULL)
825 c = 0;
826 else
827 c = ':';
828 while (!matches && c == ':'){
829 s = strchr (cdpath, ':');
830 if (s == NULL)
831 s = strchr (cdpath, 0);
832 c = *s;
833 *s = 0;
834 if (*cdpath){
835 r = concat_dir_and_file (cdpath, word);
836 SHOW_C_CTX("try_complete:filename_subst_2");
837 matches = completion_matches (r, filename_completion_function, flags);
838 g_free (r);
840 *s = c;
841 cdpath = str_get_next_char (s);
843 g_free (cdpath_ref);
848 g_free (word);
850 return matches;
853 void free_completions (WInput *in)
855 char **p;
857 if (!in->completions)
858 return;
859 for (p=in->completions; *p; p++)
860 g_free (*p);
861 g_free (in->completions);
862 in->completions = NULL;
865 static int query_height, query_width;
866 static WInput *input;
867 static int min_end;
868 static int start, end;
870 static int
871 insert_text (WInput *in, char *text, ssize_t size)
873 int buff_len = str_length (in->buffer);
875 size = min (size, (ssize_t) strlen (text)) + start - end;
876 if (strlen (in->buffer) + size >= (size_t) in->current_max_size){
877 /* Expand the buffer */
878 char *narea = g_try_realloc (in->buffer, in->current_max_size
879 + size + in->field_width);
880 if (narea != NULL) {
881 in->buffer = narea;
882 in->current_max_size += size + in->field_width;
885 if (strlen (in->buffer)+1 < (size_t) in->current_max_size){
886 if (size > 0){
887 int i = strlen (&in->buffer [end]);
888 for (; i >= 0; i--)
889 in->buffer [end + size + i] = in->buffer [end + i];
890 } else if (size < 0){
891 char *p = in->buffer + end + size, *q = in->buffer + end;
892 while (*q)
893 *(p++) = *(q++);
894 *p = 0;
896 memcpy (in->buffer + start, text, size - start + end);
897 in->point+= str_length (in->buffer) - buff_len;
898 update_input (in, 1);
899 end+= size;
901 return size != 0;
904 static cb_ret_t
905 query_callback (Dlg_head *h, Widget *sender,
906 dlg_msg_t msg, int parm, void *data)
908 static char buff[MB_LEN_MAX] = "";
909 static int bl = 0;
911 switch (msg) {
912 case DLG_KEY:
913 switch (parm) {
914 case KEY_LEFT:
915 case KEY_RIGHT:
916 bl = 0;
917 h->ret_value = 0;
918 dlg_stop (h);
919 return MSG_HANDLED;
921 case KEY_BACKSPACE:
922 bl = 0;
923 if (end == min_end) {
924 h->ret_value = 0;
925 dlg_stop (h);
926 return MSG_HANDLED;
927 } else {
928 WLEntry *e, *e1;
930 e1 = e = ((WListbox *) (h->current))->list;
931 do {
932 if (!strncmp (input->buffer + start,
933 e1->text, end - start - 1)) {
935 listbox_select_entry ((WListbox *) (h->current), e1);
936 end = str_get_prev_char (&(input->buffer[end]))
937 - input->buffer;
938 handle_char (input, parm);
939 send_message (h->current, WIDGET_DRAW, 0);
940 break;
942 e1 = e1->next;
943 } while (e != e1);
945 return MSG_HANDLED;
947 default:
948 if (parm < 32 || parm > 256) {
949 bl = 0;
950 if (is_in_input_map (input, parm) == 2) {
951 if (end == min_end)
952 return MSG_HANDLED;
953 h->ret_value = B_USER; /* This means we want to refill the
954 list box and start again */
955 dlg_stop (h);
956 return MSG_HANDLED;
957 } else
958 return MSG_NOT_HANDLED;
959 } else {
960 WLEntry *e, *e1;
961 int need_redraw = 0;
962 int low = 4096;
963 char *last_text = NULL;
965 buff[bl] = (char) parm;
966 bl++;
967 buff[bl] = '\0';
968 switch (str_is_valid_char (buff, bl)) {
969 case -1:
970 bl = 0;
971 case -2:
972 return MSG_HANDLED;
975 e1 = e = ((WListbox *) (h->current))->list;
976 do {
977 if (!strncmp (input->buffer + start,
978 e1->text, end - start)) {
980 if (strncmp (&e1->text[end - start], buff, bl) == 0) {
981 if (need_redraw) {
982 char *si, *sl;
983 char *ni, *nl;
984 si = &(e1->text[end - start]);
985 sl = &(last_text[end - start]);
987 for (; si[0] != '\0' && sl[0] != '\0';) {
989 ni = str_get_next_char (si);
990 nl = str_get_next_char (sl);
992 if (ni - si != nl - sl) break;
993 if (strncmp (si, sl, ni - si) != 0) break;
995 si = ni;
996 sl = nl;
999 if (low > si - &e1->text[end - start])
1000 low = si - &e1->text[end - start];
1002 last_text = e1->text;
1003 need_redraw = 2;
1004 } else {
1005 need_redraw = 1;
1006 listbox_select_entry ((WListbox *) (h->
1007 current),
1008 e1);
1009 last_text = e1->text;
1013 e1 = e1->next;
1014 } while (e != e1);
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, NULL);
1110 add_widget (query_dlg, query_list);
1111 for (p = in->completions + 1; *p; p++)
1112 listbox_add_item (query_list, 0, 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));