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-2022
   6    Free Software Foundation, Inc.
   7 
   8    Written by:
   9    Jakub Jelinek, 1995
  10    Slava Zanko <slavazanko@gmail.com>, 2013
  11    Andrew Borodin <aborodin@vmail.ru>, 2013-2022
  12 
  13    This file is part of the Midnight Commander.
  14 
  15    The Midnight Commander is free software: you can redistribute it
  16    and/or modify it under the terms of the GNU General Public License as
  17    published by the Free Software Foundation, either version 3 of the License,
  18    or (at your option) any later version.
  19 
  20    The Midnight Commander is distributed in the hope that it will be useful,
  21    but WITHOUT ANY WARRANTY; without even the implied warranty of
  22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23    GNU General Public License for more details.
  24 
  25    You should have received a copy of the GNU General Public License
  26    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  27  */
  28 
  29 /** \file lib/widget/input_complete.c
  30  *  \brief Source: Input line filename/username/hostname/variable/command completion
  31  */
  32 
  33 #include <config.h>
  34 
  35 #include <ctype.h>
  36 #include <limits.h>             /* MB_LEN_MAX */
  37 #include <stdio.h>
  38 #include <stdlib.h>
  39 #include <string.h>
  40 #include <dirent.h>
  41 #include <sys/types.h>
  42 #include <sys/stat.h>
  43 #include <pwd.h>
  44 #include <unistd.h>
  45 
  46 #include "lib/global.h"
  47 
  48 #include "lib/tty/tty.h"
  49 #include "lib/tty/key.h"        /* XCTRL and ALT macros */
  50 #include "lib/vfs/vfs.h"
  51 #include "lib/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 vfs_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, TRUE);
 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, TRUE);
 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, TRUE);
 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     ssize_t new_size;
 984 
 985     text_len = strlen (text);
 986     buff_len = str_length (in->buffer->str);
 987     if (size < 0)
 988         size = (ssize_t) text_len;
 989     else
 990         size = MIN (size, (ssize_t) text_len);
 991 
 992     new_size = size + start - end;
 993     if (new_size != 0)
 994     {
 995         /* make a hole within buffer */
 996 
 997         size_t tail_len;
 998 
 999         tail_len = in->buffer->len - end;
1000         if (tail_len != 0)
1001         {
1002             char *tail;
1003             size_t hole_end;
1004 
1005             tail = g_strndup (in->buffer->str + end, tail_len);
1006 
1007             hole_end = end + new_size;
1008             if (in->buffer->len < hole_end)
1009                 g_string_set_size (in->buffer, hole_end + tail_len);
1010 
1011             g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
1012 
1013             g_free (tail);
1014         }
1015     }
1016 
1017     g_string_overwrite_len (in->buffer, start, text, size);
1018 
1019     in->point += str_length (in->buffer->str) - buff_len;
1020     input_update (in, TRUE);
1021     end += new_size;
1022 
1023     return new_size != 0;
1024 }
1025 
1026 /* --------------------------------------------------------------------------------------------- */
1027 
1028 static cb_ret_t
1029 complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1030 {
1031     static int bl = 0;
1032 
1033     WGroup *g = GROUP (w);
1034     WDialog *h = DIALOG (w);
1035 
1036     switch (msg)
1037     {
1038     case MSG_KEY:
1039         switch (parm)
1040         {
1041         case KEY_LEFT:
1042         case KEY_RIGHT:
1043             bl = 0;
1044             h->ret_value = 0;
1045             dlg_stop (h);
1046             return MSG_HANDLED;
1047 
1048         case KEY_BACKSPACE:
1049             bl = 0;
1050             /* exit from completion list if input line is empty */
1051             if (end == 0)
1052             {
1053                 h->ret_value = 0;
1054                 dlg_stop (h);
1055             }
1056             /* Refill the list box and start again */
1057             else if (end == min_end)
1058             {
1059                 end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1060                 input_handle_char (input, parm);
1061                 h->ret_value = B_USER;
1062                 dlg_stop (h);
1063             }
1064             else
1065             {
1066                 int new_end;
1067                 int i;
1068                 GList *e;
1069 
1070                 new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
1071 
1072                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1073                      e != NULL; i++, e = g_list_next (e))
1074                 {
1075                     WLEntry *le = LENTRY (e->data);
1076 
1077                     if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
1078                     {
1079                         listbox_select_entry (LISTBOX (g->current->data), i);
1080                         end = new_end;
1081                         input_handle_char (input, parm);
1082                         widget_draw (WIDGET (g->current->data));
1083                         break;
1084                     }
1085                 }
1086             }
1087             return MSG_HANDLED;
1088 
1089         default:
1090             if (parm < 32 || parm > 255)
1091             {
1092                 bl = 0;
1093                 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1094                     return MSG_NOT_HANDLED;
1095 
1096                 if (end == min_end)
1097                     return MSG_HANDLED;
1098 
1099                 /* This means we want to refill the list box and start again */
1100                 h->ret_value = B_USER;
1101                 dlg_stop (h);
1102             }
1103             else
1104             {
1105                 static char buff[MB_LEN_MAX] = "";
1106                 GList *e;
1107                 int i;
1108                 int need_redraw = 0;
1109                 int low = 4096;
1110                 char *last_text = NULL;
1111 
1112                 buff[bl++] = (char) parm;
1113                 buff[bl] = '\0';
1114 
1115                 switch (str_is_valid_char (buff, bl))
1116                 {
1117                 case -1:
1118                     bl = 0;
1119                     MC_FALLTHROUGH;
1120                 case -2:
1121                     return MSG_HANDLED;
1122                 default:
1123                     break;
1124                 }
1125 
1126                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1127                      e != NULL; i++, e = g_list_next (e))
1128                 {
1129                     WLEntry *le = LENTRY (e->data);
1130 
1131                     if (strncmp (input->buffer->str + start, le->text, end - start) == 0
1132                         && strncmp (le->text + end - start, buff, bl) == 0)
1133                     {
1134                         if (need_redraw == 0)
1135                         {
1136                             need_redraw = 1;
1137                             listbox_select_entry (LISTBOX (g->current->data), i);
1138                             last_text = le->text;
1139                         }
1140                         else
1141                         {
1142                             char *si, *sl;
1143                             int si_num = 0;
1144                             int sl_num = 0;
1145 
1146                             /* count symbols between start and end */
1147                             for (si = le->text + start; si < le->text + end;
1148                                  str_next_char (&si), si_num++)
1149                                 ;
1150                             for (sl = last_text + start; sl < last_text + end;
1151                                  str_next_char (&sl), sl_num++)
1152                                 ;
1153 
1154                             /* pointers to next symbols */
1155                             si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1156                             sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1157 
1158                             while (si[0] != '\0' && sl[0] != '\0')
1159                             {
1160                                 char *nexti, *nextl;
1161 
1162                                 nexti = str_get_next_char (si);
1163                                 nextl = str_get_next_char (sl);
1164 
1165                                 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1166                                     break;
1167 
1168                                 si = nexti;
1169                                 sl = nextl;
1170 
1171                                 si_num++;
1172                             }
1173 
1174                             last_text = le->text;
1175 
1176                             si = &last_text[str_offset_to_pos (last_text, si_num)];
1177                             if (low > si - last_text)
1178                                 low = si - last_text;
1179 
1180                             need_redraw = 2;
1181                         }
1182                     }
1183                 }
1184 
1185                 if (need_redraw == 2)
1186                 {
1187                     insert_text (input, last_text, low);
1188                     widget_draw (WIDGET (g->current->data));
1189                 }
1190                 else if (need_redraw == 1)
1191                 {
1192                     h->ret_value = B_ENTER;
1193                     dlg_stop (h);
1194                 }
1195                 bl = 0;
1196             }
1197         }
1198         return MSG_HANDLED;
1199 
1200     default:
1201         return dlg_default_callback (w, sender, msg, parm, data);
1202     }
1203 }
1204 
1205 /* --------------------------------------------------------------------------------------------- */
1206 
1207 /** Returns TRUE if the user would like to see us again */
1208 static gboolean
1209 complete_engine (WInput * in, int what_to_do)
     /* [previous][next][first][last][top][bottom][index][help]  */
