Manual pages: mcmcdiffmceditmcview

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                     if (s == NULL)
 912                         s = strchr (cdpath, '\0');
 913                     c = *s;
 914                     *s = '\0';
 915                     if (*cdpath != '\0')
 916                     {
 917                         state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
 918                         SHOW_C_CTX ("try_complete:filename_subst_2");
 919                         matches = completion_matches (state->r, filename_completion_function,
 920                                                       state->flags);
 921                         g_free (state->r);
 922                     }
 923                     *s = c;
 924                     cdpath = str_get_next_char (s);
 925                 }
 926                 g_free (cdpath_ref);
 927             }
 928         }
 929     }
 930     return matches;
 931 }
 932 
 933 /* --------------------------------------------------------------------------------------------- */
 934 
 935 static gboolean
 936 insert_text (WInput *in, const char *text, ssize_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 937 {
 938     size_t text_len;
 939     int buff_len;
 940     ssize_t new_size;
 941 
 942     text_len = strlen (text);
 943     buff_len = str_length (in->buffer->str);
 944     if (size < 0)
 945         size = (ssize_t) text_len;
 946     else
 947         size = MIN (size, (ssize_t) text_len);
 948 
 949     new_size = size + start - end;
 950     if (new_size != 0)
 951     {
 952         // make a hole within buffer
 953 
 954         size_t tail_len;
 955 
 956         tail_len = in->buffer->len - end;
 957         if (tail_len != 0)
 958         {
 959             char *tail;
 960             size_t hole_end;
 961 
 962             tail = g_strndup (in->buffer->str + end, tail_len);
 963 
 964             hole_end = end + new_size;
 965             if (in->buffer->len < hole_end)
 966                 g_string_set_size (in->buffer, hole_end + tail_len);
 967 
 968             g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
 969 
 970             g_free (tail);
 971         }
 972     }
 973 
 974     g_string_overwrite_len (in->buffer, start, text, size);
 975 
 976     in->point += str_length (in->buffer->str) - buff_len;
 977     input_update (in, TRUE);
 978     end += new_size;
 979 
 980     return new_size != 0;
 981 }
 982 
 983 /* --------------------------------------------------------------------------------------------- */
 984 
 985 static cb_ret_t
 986 complete_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 987 {
 988     static int bl = 0;
 989 
 990     WGroup *g = GROUP (w);
 991     WDialog *h = DIALOG (w);
 992 
 993     switch (msg)
 994     {
 995     case MSG_KEY:
 996         switch (parm)
 997         {
 998         case KEY_LEFT:
 999         case KEY_RIGHT:
1000             bl = 0;
1001             h->ret_value = 0;
1002             dlg_close (h);
1003             return MSG_HANDLED;
1004 
1005         case KEY_BACKSPACE:
1006             bl = 0;
1007             // exit from completion list if input line is empty
1008             if (end == 0)
1009             {
1010                 h->ret_value = 0;
1011                 dlg_close (h);
1012             }
1013             // Refill the list box and start again
1014             else if (end == min_end)
1015             {
1016                 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1017                 input_handle_char (input, parm);
1018                 h->ret_value = B_USER;
1019                 dlg_close (h);
1020             }
1021             else
1022             {
1023                 int new_end;
1024                 int i;
1025                 GList *e;
1026 
1027                 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1028 
1029                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1030                      i++, e = g_list_next (e))
1031                 {
1032                     WLEntry *le = LENTRY (e->data);
1033 
1034                     if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1035                     {
1036                         listbox_set_current (LISTBOX (g->current->data), i);
1037                         end = new_end;
1038                         input_handle_char (input, parm);
1039                         widget_draw (WIDGET (g->current->data));
1040                         break;
1041                     }
1042                 }
1043             }
1044             return MSG_HANDLED;
1045 
1046         default:
1047             if (parm < 32 || parm > 255)
1048             {
1049                 bl = 0;
1050                 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1051                     return MSG_NOT_HANDLED;
1052 
1053                 if (end == min_end)
1054                     return MSG_HANDLED;
1055 
1056                 // This means we want to refill the list box and start again
1057                 h->ret_value = B_USER;
1058                 dlg_close (h);
1059             }
1060             else
1061             {
1062                 static char buff[MB_LEN_MAX] = "";
1063                 GList *e;
1064                 int i;
1065                 int need_redraw = 0;
1066                 int low = 4096;
1067                 char *last_text = NULL;
1068 
1069                 buff[bl++] = (char) parm;
1070                 buff[bl] = '\0';
1071 
1072                 switch (str_is_valid_char (buff, bl))
1073                 {
1074                 case -1:
1075                     bl = 0;
1076                     MC_FALLTHROUGH;
1077                 case -2:
1078                     return MSG_HANDLED;
1079                 default:
1080                     break;
1081                 }
1082 
1083                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data)); e != NULL;
1084                      i++, e = g_list_next (e))
1085                 {
1086                     WLEntry *le = LENTRY (e->data);
1087 
1088                     if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1089                         && strncmp (le->text + end - start, buff, bl) == 0)
1090                     {
1091                         if (need_redraw == 0)
1092                         {
1093                             need_redraw = 1;
1094                             listbox_set_current (LISTBOX (g->current->data), i);
1095                             last_text = le->text;
1096                         }
1097                         else
1098                         {
1099                             char *si, *sl;
1100                             int si_num = 0;
1101                             int sl_num = 0;
1102 
1103                             // count symbols between start and end
1104                             for (si = le->text + start; si < le->text + end;
1105                                  str_next_char (&si), si_num++)
1106                                 ;
1107                             for (sl = last_text + start; sl < last_text + end;
1108                                  str_next_char (&sl), sl_num++)
1109                                 ;
1110 
1111                             // pointers to next symbols
1112                             si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1113                             sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1114 
1115                             while (si[0] != '\0' && sl[0] != '\0')
1116                             {
1117                                 char *nexti, *nextl;
1118 
1119                                 nexti = str_get_next_char (si);
1120                                 nextl = str_get_next_char (sl);
1121 
1122                                 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1123                                     break;
1124 
1125                                 si = nexti;
1126                                 sl = nextl;
1127 
1128                                 si_num++;
1129                             }
1130 
1131                             last_text = le->text;
1132 
1133                             si = &last_text[str_offset_to_pos (last_text, si_num)];
1134                             if (low > si - last_text)
1135                                 low = si - last_text;
1136 
1137                             need_redraw = 2;
1138                         }
1139                     }
1140                 }
1141 
1142                 if (need_redraw == 2)
1143                 {
1144                     insert_text (input, last_text, low);
1145                     widget_draw (WIDGET (g->current->data));
1146                 }
1147                 else if (need_redraw == 1)
1148                 {
1149                     h->ret_value = B_ENTER;
1150                     dlg_close (h);
1151                 }
1152                 bl = 0;
1153             }
1154         }
1155         return MSG_HANDLED;
1156 
1157     default:
1158         return dlg_default_callback (w, sender, msg, parm, data);
1159     }
1160 }
1161 
1162 /* --------------------------------------------------------------------------------------------- */
1163 
1164 /** Returns TRUE if the user would like to see us again */
1165 static gboolean
1166 complete_engine (WInput *in, int what_to_do)
     /* [previous][next][first][last][top][bottom][index][help]  */
1167 {
1168     if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1169         input_complete_free (in);
1170 
1171     if (in->completions == NULL)
1172         complete_engine_fill_completions (in);
1173 
1174     if (in->completions == NULL)
1175         tty_beep ();
1176     else
1177     {
1178         if ((what_to_do & DO_INSERTION) != 0
1179             || ((what_to_do & DO_QUERY) != 0 && in->completions->len == 1))
1180         {
1181             const char *lc_complete;
1182 
1183             lc_complete = g_ptr_array_index (in->completions, 0);
1184             if (!insert_text (in, lc_complete, -1) || in->completions->len > 1)
1185                 tty_beep ();
1186             else
1187                 input_complete_free (in);
1188         }
1189 
1190         if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions->len > 1)
1191         {
1192             int maxlen = 0;
1193             int i;
1194             size_t k;
1195             int count;
1196             int x, y, w, h;
1197             int start_x, start_y;
1198             char *q;
1199             WDialog *complete_dlg;
1200             WListbox *complete_list;
1201 
1202             for (k = 1; k < in->completions->len; k++)
1203             {
1204                 q = g_ptr_array_index (in->completions, k);
1205                 i = str_term_width1 (q);
1206                 maxlen = MAX (maxlen, i);
1207             }
1208 
1209             count = in->completions->len - 1;
1210             start_x = WIDGET (in)->rect.x;
1211             start_y = WIDGET (in)->rect.y;
1212             if (start_y - 2 >= count)
1213             {
1214                 y = start_y - 2 - count;
1215                 h = 2 + count;
1216             }
1217             else if (start_y >= LINES - start_y - 1)
1218             {
1219                 y = 0;
1220                 h = start_y;
1221             }
1222             else
1223             {
1224                 y = start_y + 1;
1225                 h = LINES - start_y - 1;
1226             }
1227             x = start - in->term_first_shown - 2 + start_x;
1228             w = maxlen + 4;
1229             if (x + w > COLS)
1230                 x = COLS - w;
1231             if (x < 0)
1232                 x = 0;
1233             if (x + w > COLS)
1234                 w = COLS;
1235 
1236             input = in;
1237             min_end = end;
1238 
1239             complete_dlg = dlg_create (TRUE, y, x, h, w, WPOS_KEEP_DEFAULT, TRUE, dialog_colors,
1240                                        complete_callback, NULL, "[Completion]", NULL);
1241             complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1242             group_add_widget (GROUP (complete_dlg), complete_list);
1243 
1244             for (k = 1; k < in->completions->len; k++)
1245             {
1246                 q = g_ptr_array_index (in->completions, k);
1247                 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, q, NULL, FALSE);
1248             }
1249 
1250             i = dlg_run (complete_dlg);
1251             q = NULL;
1252             if (i == B_ENTER)
1253             {
1254                 listbox_get_current (complete_list, &q, NULL);
1255                 if (q != NULL)
1256                     insert_text (in, q, -1);
1257             }
1258             if (q != NULL || end != min_end)
1259                 input_complete_free (in);
1260             widget_destroy (WIDGET (complete_dlg));
1261 
1262             // B_USER if user wants to start over again
1263             return (i == B_USER);
1264         }
1265     }
1266 
1267     return FALSE;
1268 }
1269 
1270 /* --------------------------------------------------------------------------------------------- */
1271 /*** public functions ****************************************************************************/
1272 /* --------------------------------------------------------------------------------------------- */
1273 
1274 /** Returns an array of matches, or NULL if none. */
1275 GPtrArray *
1276 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
1277 {
1278     try_complete_automation_state_t state;
1279     GPtrArray *matches = NULL;
1280 
1281     memset (&state, 0, sizeof (state));
1282     state.flags = flags;
1283 
1284     SHOW_C_CTX ("try_complete");
1285     state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1286 
1287     state.is_cd = check_is_cd (text, *lc_start, state.flags);
1288 
1289     /* Determine if this could be a command word. It is if it appears at
1290        the start of the line (ignoring preceding whitespace), or if it
1291        appears after a character that separates commands. And we have to
1292        be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1293     if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1294         try_complete_commands_prepare (&state, text, lc_start);
1295 
1296     try_complete_find_start_sign (&state);
1297 
1298     // Command substitution?
1299     if (state.p > state.q && state.p > state.r)
1300     {
1301         SHOW_C_CTX ("try_complete:cmd_backq_subst");
1302         matches = completion_matches (str_cget_next_char (state.p), command_completion_function,
1303                                       state.flags & (~INPUT_COMPLETE_FILENAMES));
1304         if (matches != NULL)
1305             *lc_start += str_get_next_char (state.p) - state.word;
1306     }
1307 
1308     // Variable name?
1309     else if (state.q > state.p && state.q > state.r)
1310     {
1311         SHOW_C_CTX ("try_complete:var_subst");
1312         matches = completion_matches (state.q, variable_completion_function, state.flags);
1313         if (matches != NULL)
1314             *lc_start += state.q - state.word;
1315     }
1316 
1317     /* Starts with '@', then look through the known hostnames for
1318        completion first. */
1319     else if (state.r > state.p && state.r > state.q)
1320     {
1321         SHOW_C_CTX ("try_complete:host_subst");
1322         matches = completion_matches (state.r, hostname_completion_function, state.flags);
1323         if (matches != NULL)
1324             *lc_start += state.r - state.word;
1325     }
1326 
1327     /* Starts with '~' and there is no slash in the word, then
1328        try completing this word as a username. */
1329     if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1330         && strchr (state.word, PATH_SEP) == NULL)
1331     {
1332         SHOW_C_CTX ("try_complete:user_subst");
1333         matches = completion_matches (state.word, username_completion_function, state.flags);
1334     }
1335 
1336     /* If this word is in a command position, then
1337        complete over possible command names, including aliases, functions,
1338        and command names. */
1339     if (matches == NULL)
1340         matches = try_complete_all_possible (&state, text, lc_start);
1341 
1342     // And finally if nothing found, try complete directory name
1343     if (matches == NULL)
1344     {
1345         state.in_command_position = 0;
1346         matches = try_complete_all_possible (&state, text, lc_start);
1347     }
1348 
1349     g_free (state.word);
1350 
1351     if (matches != NULL && (flags & INPUT_COMPLETE_FILENAMES) != 0
1352         && (flags & INPUT_COMPLETE_SHELL_ESC) == 0)
1353     {
1354         // FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line.
1355         size_t i;
1356 
1357         for (i = 0; i < matches->len; i++)
1358         {
1359             char *p;
1360 
1361             p = g_ptr_array_index (matches, i);
1362             /* Escape only '?', '*', and '&' symbols as described in the
1363                manual page (see a11995e12b88285e044f644904c306ed6c342ad0). */
1364             g_ptr_array_index (matches, i) = str_escape (p, -1, "?*&", TRUE);
1365             g_free (p);
1366         }
1367     }
1368 
1369     return matches;
1370 }
1371 
1372 /* --------------------------------------------------------------------------------------------- */
1373 
1374 void
1375 complete_engine_fill_completions (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1376 {
1377     char *s;
1378     const char *word_separators;
1379 
1380     word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1381 
1382     end = str_offset_to_pos (in->buffer->str, in->point);
1383 
1384     s = in->buffer->str;
1385     if (in->point != 0)
1386     {
1387         // get symbol before in->point
1388         size_t i;
1389 
1390         for (i = in->point - 1; i > 0; i--)
1391             str_next_char (&s);
1392     }
1393 
1394     for (; s >= in->buffer->str; str_prev_char (&s))
1395     {
1396         start = s - in->buffer->str;
1397         if (strchr (word_separators, *s) != NULL && !str_is_char_escaped (in->buffer->str, s))
1398             break;
1399     }
1400 
1401     if (start < end)
1402     {
1403         str_next_char (&s);
1404         start = s - in->buffer->str;
1405     }
1406 
1407     in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1408 }
1409 
1410 /* --------------------------------------------------------------------------------------------- */
1411 
1412 /* declared in lib/widget/input.h */
1413 void
1414 input_complete (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1415 {
1416     int engine_flags;
1417 
1418     if (!str_is_valid_string (in->buffer->str))
1419         return;
1420 
1421     if (in->completions != NULL)
1422         engine_flags = DO_QUERY;
1423     else
1424     {
1425         engine_flags = DO_INSERTION;
1426 
1427         if (mc_global.widget.show_all_if_ambiguous)
1428             engine_flags |= DO_QUERY;
1429     }
1430 
1431     while (complete_engine (in, engine_flags))
1432         ;
1433 }
1434 
1435 /* --------------------------------------------------------------------------------------------- */
1436 
1437 void
1438 input_complete_free (WInput *in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1439 {
1440     if (in->completions != NULL)
1441     {
1442         g_ptr_array_free (in->completions, TRUE);
1443         in->completions = NULL;
1444     }
1445 }
1446 
1447 /* --------------------------------------------------------------------------------------------- */

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