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. fetch_hosts
  6. hostname_completion_function
  7. command_completion_function
  8. match_compare
  9. completion_matches
  10. check_is_cd
  11. try_complete_commands_prepare
  12. try_complete_find_start_sign
  13. try_complete_all_possible
  14. insert_text
  15. complete_callback
  16. complete_engine
  17. try_complete
  18. complete_engine_fill_completions
  19. input_complete
  20. input_complete_free

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

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