1210 {
1211     if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
1212         input_complete_free (in);
1213 
1214     if (in->completions == NULL)
1215         complete_engine_fill_completions (in);
1216 
1217     if (in->completions == NULL)
1218         tty_beep ();
1219     else
1220     {
1221         if ((what_to_do & DO_INSERTION) != 0
1222             || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
1223         {
1224             char *lc_complete = in->completions[0];
1225 
1226             if (!insert_text (in, lc_complete, -1) || in->completions[1] != NULL)
1227                 tty_beep ();
1228             else
1229                 input_complete_free (in);
1230         }
1231 
1232         if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
1233         {
1234             int maxlen = 0, count = 0, i;
1235             int x, y, w, h;
1236             int start_x, start_y;
1237             char **p, *q;
1238             WDialog *complete_dlg;
1239             WListbox *complete_list;
1240 
1241             for (p = in->completions + 1; *p != NULL; count++, p++)
1242             {
1243                 i = str_term_width1 (*p);
1244                 if (i > maxlen)
1245                     maxlen = i;
1246             }
1247 
1248             start_x = WIDGET (in)->rect.x;
1249             start_y = WIDGET (in)->rect.y;
1250             if (start_y - 2 >= count)
1251             {
1252                 y = start_y - 2 - count;
1253                 h = 2 + count;
1254             }
1255             else if (start_y >= LINES - start_y - 1)
1256             {
1257                 y = 0;
1258                 h = start_y;
1259             }
1260             else
1261             {
1262                 y = start_y + 1;
1263                 h = LINES - start_y - 1;
1264             }
1265             x = start - in->term_first_shown - 2 + start_x;
1266             w = maxlen + 4;
1267             if (x + w > COLS)
1268                 x = COLS - w;
1269             if (x < 0)
1270                 x = 0;
1271             if (x + w > COLS)
1272                 w = COLS;
1273 
1274             input = in;
1275             min_end = end;
1276             complete_height = h;
1277             complete_width = w;
1278 
1279             complete_dlg =
1280                 dlg_create (TRUE, y, x, complete_height, complete_width, WPOS_KEEP_DEFAULT, TRUE,
1281                             dialog_colors, complete_callback, NULL, "[Completion]", NULL);
1282             complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1283             group_add_widget (GROUP (complete_dlg), complete_list);
1284 
1285             for (p = in->completions + 1; *p != NULL; p++)
1286                 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
1287 
1288             i = dlg_run (complete_dlg);
1289             q = NULL;
1290             if (i == B_ENTER)
1291             {
1292                 listbox_get_current (complete_list, &q, NULL);
1293                 if (q != NULL)
1294                     insert_text (in, q, -1);
1295             }
1296             if (q != NULL || end != min_end)
1297                 input_complete_free (in);
1298             widget_destroy (WIDGET (complete_dlg));
1299 
1300             /* B_USER if user wants to start over again */
1301             return (i == B_USER);
1302         }
1303     }
1304 
1305     return FALSE;
1306 }
1307 
1308 /* --------------------------------------------------------------------------------------------- */
1309 /*** public functions ****************************************************************************/
1310 /* --------------------------------------------------------------------------------------------- */
1311 
1312 /** Returns an array of matches, or NULL if none. */
1313 char **
1314 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
1315 {
1316     try_complete_automation_state_t state;
1317     char **matches = NULL;
1318 
1319     memset (&state, 0, sizeof (state));
1320     state.flags = flags;
1321 
1322     SHOW_C_CTX ("try_complete");
1323     state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1324 
1325     state.is_cd = check_is_cd (text, *lc_start, state.flags);
1326 
1327     /* Determine if this could be a command word. It is if it appears at
1328        the start of the line (ignoring preceding whitespace), or if it
1329        appears after a character that separates commands. And we have to
1330        be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1331     if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1332         try_complete_commands_prepare (&state, text, lc_start);
1333 
1334     try_complete_find_start_sign (&state);
1335 
1336     /* Command substitution? */
1337     if (state.p > state.q && state.p > state.r)
1338     {
1339         SHOW_C_CTX ("try_complete:cmd_backq_subst");
1340         matches = completion_matches (str_cget_next_char (state.p),
1341                                       command_completion_function,
1342                                       state.flags & (~INPUT_COMPLETE_FILENAMES));
1343         if (matches != NULL)
1344             *lc_start += str_get_next_char (state.p) - state.word;
1345     }
1346 
1347     /* Variable name? */
1348     else if (state.q > state.p && state.q > state.r)
1349     {
1350         SHOW_C_CTX ("try_complete:var_subst");
1351         matches = completion_matches (state.q, variable_completion_function, state.flags);
1352         if (matches != NULL)
1353             *lc_start += state.q - state.word;
1354     }
1355 
1356     /* Starts with '@', then look through the known hostnames for 
1357        completion first. */
1358     else if (state.r > state.p && state.r > state.q)
1359     {
1360         SHOW_C_CTX ("try_complete:host_subst");
1361         matches = completion_matches (state.r, hostname_completion_function, state.flags);
1362         if (matches != NULL)
1363             *lc_start += state.r - state.word;
1364     }
1365 
1366     /* Starts with '~' and there is no slash in the word, then
1367        try completing this word as a username. */
1368     if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1369         && strchr (state.word, PATH_SEP) == NULL)
1370     {
1371         SHOW_C_CTX ("try_complete:user_subst");
1372         matches = completion_matches (state.word, username_completion_function, state.flags);
1373     }
1374 
1375     /* If this word is in a command position, then
1376        complete over possible command names, including aliases, functions,
1377        and command names. */
1378     if (matches == NULL)
1379         matches = try_complete_all_possible (&state, text, lc_start);
1380 
1381     /* And finally if nothing found, try complete directory name */
1382     if (matches == NULL)
1383     {
1384         state.in_command_position = 0;
1385         matches = try_complete_all_possible (&state, text, lc_start);
1386     }
1387 
1388     g_free (state.word);
1389 
1390     if (matches != NULL &&
1391         (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
1392         (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
1393     {
1394         /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1395         char **m;
1396 
1397         for (m = matches; *m != NULL; m++)
1398         {
1399             char *p;
1400 
1401             p = *m;
1402             *m = strutils_shell_escape (*m);
1403             g_free (p);
1404         }
1405     }
1406 
1407     return matches;
1408 }
1409 
1410 /* --------------------------------------------------------------------------------------------- */
1411 
1412 void
1413 complete_engine_fill_completions (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1414 {
1415     char *s;
1416     const char *word_separators;
1417 
1418     word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1419 
1420     end = str_offset_to_pos (in->buffer->str, in->point);
1421 
1422     s = in->buffer->str;
1423     if (in->point != 0)
1424     {
1425         /* get symbol before in->point */
1426         size_t i;
1427 
1428         for (i = in->point - 1; i > 0; i--)
1429             str_next_char (&s);
1430     }
1431 
1432     for (; s >= in->buffer->str; str_prev_char (&s))
1433     {
1434         start = s - in->buffer->str;
1435         if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer->str, s))
1436             break;
1437     }
1438 
1439     if (start < end)
1440     {
1441         str_next_char (&s);
1442         start = s - in->buffer->str;
1443     }
1444 
1445     in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
1446 }
1447 
1448 /* --------------------------------------------------------------------------------------------- */
1449 
1450 /* declared in lib/widget/input.h */
1451 void
1452 input_complete (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1453 {
1454     int engine_flags;
1455 
1456     if (!str_is_valid_string (in->buffer->str))
1457         return;
1458 
1459     if (in->completions != NULL)
1460         engine_flags = DO_QUERY;
1461     else
1462     {
1463         engine_flags = DO_INSERTION;
1464 
1465         if (mc_global.widget.show_all_if_ambiguous)
1466             engine_flags |= DO_QUERY;
1467     }
1468 
1469     while (complete_engine (in, engine_flags))
1470         ;
1471 }
1472 
1473 /* --------------------------------------------------------------------------------------------- */
1474 
1475 void
1476 input_complete_free (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1477 {
1478     g_strfreev (in->completions);
1479     in->completions = NULL;
1480 }
1481 
1482 /* --------------------------------------------------------------------------------------------- */

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