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-2025
   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 <https://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)                                                                       \
  66         fprintf (stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags (flags))
  67 #else
  68 #    define SHOW_C_CTX(func)
  69 #endif
  70 
  71 #define DO_INSERTION 1
  72 #define DO_QUERY     2
  73 
  74 /*** file scope type declarations ****************************************************************/
  75 
  76 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
  77 
  78 typedef struct
  79 {
  80     size_t in_command_position;
  81     char *word;
  82     char *p;
  83     char *q;
  84     char *r;
  85     gboolean is_cd;
  86     input_complete_t flags;
  87 } try_complete_automation_state_t;
  88 
  89 /*** forward declarations (file scope functions) *************************************************/
  90 
  91 MC_MOCKABLE GPtrArray *try_complete (char *text, int *lc_start, int *lc_end,
  92                                      input_complete_t flags);
  93 void complete_engine_fill_completions (WInput *in);
  94 
  95 /*** file scope variables ************************************************************************/
  96 
  97 static WInput *input;
  98 static int min_end;
  99 static int start = 0;
 100 static int end = 0;
 101 
 102 /* --------------------------------------------------------------------------------------------- */
 103 /*** file scope functions ************************************************************************/
 104 /* --------------------------------------------------------------------------------------------- */
 105 
 106 #ifdef DO_COMPLETION_DEBUG
 107 /**
 108  * Useful to print/debug completion flags
 109  */
 110 static const char *
 111 show_c_flags (input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 112 {
 113     static char s_cf[] = "FHCVUDS";
 114 
 115     s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
 116     s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
 117     s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
 118     s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
 119     s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
 120     s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
 121     s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
 122 
 123     return s_cf;
 124 }
 125 #endif
 126 
 127 /* --------------------------------------------------------------------------------------------- */
 128 
 129 static char *
 130 filename_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 131 {
 132     static DIR *directory = NULL;
 133     static char *filename = NULL;
 134     static char *dirname = NULL;
 135     static char *users_dirname = NULL;
 136     static size_t filename_len = 0;
 137     static vfs_path_t *dirname_vpath = NULL;
 138 
 139     gboolean isdir = TRUE, isexec = FALSE;
 140     struct vfs_dirent *entry = NULL;
 141 
 142     SHOW_C_CTX ("filename_completion_function");
 143 
 144     if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
 145     {
 146         char *u_text;
 147         char *result;
 148         char *e_result;
 149 
 150         u_text = str_shell_unescape (text);
 151 
 152         result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
 153         g_free (u_text);
 154 
 155         e_result = str_shell_escape (result);
 156         g_free (result);
 157 
 158         return e_result;
 159     }
 160 
 161     // If we're starting the match process, initialize us a bit.
 162     if (state == 0)
 163     {
 164         const char *temp;
 165 
 166         g_free (dirname);
 167         g_free (filename);
 168         g_free (users_dirname);
 169         vfs_path_free (dirname_vpath, TRUE);
 170 
 171         if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
 172         {
 173             filename = g_strdup (++temp);
 174             dirname = g_strndup (text, temp - text);
 175         }
 176         else
 177         {
 178             dirname = g_strdup (".");
 179             filename = g_strdup (text);
 180         }
 181 
 182         // We aren't done yet.  We also support the "~user" syntax.
 183 
 184         // Save the version of the directory that the user typed.
 185         users_dirname = dirname;
 186         dirname = tilde_expand (dirname);
 187         canonicalize_pathname (dirname);
 188         dirname_vpath = vfs_path_from_str (dirname);
 189 
 190         /* Here we should do something with variable expansion
 191            and `command`.
 192            Maybe a dream - UNIMPLEMENTED yet. */
 193 
 194         directory = mc_opendir (dirname_vpath);
 195         filename_len = strlen (filename);
 196     }
 197 
 198     // Now that we have some state, we can read the directory.
 199 
 200     while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
 201     {
 202         if (!str_is_valid_string (entry->d_name))
 203             continue;
 204 
 205         /* Special case for no filename.
 206            All entries except "." and ".." match. */
 207         if (filename_len == 0)
 208         {
 209             if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
 210                 continue;
 211         }
 212         else
 213         {
 214             /* Otherwise, if these match up to the length of filename, then
 215                it may be a match. */
 216             if (entry->d_name[0] != filename[0] || entry->d_len < filename_len
 217                 || strncmp (filename, entry->d_name, filename_len) != 0)
 218                 continue;
 219         }
 220 
 221         isdir = TRUE;
 222         isexec = FALSE;
 223 
 224         {
 225             struct stat tempstat;
 226             vfs_path_t *tmp_vpath;
 227 
 228             tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
 229 
 230             // Unix version
 231             if (mc_stat (tmp_vpath, &tempstat) == 0)
 232             {
 233                 uid_t my_uid;
 234                 gid_t my_gid;
 235 
 236                 my_uid = getuid ();
 237                 my_gid = getgid ();
 238 
 239                 if (!S_ISDIR (tempstat.st_mode))
 240                 {
 241                     isdir = FALSE;
 242 
 243                     if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0)
 244                         || (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0)
 245                         || (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0)
 246                         || (tempstat.st_mode & 0001) != 0)
 247                         isexec = TRUE;
 248                 }
 249             }
 250             else
 251             {
 252                 // stat failed, strange. not a dir in any case
 253                 isdir = FALSE;
 254             }
 255             vfs_path_free (tmp_vpath, TRUE);
 256         }
 257 
 258         if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
 259             break;
 260         if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
 261             break;
 262         if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
 263             break;
 264     }
 265 
 266     if (entry == NULL)
 267     {
 268         if (directory != NULL)
 269         {
 270             mc_closedir (directory);
 271             directory = NULL;
 272         }
 273         MC_PTR_FREE (dirname);
 274         vfs_path_free (dirname_vpath, TRUE);
 275         dirname_vpath = NULL;
 276         MC_PTR_FREE (filename);
 277         MC_PTR_FREE (users_dirname);
 278         return NULL;
 279     }
 280 
 281     {
 282         GString *temp;
 283 
 284         temp = g_string_sized_new (16);
 285 
 286         if (users_dirname != NULL && !DIR_IS_DOT (users_dirname))
 287         {
 288             g_string_append (temp, users_dirname);
 289 
 290             // We need a '/' at the end.
 291             if (!IS_PATH_SEP (temp->str[temp->len - 1]))
 292                 g_string_append_c (temp, PATH_SEP);
 293         }
 294         g_string_append_len (temp, entry->d_name, entry->d_len);
 295         if (isdir)
 296             g_string_append_c (temp, PATH_SEP);
 297 
 298         return g_string_free (temp, FALSE);
 299     }
 300 }
 301 
 302 /* --------------------------------------------------------------------------------------------- */
 303 /** We assume here that text[0] == '~' , if you want to call it in another way,
 304    you have to change the code */
 305 
 306 static char *
 307 username_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 308 {
 309     static struct passwd *entry = NULL;
 310     static size_t userlen = 0;
 311 
 312     (void) flags;
 313     SHOW_C_CTX ("username_completion_function");
 314 
 315     if (text[0] == '\\' && text[1] == '~')
 316         text++;
 317     if (state == 0)
 318     {  // Initialization stuff
 319         setpwent ();
 320         userlen = strlen (text + 1);
 321     }
 322 
 323     while ((entry = getpwent ()) != NULL)
 324     {
 325         // Null usernames should result in all users as possible completions.
 326         if (userlen == 0)
 327             break;
 328         if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
 329             break;
 330     }
 331 
 332     if (entry != NULL)
 333         return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
 334 
 335     endpwent ();
 336     return NULL;
 337 }
 338 
 339 /* --------------------------------------------------------------------------------------------- */
 340 /** We assume text [0] == '$' and want to have a look at text [1], if it is
 341    equal to '{', so that we should append '}' at the end */
 342 
 343 static char *
 344 variable_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 345 {
 346     static char **env_p = NULL;
 347     static gboolean isbrace = FALSE;
 348     static size_t varlen = 0;
 349     const char *p = NULL;
 350 
 351     (void) flags;
 352     SHOW_C_CTX ("variable_completion_function");
 353 
 354     if (state == 0)
 355     {  // Initialization stuff
 356         isbrace = (text[1] == '{');
 357         varlen = strlen (text + 1 + isbrace);
 358         env_p = environ;
 359     }
 360 
 361     while (*env_p != NULL)
 362     {
 363         p = strchr (*env_p, '=');
 364         if (p != NULL && ((size_t) (p - *env_p) >= varlen)
 365             && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
 366             break;
 367         env_p++;
 368     }
 369 
 370     if (*env_p == NULL)
 371         return NULL;
 372 
 373     {
 374         GString *temp;
 375 
 376         temp = g_string_new_len (*env_p, p - *env_p);
 377 
 378         if (isbrace)
 379         {
 380             g_string_prepend_c (temp, '{');
 381             g_string_append_c (temp, '}');
 382         }
 383         g_string_prepend_c (temp, '$');
 384 
 385         env_p++;
 386 
 387         return g_string_free (temp, FALSE);
 388     }
 389 }
 390 
 391 /* --------------------------------------------------------------------------------------------- */
 392 
 393 static gboolean
 394 host_equal_func (gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 395 {
 396     return (strcmp ((const char *) a, (const char *) b) == 0);
 397 }
 398 
 399 /* --------------------------------------------------------------------------------------------- */
 400 
 401 static void
 402 fetch_hosts (const char *filename, GPtrArray *hosts)
     /* [previous][next][first][last][top][bottom][index][help]  */
 403 {
 404     FILE *file;
 405     char buffer[BUF_MEDIUM];
 406     char *bi;
 407 
 408     file = fopen (filename, "r");
 409     if (file == NULL)
 410         return;
 411 
 412     while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
 413     {
 414         // Skip to first character.
 415         for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
 416             ;
 417 
 418         // Ignore comments...
 419         if (bi[0] == '#')
 420             continue;
 421 
 422         // Handle $include.
 423         if (strncmp (bi, "$include ", 9) == 0)
 424         {
 425             char *includefile, *t;
 426 
 427             // Find start of filename.
 428             for (includefile = bi + 9; includefile[0] != '\0' && whitespace (includefile[0]);
 429                  includefile++)
 430                 ;
 431             t = includefile;
 432 
 433             // Find end of filename.
 434             for (; t[0] != '\0' && !str_isspace (t); str_next_char (&t))
 435                 ;
 436             *t = '\0';
 437 
 438             fetch_hosts (includefile, hosts);
 439             continue;
 440         }
 441 
 442         // Skip IP #s.
 443         for (; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
 444             ;
 445 
 446         // Get the host names separated by white space.
 447         while (bi[0] != '\0' && bi[0] != '#')
 448         {
 449             char *lc_start, *name;
 450 
 451             for (; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
 452                 ;
 453             if (bi[0] == '#')
 454                 continue;
 455 
 456             for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
 457                 ;
 458 
 459             if (bi == lc_start)
 460                 continue;
 461 
 462             name = g_strndup (lc_start, bi - lc_start);
 463             if (!g_ptr_array_find_with_equal_func (hosts, name, host_equal_func, NULL))
 464                 g_ptr_array_add (hosts, name);
 465             else
 466                 g_free (name);
 467         }
 468     }
 469 
 470     fclose (file);
 471 }
 472 
 473 /* --------------------------------------------------------------------------------------------- */
 474 
 475 static char *
 476 hostname_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 477 {
 478     static GPtrArray *hosts = NULL;
 479     static unsigned int host_p = 0;
 480     static size_t textstart = 0;
 481     static size_t textlen = 0;
 482 
 483     (void) flags;
 484     SHOW_C_CTX ("hostname_completion_function");
 485 
 486     if (state == 0)
 487     {  // Initialization stuff
 488         const char *p;
 489 
 490         if (hosts != NULL)
 491             g_ptr_array_free (hosts, TRUE);
 492         hosts = g_ptr_array_new_with_free_func (g_free);
 493         p = getenv ("HOSTFILE");
 494         fetch_hosts (p != NULL ? p : "/etc/hosts", hosts);
 495         host_p = 0;
 496         textstart = (*text == '@') ? 1 : 0;
 497         textlen = strlen (text + textstart);
 498     }
 499 
 500     for (; host_p < hosts->len; host_p++)
 501     {
 502         if (textlen == 0)
 503             break;  // Match all of them
 504         if (strncmp (text + textstart, g_ptr_array_index (hosts, host_p), textlen) == 0)
 505             break;
 506     }
 507 
 508     if (host_p == hosts->len)
 509     {
 510         g_ptr_array_free (hosts, TRUE);
 511         hosts = NULL;
 512         return NULL;
 513     }
 514 
 515     {
 516         GString *temp;
 517 
 518         temp = g_string_sized_new (8);
 519 
 520         if (textstart != 0)
 521             g_string_append_c (temp, '@');
 522         g_string_append (temp, g_ptr_array_index (hosts, host_p));
 523         host_p++;
 524 
 525         return g_string_free (temp, FALSE);
 526     }
 527 }
 528 
 529 /* --------------------------------------------------------------------------------------------- */
 530 /**
 531  * This is the function to call when the word to complete is in a position
 532  * where a command word can be found. It looks around $PATH, looking for
 533  * commands that match. It also scans aliases, function names, and the
 534  * table of shell built-ins.
 535  */
 536 
 537 static char *
 538 command_completion_function (const char *text, int state, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 539 {
 540     static const char *path_end = NULL;
 541     static gboolean isabsolute = FALSE;
 542     static int phase = 0;
 543     static size_t text_len = 0;
 544     static const char *const *words = NULL;
 545     static char *path = NULL;
 546     static char *cur_path = NULL;
 547     static char *cur_word = NULL;
 548     static int init_state = 0;
 549     static const char *const bash_reserved[] = {
 550         "if",     "then",  "else",  "elif", "fi",   "case", "esac",     "for",
 551         "select", "while", "until", "do",   "done", "in",   "function", 0,
 552     };
 553     static const char *const bash_builtins[] = {
 554         "alias",   "bg",      "bind",    "break",  "builtin", "cd",      "command", "continue",
 555         "declare", "dirs",    "echo",    "enable", "eval",    "exec",    "exit",    "export",
 556         "fc",      "fg",      "getopts", "hash",   "help",    "history", "jobs",    "kill",
 557         "let",     "local",   "logout",  "popd",   "pushd",   "pwd",     "read",    "readonly",
 558         "return",  "set",     "shift",   "source", "suspend", "test",    "times",   "trap",
 559         "type",    "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 == '>')
 828                 || (ti != text && 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 = completion_matches (state->word, command_completion_function,
 876                                       state->flags & (~INPUT_COMPLETE_FILENAMES));
 877     }
 878     else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
 879     {
 880         if (state->is_cd)
 881             state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
 882         SHOW_C_CTX ("try_complete:filename_subst_1");
 883         matches = completion_matches (state->word, filename_completion_function, state->flags);
 884 
 885         if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
 886         {
 887             state->q = text + *lc_start;
 888             for (state->p = text;
 889                  *state->p != '\0' && state->p < state->q && whitespace (*state->p);
 890                  str_next_char (&state->p))
 891                 ;
 892             if (strncmp (state->p, "cd", 2) == 0)
 893                 for (state->p += 2;
 894                      *state->p != '\0' && state->p < state->q && whitespace (*state->p);
 895                      str_next_char (&state->p))
 896                     ;
 897             if (state->p == state->q)
 898             {
 899                 char *cdpath_ref, *cdpath;
 900                 char c;
 901 
 902                 cdpath_ref = g_strdup (getenv ("CDPATH"));
 903                 cdpath = cdpath_ref;
 904                 c = (cdpath == NULL) ? '\0' : ':';
 905 
 906                 while (matches == NULL && c == ':')
 907                 {
 908                     char *s;
 909 
 910                     s = strchr (cdpath, ':');
 911                     // cppcheck-suppress nullPointer
 912                     if (s == NULL)
 913                         s = strchr (cdpath, '\0');
 914                     c = *s;
 915                     *s = '\0';
 916                     if (*cdpath != '\0')
 917                     {
 918                         state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
 919                         SHOW_C_CTX ("try_complete:filename_subst_2");
 920                         matches = completion_matches (state->r, filename_completion_function,
 921                                                       state->flags);
 922                         g_free (state->r);
 923                     }
 924                     *s = c;
 925                     cdpath = str_get_next_char (s);
 926                 }
 927                 g_free (cdpath_ref);
 928             }
 929         }
 930     }
 931     return matches;
 932 }
 933 
 934 /* --------------------------------------------------------------------------------------------- */
 935 
 936 static gboolean
 937 insert_text (WInput *in, const char *text, ssize_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 938 {
 939     size_t text_len;
 940     int buff_len;
 941     ssize_t new_size;
 942 
 943     text_len = strlen (text);
 944     buff_len = str_length (in->buffer->str);
 945     if (size < 0)
 946         size = (ssize_t) text_len;
 947     else
 948         size = MIN (size, (ssize_t) text_len);
 949 
 950     new_size = size + start - end;
 951     if (new_size != 0)
 952     {
 953         // make a hole within buffer
 954 
 955         size_t tail_len;
 956 
 957         tail_len = in->buffer->len - end;
 958         if (tail_len != 0)
 959         {
 960             char *tail;
 961             size_t hole_end;
 962 
 963             tail = g_strndup (in->buffer->str + end, tail_len);
 964 
 965             hole_end = end + new_size;
 966             if (in->buffer->len < hole_end)
 967                 g_string_set_size (in->buffer, hole_end + tail_len);
 968 
 969             g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
 970 
 971             g_free (tail);
 972         }
 973     }
 974 
 975     g_string_overwrite_len (in->buffer, start, text, size);
 976 
 977     in->point += str_length (in->buffer->str) - buff_len;
 978     input_update (in, TRUE);
 979     end += new_size;
 980 
 981     return new_size != 0;
 982 }
 983 
 984 /* --------------------------------------------------------------------------------------------- */
 985 
 986 static cb_ret_t
 987 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 988 {
 989     static int bl = 0;
 990 
 991     WGroup *g = GROUP (w);
 992     WDialog *h = DIALOG (w);
 993 
 994     switch (msg)
 995     {
 996     case MSG_KEY:
 997         switch (parm)
 998         {
 999         case KEY_LEFT:
1000         case KEY_RIGHT:
1001             bl = 0;
1002             h->ret_value = 0;
1003             dlg_close (h);
1004             return MSG_HANDLED;
1005 
1006         case KEY_BACKSPACE:
1007             bl = 0;
1008             // exit from completion list if input line is empty
1009             if (end == 0)
1010             {
1011                 h->ret_value = 0;
1012                 dlg_close (h);
1013             }
1014             // Refill the list box and start again
1015             else if (end == min_end)
1016             {
1017                 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1018                 input_handle_char (input, parm);
1019                 h->ret_value = B_USER;
1020                 dlg_close (h);
1021             }
1022             else
1023             {
1024                 int new_end;
1025                 int i;
1026                 GList *e;
1027 
1028                 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1029 
1030                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1031                      i++, e = g_list_next (e))
1032                 {
1033                     WLEntry *le = LENTRY (e->data);
1034 
1035                     if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1036                     {
1037                         listbox_set_current (LISTBOX (g->current->data), i);
1038                         end = new_end;
1039                         input_handle_char (input, parm);
1040                         widget_draw (WIDGET (g->current->data));
1041                         break;
1042                     }
1043                 }
1044             }
1045             return MSG_HANDLED;
1046 
1047         default:
1048             if (parm < 32 || parm > 255)
1049             {
1050                 bl = 0;
1051                 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1052                     return MSG_NOT_HANDLED;
1053 
1054                 if (end == min_end)
1055                     return MSG_HANDLED;
1056 
1057                 // This means we want to refill the list box and start again
1058                 h->ret_value = B_USER;
1059                 dlg_close (h);
1060             }
1061             else
1062             {
1063                 static char buff[MB_LEN_MAX] = "";
1064                 GList *e;
1065                 int i;
1066                 int need_redraw = 0;
1067                 int low = 4096;
1068                 char *last_text = NULL;
1069 
1070                 buff[bl++] = (char) parm;
1071                 buff[bl] = '\0';
1072 
1073                 switch (str_is_valid_char (buff, bl))
1074                 {
1075                 case -1:
1076                     bl = 0;
1077                     MC_FALLTHROUGH;
1078                 case -2:
1079                     return MSG_HANDLED;
1080                 default:
1081                     break;
1082                 }
1083 
1084                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1085                      i++, e = g_list_next (e))
1086                 {
1087                     WLEntry *le = LENTRY (e->data);
1088 
1089                     if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1090                         && strncmp (le->text + end - start, buff, bl) == 0)
1091                     {
1092                         if (need_redraw == 0)
1093                         {
1094                             need_redraw = 1;
1095                             listbox_set_current (LISTBOX (g->current->data), i);
1096                             last_text = le->text;
1097                         }
1098                         else
1099                         {
1100                             char *si, *sl;
1101                             int si_num = 0;
1102                             int sl_num = 0;
1103 
1104                             // count symbols between start and end
1105                             for (si = le->text + start; si < le->text + end;
1106                                  str_next_char (&si), si_num++)
1107                                 ;
1108                             for (sl = last_text + start; sl < last_text + end;
1109                                  str_next_char (&sl), sl_num++)
1110                                 ;
1111 
1112                             // pointers to next symbols
1113                             si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1114                             sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1115 
1116                             while (si[0] != '\0' && sl[0] != '\0')
1117                             {
1118                                 char *nexti, *nextl;
1119 
1120                                 nexti = str_get_next_char (si);
1121                                 nextl = str_get_next_char (sl);
1122 
1123                                 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1124                                     break;
1125 
1126                                 si = nexti;
1127                                 sl = nextl;
1128 
1129                                 si_num++;
1130                             }
1131 
1132                             last_text = le->text;
1133 
1134                             si = &last_text[str_offset_to_pos (last_text, si_num)];
1135                             if (low > si - last_text)
1136                                 low = si - last_text;
1137 
1138                             need_redraw = 2;
1139                         }
1140                     }
1141                 }
1142 
1143                 if (need_redraw == 2)
1144                 {
1145                     insert_text (input, last_text, low);
1146                     widget_draw (WIDGET (g->current->data));
1147                 }
1148                 else if (need_redraw == 1)
1149                 {
1150                     h->ret_value = B_ENTER;
1151                     dlg_close (h);
1152                 }
1153                 bl = 0;
1154             }
1155         }
1156         return MSG_HANDLED;
1157 
1158     default:
1159         return dlg_default_callback (w, sender, msg, parm, data);
1160     }
1161 }
1162 
1163 /* --------------------------------------------------------------------------------------------- */
1164 
1165 /** Returns TRUE if the user would like to see us again */
1166 static gboolean
1167 complete_engine (WInput *in, int what_to_do)
     /* [previous][next][first][last][top][bottom][index][help]  */
1168 {
1169     if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1170         input_complete_free (in);
1171 
1172     if (in->completions == NULL)
1173         complete_engine_fill_completions (in);
1174 
1175     if (in->completions == NULL)
1176         tty_beep ();
1177     else
1178     {
1179         if ((what_to_do & DO_INSERTION) != 0
1180             || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1181         {
1182             const char *lc_complete;
1183 
1184             lc_complete = g_ptr_array_index (in->completions, 0);
1185             if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1186                 tty_beep ();
1187             else
1188                 input_complete_free (in);
1189         }
1190 
1191         if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1192         {
1193             int maxlen = 0;
1194             int i;
1195             size_t k;
1196             int count;
1197             int x, y, w, h;
1198             int start_x, start_y;
1199             char *q;
1200             WDialog *complete_dlg;
1201             WListbox *complete_list;
1202 
1203             for (k = 1; k < in->completions->len; k++)
1204             {
1205                 q = g_ptr_array_index (in->completions, k);
1206                 i = str_term_width1 (q);
1207                 maxlen = MAX (maxlen, i);
1208             }
1209 
1210             count = in->completions->len - 1;
1211             start_x = WIDGET (in)->rect.x;
1212             start_y = WIDGET (in)->rect.y;
1213             if (start_y - 2 >= count)
1214             {
1215                 y = start_y - 2 - count;
1216                 h = 2 + count;
1217             }
1218             else if (start_y >= LINES - start_y - 1)
1219             {
1220                 y = 0;
1221                 h = start_y;
1222             }
1223             else
1224             {
1225                 y = start_y + 1;
1226                 h = LINES - start_y - 1;
1227             }
1228             x = start - in->term_first_shown - 2 + start_x;
1229             w = maxlen + 4;
1230             if (x + w > COLS)
1231                 x = COLS - w;
1232             if (x < 0)
1233                 x = 0;
1234             if (x + w > COLS)
1235                 w = COLS;
1236 
1237             input = in;
1238             min_end = end;
1239 
1240             complete_dlg = dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE, dialog_colors,
1241                                        complete_callback, NULL, "[Completion]", NULL);
1242             complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1243             group_add_widget (GROUP (complete_dlg), complete_list);
1244 
1245             for (k = 1; k < in->completions->len; k++)
1246             {
1247                 q = g_ptr_array_index (in->completions, k);
1248                 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1249             }
1250 
1251             i = dlg_run (complete_dlg);
1252             q = NULL;
1253             if (i == B_ENTER)
1254             {
1255                 listbox_get_current (complete_list, &q, NULL);
1256                 if (q != NULL)
1257                     insert_text (in, q, -1);
1258             }
1259             if (q != NULL || end != min_end)
1260                 input_complete_free (in);
1261             widget_destroy (WIDGET (complete_dlg));
1262 
1263             // B_USER if user wants to start over again
1264             return (i == B_USER);
1265         }
1266     }
1267 
1268     return FALSE;
1269 }
1270 
1271 /* --------------------------------------------------------------------------------------------- */
1272 /*** public functions ****************************************************************************/
1273 /* --------------------------------------------------------------------------------------------- */
1274 
1275 /** Returns an array of matches, or NULL if none. */
1276 GPtrArray *
1277 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
1278 {
1279     try_complete_automation_state_t state;
1280     GPtrArray *matches = NULL;
1281 
1282     memset (&state, 0, sizeof (state));
1283     state.flags = flags;
1284 
1285     SHOW_C_CTX ("try_complete");
1286     state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1287 
1288     state.is_cd = check_is_cd (text, *lc_start, state.flags);
1289 
1290     /* Determine if this could be a command word. It is if it appears at
1291        the start of the line (ignoring preceding whitespace), or if it
1292        appears after a character that separates commands. And we have to
1293        be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1294     if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1295         try_complete_commands_prepare (&state, text, lc_start);
1296 
1297     try_complete_find_start_sign (&state);
1298 
1299     // Command substitution?
1300     if (state.p > state.q && state.p > state.r)
1301     {
1302         SHOW_C_CTX ("try_complete:cmd_backq_subst");
1303         matches = completion_matches (str_cget_next_char (state.p), command_completion_function,
1304                                       state.flags & (~INPUT_COMPLETE_FILENAMES));
1305         if (matches != NULL)
1306             *lc_start += str_get_next_char (state.p) - state.word;
1307     }
1308 
1309     // Variable name?
1310     else if (state.q > state.p && state.q > state.r)
1311     {
1312         SHOW_C_CTX ("try_complete:var_subst");
1313         matches = completion_matches (state.q, variable_completion_function, state.flags);
1314         if (matches != NULL)
1315             *lc_start += state.q - state.word;
1316     }
1317 
1318     /* Starts with '@', then look through the known hostnames for
1319        completion first. */
1320     else if (state.r > state.p && state.r > state.q)
1321     {
1322         SHOW_C_CTX ("try_complete:host_subst");
1323         matches = completion_matches (state.r, hostname_completion_function, state.flags);
1324         if (matches != NULL)
1325             *lc_start += state.r - state.word;
1326     }
1327 
1328     /* Starts with '~' and there is no slash in the word, then
1329        try completing this word as a username. */
1330     if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1331         && strchr (state.word, PATH_SEP) == NULL)
1332     {
1333         SHOW_C_CTX ("try_complete:user_subst");
1334         matches = completion_matches (state.word, username_completion_function, state.flags);
1335     }
1336 
1337     /* If this word is in a command position, then
1338        complete over possible command names, including aliases, functions,
1339        and command names. */
1340     if (matches == NULL)
1341         matches = try_complete_all_possible (&state, text, lc_start);
1342 
1343     // And finally if nothing found, try complete directory name
1344     if (matches == NULL)
1345     {
1346         state.in_command_position = 0;
1347         matches = try_complete_all_possible (&state, text, lc_start);
1348     }
1349 
1350     g_free (state.word);
1351 
1352     if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0
1353         && (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1354     {
1355         // FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line.
1356         size_t i;
1357 
1358         for (i = 0; i < matches->len; i++)
1359         {
1360             char *p;
1361 
1362             p = g_ptr_array_index (matches, i);
1363             /* Escape only '?', '*', and '&' symbols as described in the
1364                manual page (see a11995e12b88285e044f644904c306ed6c342ad0). */
1365             g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1366             g_free (p);
1367         }
1368     }
1369 
1370     return matches;
1371 }
1372 
1373 /* --------------------------------------------------------------------------------------------- */
1374 
1375 void
1376 complete_engine_fill_completions (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1377 {
1378     char *s;
1379     const char *word_separators;
1380 
1381     word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1382 
1383     end = str_offset_to_pos (in->buffer->str, in->point);
1384 
1385     s = in->buffer->str;
1386     if (in->point != 0)
1387     {
1388         // get symbol before in->point
1389         size_t i;
1390 
1391         for (i = in->point - 1; i > 0; i--)
1392             str_next_char (&s);
1393     }
1394 
1395     for (; s >= in->buffer->str; str_prev_char (&s))
1396     {
1397         start = s - in->buffer->str;
1398         if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1399             break;
1400     }
1401 
1402     if (start < end)
1403     {
1404         str_next_char (&s);
1405         start = s - in->buffer->str;
1406     }
1407 
1408     in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1409 }
1410 
1411 /* --------------------------------------------------------------------------------------------- */
1412 
1413 /* declared in lib/widget/input.h */
1414 void
1415 input_complete (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1416 {
1417     int engine_flags;
1418 
1419     if (!str_is_valid_string (in->buffer->str))
1420         return;
1421 
1422     if (in->completions != NULL)
1423         engine_flags = DO_QUERY;
1424     else
1425     {
1426         engine_flags = DO_INSERTION;
1427 
1428         if (mc_global.widget.show_all_if_ambiguous)
1429             engine_flags |= DO_QUERY;
1430     }
1431 
1432     while (complete_engine (in, engine_flags))
1433         ;
1434 }
1435 
1436 /* --------------------------------------------------------------------------------------------- */
1437 
1438 void
1439 input_complete_free (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1440 {
1441     if (in->completions != NULL)
1442     {
1443         g_ptr_array_free (in->completions, TRUE);
1444         in->completions = NULL;
1445     }
1446 }
1447 
1448 /* --------------------------------------------------------------------------------------------- */

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