root/lib/widget/input_complete.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. show_c_flags
  2. filename_completion_function
  3. username_completion_function
  4. variable_completion_function
  5. host_equal_func
  6. fetch_hosts
  7. hostname_completion_function
  8. command_completion_function
  9. match_compare
  10. completion_matches
  11. check_is_cd
  12. try_complete_commands_prepare
  13. try_complete_find_start_sign
  14. try_complete_all_possible
  15. insert_text
  16. complete_callback
  17. complete_engine
  18. try_complete
  19. complete_engine_fill_completions
  20. input_complete
  21. input_complete_free

   1 /*
   2    Input line filename/username/hostname/variable/command completion.
   3    (Let mc type for you...)
   4 
   5    Copyright (C) 1995-2024
   6    Free Software Foundation, Inc.
   7 
   8    Written by:
   9    Jakub Jelinek, 1995
  10    Slava Zanko <slavazanko@gmail.com>, 2013
  11    Andrew Borodin <aborodin@vmail.ru>, 2013-2022
  12 
  13    This file is part of the Midnight Commander.
  14 
  15    The Midnight Commander is free software: you can redistribute it
  16    and/or modify it under the terms of the GNU General Public License as
  17    published by the Free Software Foundation, either version 3 of the License,
  18    or (at your option) any later version.
  19 
  20    The Midnight Commander is distributed in the hope that it will be useful,
  21    but WITHOUT ANY WARRANTY; without even the implied warranty of
  22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23    GNU General Public License for more details.
  24 
  25    You should have received a copy of the GNU General Public License
  26    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  27  */
  28 
  29 /** \file lib/widget/input_complete.c
  30  *  \brief Source: Input line filename/username/hostname/variable/command completion
  31  */
  32 
  33 #include <config.h>
  34 
  35 #include <ctype.h>
  36 #include <limits.h>             /* MB_LEN_MAX */
  37 #include <stdio.h>
  38 #include <stdlib.h>
  39 #include <string.h>
  40 #include <dirent.h>
  41 #include <sys/types.h>
  42 #include <sys/stat.h>
  43 #include <pwd.h>
  44 #include <unistd.h>
  45 
  46 #include "lib/global.h"
  47 
  48 #include "lib/tty/tty.h"
  49 #include "lib/tty/key.h"        /* XCTRL and ALT macros */
  50 #include "lib/vfs/vfs.h"
  51 #include "lib/strutil.h"
  52 #include "lib/util.h"
  53 #include "lib/widget.h"
  54 
  55 /*** global variables ****************************************************************************/
  56 
  57 #if !HAVE_DECL_ENVIRON
  58 extern char **environ;
  59 #endif
  60 
  61 /*** file scope macro definitions ****************************************************************/
  62 
  63 /* #define DO_COMPLETION_DEBUG */
  64 #ifdef DO_COMPLETION_DEBUG
  65 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
  66 #else
  67 #define SHOW_C_CTX(func)
  68 #endif /* DO_CMPLETION_DEBUG */
  69 
  70 #define DO_INSERTION 1
  71 #define DO_QUERY     2
  72 
  73 /*** file scope type declarations ****************************************************************/
  74 
  75 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
  76 
  77 typedef struct
  78 {
  79     size_t in_command_position;
  80     char *word;
  81     char *p;
  82     char *q;
  83     char *r;
  84     gboolean is_cd;
  85     input_complete_t flags;
  86 } try_complete_automation_state_t;
  87 
  88 /*** forward declarations (file scope functions) *************************************************/
  89 
  90 MC_MOCKABLE GPtrArray *try_complete (char *text, int *lc_start, int *lc_end,
  91                                      input_complete_t flags);
  92 void complete_engine_fill_completions (WInput * in);
  93 
  94 /*** file scope variables ************************************************************************/
  95 
  96 static WInput *input;
  97 static int min_end;
  98 static int start = 0;
  99 static int end = 0;
 100 
 101 /* --------------------------------------------------------------------------------------------- */
 102 /*** file scope functions ************************************************************************/
 103 /* --------------------------------------------------------------------------------------------- */
 104 
 105 #ifdef DO_COMPLETION_DEBUG
 106 /**
 107  * Useful to print/debug completion flags
 108  */
 109 static const char *
 110 show_c_flags (input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 111 {
 112     static char s_cf[] = "FHCVUDS";
 113 
 114     s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
 115     s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
 116     s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
 117     s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
 118     s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
 119     s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
 120     s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
 121 
 122     return s_cf;
 123 }
 124 #endif /* DO_CMPLETION_DEBUG */
 125 
 126 /* --------------------------------------------------------------------------------------------- */
 127 
 128 static char *
 129 filename_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 130 {
 131     static DIR *directory = NULL;
 132     static char *filename = NULL;
 133     static char *dirname = NULL;
 134     static char *users_dirname = NULL;
 135     static size_t filename_len = 0;
 136     static vfs_path_t *dirname_vpath = NULL;
 137 
 138     gboolean isdir = TRUE, isexec = FALSE;
 139     struct vfs_dirent *entry = NULL;
 140 
 141     SHOW_C_CTX ("filename_completion_function");
 142 
 143     if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
 144     {
 145         char *u_text;
 146         char *result;
 147         char *e_result;
 148 
 149         u_text = str_shell_unescape (text);
 150 
 151         result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
 152         g_free (u_text);
 153 
 154         e_result = str_shell_escape (result);
 155         g_free (result);
 156 
 157         return e_result;
 158     }
 159 
 160     /* If we're starting the match process, initialize us a bit. */
 161     if (state == 0)
 162     {
 163         const char *temp;
 164 
 165         g_free (dirname);
 166         g_free (filename);
 167         g_free (users_dirname);
 168         vfs_path_free (dirname_vpath, TRUE);
 169 
 170         if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
 171         {
 172             filename = g_strdup (++temp);
 173             dirname = g_strndup (text, temp - text);
 174         }
 175         else
 176         {
 177             dirname = g_strdup (".");
 178             filename = g_strdup (text);
 179         }
 180 
 181         /* We aren't done yet.  We also support the "~user" syntax. */
 182 
 183         /* Save the version of the directory that the user typed. */
 184         users_dirname = dirname;
 185         dirname = tilde_expand (dirname);
 186         canonicalize_pathname (dirname);
 187         dirname_vpath = vfs_path_from_str (dirname);
 188 
 189         /* Here we should do something with variable expansion
 190            and `command`.
 191            Maybe a dream - UNIMPLEMENTED yet. */
 192 
 193         directory = mc_opendir (dirname_vpath);
 194         filename_len = strlen (filename);
 195     }
 196 
 197     /* Now that we have some state, we can read the directory. */
 198 
 199     while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
 200     {
 201         if (!str_is_valid_string (entry->d_name))
 202             continue;
 203 
 204         /* Special case for no filename.
 205            All entries except "." and ".." match. */
 206         if (filename_len == 0)
 207         {
 208             if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
 209                 continue;
 210         }
 211         else
 212         {
 213             /* Otherwise, if these match up to the length of filename, then
 214                it may be a match. */
 215             if (entry->d_name[0] != filename[0] || entry->d_len < filename_len
 216                 || strncmp (filename, entry->d_name, filename_len) != 0)
 217                 continue;
 218         }
 219 
 220         isdir = TRUE;
 221         isexec = FALSE;
 222 
 223         {
 224             struct stat tempstat;
 225             vfs_path_t *tmp_vpath;
 226 
 227             tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
 228 
 229             /* Unix version */
 230             if (mc_stat (tmp_vpath, &tempstat) == 0)
 231             {
 232                 uid_t my_uid;
 233                 gid_t my_gid;
 234 
 235                 my_uid = getuid ();
 236                 my_gid = getgid ();
 237 
 238                 if (!S_ISDIR (tempstat.st_mode))
 239                 {
 240                     isdir = FALSE;
 241 
 242                     if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
 243                         (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
 244                         (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
 245                         (tempstat.st_mode & 0001) != 0)
 246                         isexec = TRUE;
 247                 }
 248             }
 249             else
 250             {
 251                 /* stat failed, strange. not a dir in any case */
 252                 isdir = FALSE;
 253             }
 254             vfs_path_free (tmp_vpath, TRUE);
 255         }
 256 
 257         if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
 258             break;
 259         if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
 260             break;
 261         if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
 262             break;
 263     }
 264 
 265     if (entry == NULL)
 266     {
 267         if (directory != NULL)
 268         {
 269             mc_closedir (directory);
 270             directory = NULL;
 271         }
 272         MC_PTR_FREE (dirname);
 273         vfs_path_free (dirname_vpath, TRUE);
 274         dirname_vpath = NULL;
 275         MC_PTR_FREE (filename);
 276         MC_PTR_FREE (users_dirname);
 277         return NULL;
 278     }
 279 
 280     {
 281         GString *temp;
 282 
 283         temp = g_string_sized_new (16);
 284 
 285         if (users_dirname != NULL && !DIR_IS_DOT (users_dirname))
 286         {
 287             g_string_append (temp, users_dirname);
 288 
 289             /* We need a '/' at the end. */
 290             if (!IS_PATH_SEP (temp->str[temp->len - 1]))
 291                 g_string_append_c (temp, PATH_SEP);
 292         }
 293         g_string_append_len (temp, entry->d_name, entry->d_len);
 294         if (isdir)
 295             g_string_append_c (temp, PATH_SEP);
 296 
 297         return g_string_free (temp, FALSE);
 298     }
 299 }
 300 
 301 /* --------------------------------------------------------------------------------------------- */
 302 /** We assume here that text[0] == '~' , if you want to call it in another way,
 303    you have to change the code */
 304 
 305 static char *
 306 username_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 307 {
 308     static struct passwd *entry = NULL;
 309     static size_t userlen = 0;
 310 
 311     (void) flags;
 312     SHOW_C_CTX ("username_completion_function");
 313 
 314     if (text[0] == '\\' && text[1] == '~')
 315         text++;
 316     if (state == 0)
 317     {                           /* Initialization stuff */
 318         setpwent ();
 319         userlen = strlen (text + 1);
 320     }
 321 
 322     while ((entry = getpwent ()) != NULL)
 323     {
 324         /* Null usernames should result in all users as possible completions. */
 325         if (userlen == 0)
 326             break;
 327         if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
 328             break;
 329     }
 330 
 331     if (entry != NULL)
 332         return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
 333 
 334     endpwent ();
 335     return NULL;
 336 }
 337 
 338 /* --------------------------------------------------------------------------------------------- */
 339 /** We assume text [0] == '$' and want to have a look at text [1], if it is
 340    equal to '{', so that we should append '}' at the end */
 341 
 342 static char *
 343 variable_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 344 {
 345     static char **env_p = NULL;
 346     static gboolean isbrace = FALSE;
 347     static size_t varlen = 0;
 348     const char *p = NULL;
 349 
 350     (void) flags;
 351     SHOW_C_CTX ("variable_completion_function");
 352 
 353     if (state == 0)
 354     {                           /* Initialization stuff */
 355         isbrace = (text[1] == '{');
 356         varlen = strlen (text + 1 + isbrace);
 357         env_p = environ;
 358     }
 359 
 360     while (*env_p != NULL)
 361     {
 362         p = strchr (*env_p, '=');
 363         if (p != NULL && ((size_t) (p - *env_p) >= varlen)
 364             && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
 365             break;
 366         env_p++;
 367     }
 368 
 369     if (*env_p == NULL)
 370         return NULL;
 371 
 372     {
 373         GString *temp;
 374 
 375         temp = g_string_new_len (*env_p, p - *env_p);
 376 
 377         if (isbrace)
 378         {
 379             g_string_prepend_c (temp, '{');
 380             g_string_append_c (temp, '}');
 381         }
 382         g_string_prepend_c (temp, '$');
 383 
 384         env_p++;
 385 
 386         return g_string_free (temp, FALSE);
 387     }
 388 }
 389 
 390 /* --------------------------------------------------------------------------------------------- */
 391 
 392 static gboolean
 393 host_equal_func (gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 394 {
 395     return (strcmp ((const char *) a, (const char *) b) == 0);
 396 }
 397 
 398 /* --------------------------------------------------------------------------------------------- */
 399 
 400 static void
 401 fetch_hosts (const char *filename, GPtrArray *hosts)
     /* [previous][next][first][last][top][bottom][index][help]  */
 402 {
 403     FILE *file;
 404     char buffer[BUF_MEDIUM];
 405     char *bi;
 406 
 407     file = fopen (filename, "r");
 408     if (file == NULL)
 409         return;
 410 
 411     while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
 412     {
 413         /* Skip to first character. */
 414         for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
 415             ;
 416 
 417         /* Ignore comments... */
 418         if (bi[0] == '#')
 419             continue;
 420 
 421         /* Handle $include. */
 422         if (strncmp (bi, "$include ", 9) == 0)
 423         {
 424             char *includefile, *t;
 425 
 426             /* Find start of filename. */
 427             for (includefile = bi + 9; includefile[0] != '\0' && whitespace (includefile[0]);
 428                  includefile++)
 429                 ;
 430             t = includefile;
 431 
 432             /* Find end of filename. */
 433             for (; t[0] != '\0' && !str_isspace (t); str_next_char (&t))
 434                 ;
 435             *t = '\0';
 436 
 437             fetch_hosts (includefile, hosts);
 438             continue;
 439         }
 440 
 441         /* Skip IP #s. */
 442         for (; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
 443             ;
 444 
 445         /* Get the host names separated by white space. */
 446         while (bi[0] != '\0' && bi[0] != '#')
 447         {
 448             char *lc_start, *name;
 449 
 450             for (; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
 451                 ;
 452             if (bi[0] == '#')
 453                 continue;
 454 
 455             for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
 456                 ;
 457 
 458             if (bi == lc_start)
 459                 continue;
 460 
 461             name = g_strndup (lc_start, bi - lc_start);
 462             if (!g_ptr_array_find_with_equal_func (hosts, name, host_equal_func, NULL))
 463                 g_ptr_array_add (hosts, name);
 464             else
 465                 g_free (name);
 466         }
 467     }
 468 
 469     fclose (file);
 470 }
 471 
 472 /* --------------------------------------------------------------------------------------------- */
 473 
 474 static char *
 475 hostname_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 476 {
 477     static GPtrArray *hosts = NULL;
 478     static unsigned int host_p = 0;
 479     static size_t textstart = 0;
 480     static size_t textlen = 0;
 481 
 482     (void) flags;
 483     SHOW_C_CTX ("hostname_completion_function");
 484 
 485     if (state == 0)
 486     {                           /* Initialization stuff */
 487         const char *p;
 488 
 489         if (hosts != NULL)
 490             g_ptr_array_free (hosts, TRUE);
 491         hosts = g_ptr_array_new_with_free_func (g_free);
 492         p = getenv ("HOSTFILE");
 493         fetch_hosts (p != NULL ? p : "/etc/hosts", hosts);
 494         host_p = 0;
 495         textstart = (*text == '@') ? 1 : 0;
 496         textlen = strlen (text + textstart);
 497     }
 498 
 499     for (; host_p < hosts->len; host_p++)
 500     {
 501         if (textlen == 0)
 502             break;              /* Match all of them */
 503         if (strncmp (text + textstart, g_ptr_array_index (hosts, host_p), textlen) == 0)
 504             break;
 505     }
 506 
 507     if (host_p == hosts->len)
 508     {
 509         g_ptr_array_free (hosts, TRUE);
 510         hosts = NULL;
 511         return NULL;
 512     }
 513 
 514     {
 515         GString *temp;
 516 
 517         temp = g_string_sized_new (8);
 518 
 519         if (textstart != 0)
 520             g_string_append_c (temp, '@');
 521         g_string_append (temp, g_ptr_array_index (hosts, host_p));
 522         host_p++;
 523 
 524         return g_string_free (temp, FALSE);
 525     }
 526 }
 527 
 528 /* --------------------------------------------------------------------------------------------- */
 529 /**
 530  * This is the function to call when the word to complete is in a position
 531  * where a command word can be found. It looks around $PATH, looking for
 532  * commands that match. It also scans aliases, function names, and the
 533  * table of shell built-ins.
 534  */
 535 
 536 static char *
 537 command_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 538 {
 539     static const char *path_end = NULL;
 540     static gboolean isabsolute = FALSE;
 541     static int phase = 0;
 542     static size_t text_len = 0;
 543     static const char *const *words = NULL;
 544     static char *path = NULL;
 545     static char *cur_path = NULL;
 546     static char *cur_word = NULL;
 547     static int init_state = 0;
 548     static const char *const bash_reserved[] = {
 549         "if", "then", "else", "elif", "fi", "case", "esac", "for",
 550         "select", "while", "until", "do", "done", "in", "function", 0
 551     };
 552     static const char *const bash_builtins[] = {
 553         "alias", "bg", "bind", "break", "builtin", "cd", "command",
 554         "continue", "declare", "dirs", "echo", "enable", "eval",
 555         "exec", "exit", "export", "fc", "fg", "getopts", "hash",
 556         "help", "history", "jobs", "kill", "let", "local", "logout",
 557         "popd", "pushd", "pwd", "read", "readonly", "return", "set",
 558         "shift", "source", "suspend", "test", "times", "trap", "type",
 559         "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
 560     };
 561 
 562     char *u_text;
 563     char *p, *found;
 564 
 565     SHOW_C_CTX ("command_completion_function");
 566 
 567     if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
 568         return NULL;
 569 
 570     u_text = str_shell_unescape (text);
 571     flags &= ~INPUT_COMPLETE_SHELL_ESC;
 572 
 573     if (state == 0)
 574     {                           /* Initialize us a little bit */
 575         isabsolute = strchr (u_text, PATH_SEP) != NULL;
 576         if (!isabsolute)
 577         {
 578             words = bash_reserved;
 579             phase = 0;
 580             text_len = strlen (u_text);
 581 
 582             if (path == NULL)
 583             {
 584                 path = g_strdup (getenv ("PATH"));
 585                 if (path != NULL)
 586                 {
 587                     p = path;
 588                     path_end = strchr (p, '\0');
 589                     while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
 590                         *p++ = '\0';
 591                 }
 592             }
 593         }
 594     }
 595 
 596     if (isabsolute)
 597     {
 598         p = filename_completion_function (u_text, state, flags);
 599 
 600         if (p != NULL)
 601         {
 602             char *temp_p = p;
 603 
 604             p = str_shell_escape (p);
 605             g_free (temp_p);
 606         }
 607 
 608         g_free (u_text);
 609         return p;
 610     }
 611 
 612     found = NULL;
 613     switch (phase)
 614     {
 615     case 0:                    /* Reserved words */
 616         for (; *words != NULL; words++)
 617             if (strncmp (*words, u_text, text_len) == 0)
 618             {
 619                 g_free (u_text);
 620                 return g_strdup (*(words++));
 621             }
 622         phase++;
 623         words = bash_builtins;
 624         MC_FALLTHROUGH;
 625     case 1:                    /* Builtin commands */
 626         for (; *words != NULL; words++)
 627             if (strncmp (*words, u_text, text_len) == 0)
 628             {
 629                 g_free (u_text);
 630                 return g_strdup (*(words++));
 631             }
 632         phase++;
 633         if (path == NULL)
 634             break;
 635         cur_path = path;
 636         cur_word = NULL;
 637         MC_FALLTHROUGH;
 638     case 2:                    /* And looking through the $PATH */
 639         while (found == NULL)
 640         {
 641             if (cur_word == NULL)
 642             {
 643                 char *expanded;
 644 
 645                 if (cur_path >= path_end)
 646                     break;
 647                 expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
 648                 cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
 649                 g_free (expanded);
 650                 cur_path = strchr (cur_path, '\0') + 1;
 651                 init_state = state;
 652             }
 653             found = filename_completion_function (cur_word, state - init_state, flags);
 654             if (found == NULL)
 655                 MC_PTR_FREE (cur_word);
 656         }
 657         MC_FALLTHROUGH;
 658     default:
 659         break;
 660     }
 661 
 662     if (found == NULL)
 663         MC_PTR_FREE (path);
 664     else
 665     {
 666         p = strrchr (found, PATH_SEP);
 667         if (p != NULL)
 668         {
 669             char *tmp = found;
 670 
 671             found = str_shell_escape (p + 1);
 672             g_free (tmp);
 673         }
 674     }
 675 
 676     g_free (u_text);
 677     return found;
 678 }
 679 
 680 /* --------------------------------------------------------------------------------------------- */
 681 
 682 static int
 683 match_compare (gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 684 {
 685     return strcmp (*(char *const *) a, *(char *const *) b);
 686 }
 687 
 688 /* --------------------------------------------------------------------------------------------- */
 689 /** Returns an array of char * matches with the longest common denominator
 690    in the 1st entry. Then a NULL terminated list of different possible
 691    completions follows.
 692    You have to supply your own CompletionFunction with the word you
 693    want to complete as the first argument and an count of previous matches
 694    as the second. 
 695    In case no matches were found we return NULL. */
 696 
 697 static GPtrArray *
 698 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 699 {
 700     GPtrArray *match_list;
 701     char *string;
 702 
 703     match_list = g_ptr_array_new_with_free_func (g_free);
 704 
 705     while ((string = entry_function (text, match_list->len, flags)) != NULL)
 706         g_ptr_array_add (match_list, string);
 707 
 708     /* If there were any matches, then look through them finding out the
 709        lowest common denominator.  That then becomes match_list[0]. */
 710     if (match_list->len == 0)
 711     {
 712         /* There were no matches. */
 713         g_ptr_array_free (match_list, TRUE);
 714         return NULL;
 715     }
 716 
 717     /* If only one match, just use that. */
 718 
 719     if (match_list->len > 1)
 720     {
 721         size_t i, j;
 722         size_t low = 4096;      /* Count of max-matched characters. */
 723 
 724         g_ptr_array_sort (match_list, match_compare);
 725 
 726         /* And compare each member of the list with
 727            the next, finding out where they stop matching. 
 728            If we find two equal strings, we have to put one away... */
 729         for (i = 0, j = 1; j < match_list->len;)
 730         {
 731             char *si, *sj, *mi;
 732 
 733             si = g_ptr_array_index (match_list, i);
 734             sj = g_ptr_array_index (match_list, j);
 735             mi = si;
 736 
 737             while (si[0] != '\0' && sj[0] != '\0')
 738             {
 739                 char *ni, *nj;
 740 
 741                 ni = str_get_next_char (si);
 742                 nj = str_get_next_char (sj);
 743 
 744                 if (ni - si != nj - sj || strncmp (si, sj, ni - si) != 0)
 745                     break;
 746 
 747                 si = ni;
 748                 sj = nj;
 749             }
 750 
 751             if (si[0] == '\0' && sj[0] == '\0')
 752             {
 753                 /* Two equal strings */
 754                 g_ptr_array_remove_index (match_list, j);
 755             }
 756             else
 757             {
 758                 low = MIN (low, (size_t) (si - mi));
 759 
 760                 i++;
 761                 j++;
 762             }
 763         }
 764 
 765         string = g_ptr_array_index (match_list, 0);
 766         g_ptr_array_insert (match_list, 0, g_strndup (string, low));
 767     }
 768 
 769     return match_list;
 770 }
 771 
 772 /* --------------------------------------------------------------------------------------------- */
 773 /** Check if directory completion is needed */
 774 static gboolean
 775 check_is_cd (const char *text, int lc_start, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 776 {
 777     const char *p, *q;
 778 
 779     SHOW_C_CTX ("check_is_cd");
 780 
 781     if ((flags & INPUT_COMPLETE_CD) == 0)
 782         return FALSE;
 783 
 784     /* Skip initial spaces */
 785     p = text;
 786     q = text + lc_start;
 787     while (p < q && p[0] != '\0' && str_isspace (p))
 788         str_cnext_char (&p);
 789 
 790     /* Check if the command is "cd" and the cursor is after it */
 791     return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
 792 }
 793 
 794 /* --------------------------------------------------------------------------------------------- */
 795 
 796 static void
 797 try_complete_commands_prepare (try_complete_automation_state_t *state, char *text, int *lc_start)
     /* [previous][next][first][last][top][bottom][index][help]  */
 798 {
 799     const char *command_separator_chars = ";|&{(`";
 800     char *ti;
 801 
 802     if (*lc_start == 0)
 803         ti = text;
 804     else
 805     {
 806         ti = str_get_prev_char (&text[*lc_start]);
 807         while (ti > text && whitespace (ti[0]))
 808             str_prev_char (&ti);
 809     }
 810 
 811     if (ti == text)
 812         state->in_command_position++;
 813     else if (strchr (command_separator_chars, ti[0]) != NULL)
 814     {
 815         state->in_command_position++;
 816         if (ti != text)
 817         {
 818             int this_char, prev_char;
 819 
 820             /* Handle the two character tokens '>&', '<&', and '>|'.
 821                We are not in a command position after one of these. */
 822             this_char = ti[0];
 823             prev_char = str_get_prev_char (ti)[0];
 824 
 825             /* Quoted */
 826             if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
 827                 || (this_char == '|' && prev_char == '>') || (ti != text
 828                                                               && str_get_prev_char (ti)[0] == '\\'))
 829                 state->in_command_position = 0;
 830         }
 831     }
 832 }
 833 
 834 /* --------------------------------------------------------------------------------------------- */
 835 
 836 static void
 837 try_complete_find_start_sign (try_complete_automation_state_t *state)
     /* [previous][next][first][last][top][bottom][index][help]  */
 838 {
 839     if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
 840         state->p = strrchr (state->word, '`');
 841     if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
 842     {
 843         state->q = strrchr (state->word, '$');
 844 
 845         /* don't substitute variable in \$ case */
 846         if (str_is_char_escaped (state->word, state->q))
 847         {
 848             /* drop '\\' */
 849             str_move (state->q - 1, state->q);
 850             /* adjust flags */
 851             state->flags &= ~INPUT_COMPLETE_VARIABLES;
 852             state->q = NULL;
 853         }
 854     }
 855     if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
 856         state->r = strrchr (state->word, '@');
 857     if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
 858     {
 859         if (state->q > state->p)
 860             state->p = str_get_next_char (state->q);
 861         state->q = NULL;
 862     }
 863 }
 864 
 865 /* --------------------------------------------------------------------------------------------- */
 866 
 867 static GPtrArray *
 868 try_complete_all_possible (try_complete_automation_state_t *state, char *text, int *lc_start)
     /* [previous][next][first][last][top][bottom][index][help]  */
 869 {
 870     GPtrArray *matches = NULL;
 871 
 872     if (state->in_command_position != 0)
 873     {
 874         SHOW_C_CTX ("try_complete:cmd_subst");
 875         matches =
 876             completion_matches (state->word, command_completion_function,
 877                                 state->flags & (~INPUT_COMPLETE_FILENAMES));
 878     }
 879     else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
 880     {
 881         if (state->is_cd)
 882             state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
 883         SHOW_C_CTX ("try_complete:filename_subst_1");
 884         matches = completion_matches (state->word, filename_completion_function, state->flags);
 885 
 886         if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
 887         {
 888             state->q = text + *lc_start;
 889             for (state->p = text;
 890                  *state->p != '\0' && state->p < state->q && whitespace (*state->p);
 891                  str_next_char (&state->p))
 892                 ;
 893             if (strncmp (state->p, "cd", 2) == 0)
 894                 for (state->p += 2;
 895                      *state->p != '\0' && state->p < state->q && whitespace (*state->p);
 896                      str_next_char (&state->p))
 897                     ;
 898             if (state->p == state->q)
 899             {
 900                 char *cdpath_ref, *cdpath;
 901                 char c;
 902 
 903                 cdpath_ref = g_strdup (getenv ("CDPATH"));
 904                 cdpath = cdpath_ref;
 905                 c = (cdpath == NULL) ? '\0' : ':';
 906 
 907                 while (matches == NULL && c == ':')
 908                 {
 909                     char *s;
 910 
 911                     s = strchr (cdpath, ':');
 912                     /* cppcheck-suppress nullPointer */
 913                     if (s == NULL)
 914                         s = strchr (cdpath, '\0');
 915                     c = *s;
 916                     *s = '\0';
 917                     if (*cdpath != '\0')
 918                     {
 919                         state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
 920                         SHOW_C_CTX ("try_complete:filename_subst_2");
 921                         matches =
 922                             completion_matches (state->r, filename_completion_function,
 923                                                 state->flags);
 924                         g_free (state->r);
 925                     }
 926                     *s = c;
 927                     cdpath = str_get_next_char (s);
 928                 }
 929                 g_free (cdpath_ref);
 930             }
 931         }
 932     }
 933     return matches;
 934 }
 935 
 936 /* --------------------------------------------------------------------------------------------- */
 937 
 938 static gboolean
 939 insert_text (WInput *in, const char *text, ssize_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 940 {
 941     size_t text_len;
 942     int buff_len;
 943     ssize_t new_size;
 944 
 945     text_len = strlen (text);
 946     buff_len = str_length (in->buffer->str);
 947     if (size < 0)
 948         size = (ssize_t) text_len;
 949     else
 950         size = MIN (size, (ssize_t) text_len);
 951 
 952     new_size = size + start - end;
 953     if (new_size != 0)
 954     {
 955         /* make a hole within buffer */
 956 
 957         size_t tail_len;
 958 
 959         tail_len = in->buffer->len - end;
 960         if (tail_len != 0)
 961         {
 962             char *tail;
 963             size_t hole_end;
 964 
 965             tail = g_strndup (in->buffer->str + end, tail_len);
 966 
 967             hole_end = end + new_size;
 968             if (in->buffer->len < hole_end)
 969                 g_string_set_size (in->buffer, hole_end + tail_len);
 970 
 971             g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
 972 
 973             g_free (tail);
 974         }
 975     }
 976 
 977     g_string_overwrite_len (in->buffer, start, text, size);
 978 
 979     in->point += str_length (in->buffer->str) - buff_len;
 980     input_update (in, TRUE);
 981     end += new_size;
 982 
 983     return new_size != 0;
 984 }
 985 
 986 /* --------------------------------------------------------------------------------------------- */
 987 
 988 static cb_ret_t
 989 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 990 {
 991     static int bl = 0;
 992 
 993     WGroup *g = GROUP (w);
 994     WDialog *h = DIALOG (w);
 995 
 996     switch (msg)
 997     {
 998     case MSG_KEY:
 999         switch (parm)
1000         {
1001         case KEY_LEFT:
1002         case KEY_RIGHT:
1003             bl = 0;
1004             h->ret_value = 0;
1005             dlg_close (h);
1006             return MSG_HANDLED;
1007 
1008         case KEY_BACKSPACE:
1009             bl = 0;
1010             /* exit from completion list if input line is empty */
1011             if (end == 0)
1012             {
1013                 h->ret_value = 0;
1014                 dlg_close (h);
1015             }
1016             /* Refill the list box and start again */
1017             else if (end == min_end)
1018             {
1019                 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1020                 input_handle_char (input, parm);
1021                 h->ret_value = B_USER;
1022                 dlg_close (h);
1023             }
1024             else
1025             {
1026                 int new_end;
1027                 int i;
1028                 GList *e;
1029 
1030                 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1031 
1032                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1033                      e != NULL; i++, e = g_list_next (e))
1034                 {
1035                     WLEntry *le = LENTRY (e->data);
1036 
1037                     if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1038                     {
1039                         listbox_set_current (LISTBOX (g->current->data), i);
1040                         end = new_end;
1041                         input_handle_char (input, parm);
1042                         widget_draw (WIDGET (g->current->data));
1043                         break;
1044                     }
1045                 }
1046             }
1047             return MSG_HANDLED;
1048 
1049         default:
1050             if (parm < 32 || parm > 255)
1051             {
1052                 bl = 0;
1053                 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1054                     return MSG_NOT_HANDLED;
1055 
1056                 if (end == min_end)
1057                     return MSG_HANDLED;
1058 
1059                 /* This means we want to refill the list box and start again */
1060                 h->ret_value = B_USER;
1061                 dlg_close (h);
1062             }
1063             else
1064             {
1065                 static char buff[MB_LEN_MAX] = "";
1066                 GList *e;
1067                 int i;
1068                 int need_redraw = 0;
1069                 int low = 4096;
1070                 char *last_text = NULL;
1071 
1072                 buff[bl++] = (char) parm;
1073                 buff[bl] = '\0';
1074 
1075                 switch (str_is_valid_char (buff, bl))
1076                 {
1077                 case -1:
1078                     bl = 0;
1079                     MC_FALLTHROUGH;
1080                 case -2:
1081                     return MSG_HANDLED;
1082                 default:
1083                     break;
1084                 }
1085 
1086                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1087                      e != NULL; i++, e = g_list_next (e))
1088                 {
1089                     WLEntry *le = LENTRY (e->data);
1090 
1091                     if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1092                         && strncmp (le->text + end - start, buff, bl) == 0)
1093                     {
1094                         if (need_redraw == 0)
1095                         {
1096                             need_redraw = 1;
1097                             listbox_set_current (LISTBOX (g->current->data), i);
1098                             last_text = le->text;
1099                         }
1100                         else
1101                         {
1102                             char *si, *sl;
1103                             int si_num = 0;
1104                             int sl_num = 0;
1105 
1106                             /* count symbols between start and end */
1107                             for (si = le->text + start; si < le->text + end;
1108                                  str_next_char (&si), si_num++)
1109                                 ;
1110                             for (sl = last_text + start; sl < last_text + end;
1111                                  str_next_char (&sl), sl_num++)
1112                                 ;
1113 
1114                             /* pointers to next symbols */
1115                             si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1116                             sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1117 
1118                             while (si[0] != '\0' && sl[0] != '\0')
1119                             {
1120                                 char *nexti, *nextl;
1121 
1122                                 nexti = str_get_next_char (si);
1123                                 nextl = str_get_next_char (sl);
1124 
1125                                 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1126                                     break;
1127 
1128                                 si = nexti;
1129                                 sl = nextl;
1130 
1131                                 si_num++;
1132                             }
1133 
1134                             last_text = le->text;
1135 
1136                             si = &last_text[str_offset_to_pos (last_text, si_num)];
1137                             if (low > si - last_text)
1138                                 low = si - last_text;
1139 
1140                             need_redraw = 2;
1141                         }
1142                     }
1143                 }
1144 
1145                 if (need_redraw == 2)
1146                 {
1147                     insert_text (input, last_text, low);
1148                     widget_draw (WIDGET (g->current->data));
1149                 }
1150                 else if (need_redraw == 1)
1151                 {
1152                     h->ret_value = B_ENTER;
1153                     dlg_close (h);
1154                 }
1155                 bl = 0;
1156             }
1157         }
1158         return MSG_HANDLED;
1159 
1160     default:
1161         return dlg_default_callback (w, sender, msg, parm, data);
1162     }
1163 }
1164 
1165 /* --------------------------------------------------------------------------------------------- */
1166 
1167 /** Returns TRUE if the user would like to see us again */
1168 static gboolean
1169 complete_engine (WInput *in, int what_to_do)
     /* [previous][next][first][last][top][bottom][index][help]  */
1170 {
1171     if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1172         input_complete_free (in);
1173 
1174     if (in->completions == NULL)
1175         complete_engine_fill_completions (in);
1176 
1177     if (in->completions == NULL)
1178         tty_beep ();
1179     else
1180     {
1181         if ((what_to_do & DO_INSERTION) != 0
1182             || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1183         {
1184             const char *lc_complete;
1185 
1186             lc_complete = g_ptr_array_index (in->completions, 0);
1187             if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1188                 tty_beep ();
1189             else
1190                 input_complete_free (in);
1191         }
1192 
1193         if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1194         {
1195             int maxlen = 0;
1196             int i;
1197             size_t k;
1198             int count;
1199             int x, y, w, h;
1200             int start_x, start_y;
1201             char *q;
1202             WDialog *complete_dlg;
1203             WListbox *complete_list;
1204 
1205             for (k = 1; k < in->completions->len; k++)
1206             {
1207                 q = g_ptr_array_index (in->completions, k);
1208                 i = str_term_width1 (q);
1209                 maxlen = MAX (maxlen, i);
1210             }
1211 
1212             count = in->completions->len - 1;
1213             start_x = WIDGET (in)->rect.x;
1214             start_y = WIDGET (in)->rect.y;
1215             if (start_y - 2 >= count)
1216             {
1217                 y = start_y - 2 - count;
1218                 h = 2 + count;
1219             }
1220             else if (start_y >= LINES - start_y - 1)
1221             {
1222                 y = 0;
1223                 h = start_y;
1224             }
1225             else
1226             {
1227                 y = start_y + 1;
1228                 h = LINES - start_y - 1;
1229             }
1230             x = start - in->term_first_shown - 2 + start_x;
1231             w = maxlen + 4;
1232             if (x + w > COLS)
1233                 x = COLS - w;
1234             if (x < 0)
1235                 x = 0;
1236             if (x + w > COLS)
1237                 w = COLS;
1238 
1239             input = in;
1240             min_end = end;
1241 
1242             complete_dlg =
1243                 dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE,
1244                             dialog_colors, complete_callback, NULL, "[Completion]", NULL);
1245             complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1246             group_add_widget (GROUP (complete_dlg), complete_list);
1247 
1248             for (k = 1; k < in->completions->len; k++)
1249             {
1250                 q = g_ptr_array_index (in->completions, k);
1251                 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1252             }
1253 
1254             i = dlg_run (complete_dlg);
1255             q = NULL;
1256             if (i == B_ENTER)
1257             {
1258                 listbox_get_current (complete_list, &q, NULL);
1259                 if (q != NULL)
1260                     insert_text (in, q, -1);
1261             }
1262             if (q != NULL || end != min_end)
1263                 input_complete_free (in);
1264             widget_destroy (WIDGET (complete_dlg));
1265 
1266             /* B_USER if user wants to start over again */
1267             return (i == B_USER);
1268         }
1269     }
1270 
1271     return FALSE;
1272 }
1273 
1274 /* --------------------------------------------------------------------------------------------- */
1275 /*** public functions ****************************************************************************/
1276 /* --------------------------------------------------------------------------------------------- */
1277 
1278 /** Returns an array of matches, or NULL if none. */
1279 GPtrArray *
1280 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
1281 {
1282     try_complete_automation_state_t state;
1283     GPtrArray *matches = NULL;
1284 
1285     memset (&state, 0, sizeof (state));
1286     state.flags = flags;
1287 
1288     SHOW_C_CTX ("try_complete");
1289     state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1290 
1291     state.is_cd = check_is_cd (text, *lc_start, state.flags);
1292 
1293     /* Determine if this could be a command word. It is if it appears at
1294        the start of the line (ignoring preceding whitespace), or if it
1295        appears after a character that separates commands. And we have to
1296        be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1297     if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1298         try_complete_commands_prepare (&state, text, lc_start);
1299 
1300     try_complete_find_start_sign (&state);
1301 
1302     /* Command substitution? */
1303     if (state.p > state.q && state.p > state.r)
1304     {
1305         SHOW_C_CTX ("try_complete:cmd_backq_subst");
1306         matches = completion_matches (str_cget_next_char (state.p),
1307                                       command_completion_function,
1308                                       state.flags & (~INPUT_COMPLETE_FILENAMES));
1309         if (matches != NULL)
1310             *lc_start += str_get_next_char (state.p) - state.word;
1311     }
1312 
1313     /* Variable name? */
1314     else if (state.q > state.p && state.q > state.r)
1315     {
1316         SHOW_C_CTX ("try_complete:var_subst");
1317         matches = completion_matches (state.q, variable_completion_function, state.flags);
1318         if (matches != NULL)
1319             *lc_start += state.q - state.word;
1320     }
1321 
1322     /* Starts with '@', then look through the known hostnames for 
1323        completion first. */
1324     else if (state.r > state.p && state.r > state.q)
1325     {
1326         SHOW_C_CTX ("try_complete:host_subst");
1327         matches = completion_matches (state.r, hostname_completion_function, state.flags);
1328         if (matches != NULL)
1329             *lc_start += state.r - state.word;
1330     }
1331 
1332     /* Starts with '~' and there is no slash in the word, then
1333        try completing this word as a username. */
1334     if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1335         && strchr (state.word, PATH_SEP) == NULL)
1336     {
1337         SHOW_C_CTX ("try_complete:user_subst");
1338         matches = completion_matches (state.word, username_completion_function, state.flags);
1339     }
1340 
1341     /* If this word is in a command position, then
1342        complete over possible command names, including aliases, functions,
1343        and command names. */
1344     if (matches == NULL)
1345         matches = try_complete_all_possible (&state, text, lc_start);
1346 
1347     /* And finally if nothing found, try complete directory name */
1348     if (matches == NULL)
1349     {
1350         state.in_command_position = 0;
1351         matches = try_complete_all_possible (&state, text, lc_start);
1352     }
1353 
1354     g_free (state.word);
1355 
1356     if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0 &&
1357         (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1358     {
1359         /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1360         size_t i;
1361 
1362         for (i = 0; i < matches->len; i++)
1363         {
1364             char *p;
1365 
1366             p = g_ptr_array_index (matches, i);
1367             /* Escape only '?', '*', and '&' symbols as described in the
1368                manual page (see a11995e12b88285e044f644904c306ed6c342ad0). */
1369             g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1370             g_free (p);
1371         }
1372     }
1373 
1374     return matches;
1375 }
1376 
1377 /* --------------------------------------------------------------------------------------------- */
1378 
1379 void
1380 complete_engine_fill_completions (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1381 {
1382     char *s;
1383     const char *word_separators;
1384 
1385     word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1386 
1387     end = str_offset_to_pos (in->buffer->str, in->point);
1388 
1389     s = in->buffer->str;
1390     if (in->point != 0)
1391     {
1392         /* get symbol before in->point */
1393         size_t i;
1394 
1395         for (i = in->point - 1; i > 0; i--)
1396             str_next_char (&s);
1397     }
1398 
1399     for (; s >= in->buffer->str; str_prev_char (&s))
1400     {
1401         start = s - in->buffer->str;
1402         if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1403             break;
1404     }
1405 
1406     if (start < end)
1407     {
1408         str_next_char (&s);
1409         start = s - in->buffer->str;
1410     }
1411 
1412     in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1413 }
1414 
1415 /* --------------------------------------------------------------------------------------------- */
1416 
1417 /* declared in lib/widget/input.h */
1418 void
1419 input_complete (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1420 {
1421     int engine_flags;
1422 
1423     if (!str_is_valid_string (in->buffer->str))
1424         return;
1425 
1426     if (in->completions != NULL)
1427         engine_flags = DO_QUERY;
1428     else
1429     {
1430         engine_flags = DO_INSERTION;
1431 
1432         if (mc_global.widget.show_all_if_ambiguous)
1433             engine_flags |= DO_QUERY;
1434     }
1435 
1436     while (complete_engine (in, engine_flags))
1437         ;
1438 }
1439 
1440 /* --------------------------------------------------------------------------------------------- */
1441 
1442 void
1443 input_complete_free (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1444 {
1445     if (in->completions != NULL)
1446     {
1447         g_ptr_array_free (in->completions, TRUE);
1448         in->completions = NULL;
1449     }
1450 }
1451 
1452 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */