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

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