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

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