root/src/filemanager/command.c

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

DEFINITIONS

This source file includes following definitions.
  1. examine_cd
  2. handle_cdpath
  3. enter
  4. command_callback
  5. do_cd_command
  6. command_new
  7. command_set_default_colors
  8. command_insert

   1 /*
   2    Command line widget.
   3    This widget is derived from the WInput widget, it's used to cope
   4    with all the magic of the command input line, we depend on some
   5    help from the program's callback.
   6 
   7    Copyright (C) 1995-2019
   8    Free Software Foundation, Inc.
   9 
  10    Written by:
  11    Slava Zanko <slavazanko@gmail.com>, 2013
  12 
  13    This file is part of the Midnight Commander.
  14 
  15    The Midnight Commander is free software: you can redistribute it
  16    and/or modify it under the terms of the GNU General Public License as
  17    published by the Free Software Foundation, either version 3 of the License,
  18    or (at your option) any later version.
  19 
  20    The Midnight Commander is distributed in the hope that it will be useful,
  21    but WITHOUT ANY WARRANTY; without even the implied warranty of
  22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23    GNU General Public License for more details.
  24 
  25    You should have received a copy of the GNU General Public License
  26    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  27  */
  28 
  29 /** \file command.c
  30  *  \brief Source: command line widget
  31  */
  32 
  33 #include <config.h>
  34 
  35 #include <stdlib.h>
  36 #include <errno.h>
  37 #include <string.h>
  38 
  39 #include "lib/global.h"         /* home_dir */
  40 #include "lib/tty/tty.h"
  41 #include "lib/vfs/vfs.h"
  42 #include "lib/strescape.h"
  43 #include "lib/skin.h"           /* DEFAULT_COLOR */
  44 #include "lib/util.h"
  45 #include "lib/widget.h"
  46 
  47 #include "src/setup.h"          /* quit */
  48 #ifdef ENABLE_SUBSHELL
  49 #include "src/subshell/subshell.h"
  50 #endif
  51 #include "src/execute.h"        /* shell_execute */
  52 #include "src/usermenu.h"       /* expand_format */
  53 
  54 #include "midnight.h"           /* current_panel */
  55 #include "layout.h"             /* for command_prompt variable */
  56 #include "tree.h"               /* sync_tree() */
  57 
  58 #include "command.h"
  59 
  60 /*** global variables ****************************************************************************/
  61 
  62 /* This holds the command line */
  63 WInput *cmdline;
  64 
  65 /*** file scope macro definitions ****************************************************************/
  66 
  67 #define CD_OPERAND_OFFSET 3
  68 
  69 /*** file scope type declarations ****************************************************************/
  70 
  71 /*** file scope variables ************************************************************************/
  72 
  73 /* Color styles command line */
  74 static input_colors_t command_colors;
  75 
  76 /* --------------------------------------------------------------------------------------------- */
  77 /*** file scope functions ************************************************************************/
  78 /* --------------------------------------------------------------------------------------------- */
  79 
  80 /**
  81  * Expand the argument to "cd" and change directory.  First try tilde
  82  * expansion, then variable substitution.  If the CDPATH variable is set
  83  * (e.g. CDPATH=".:~:/usr"), try all the paths contained there.
  84  * We do not support such rare substitutions as ${var:-value} etc.
  85  * No quoting is implemented here, so ${VAR} and $VAR will be always
  86  * substituted.  Wildcards are not supported either.
  87  * Advanced users should be encouraged to use "\cd" instead of "cd" if
  88  * they want the behavior they are used to in the shell.
  89  *
  90  * @param _path string to examine
  91  * @return newly allocated string
  92  */
  93 
  94 static char *
  95 examine_cd (const char *_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
  96 {
  97     /* *INDENT-OFF* */
  98     typedef enum
  99     {
 100         copy_sym,
 101         subst_var
 102     } state_t;
 103     /* *INDENT-ON* */
 104 
 105     state_t state = copy_sym;
 106     GString *q;
 107     char *path_tilde, *path;
 108     char *p;
 109 
 110     /* Tilde expansion */
 111     path = strutils_shell_unescape (_path);
 112     path_tilde = tilde_expand (path);
 113     g_free (path);
 114 
 115     q = g_string_sized_new (32);
 116 
 117     /* Variable expansion */
 118     for (p = path_tilde; *p != '\0';)
 119     {
 120         switch (state)
 121         {
 122         case copy_sym:
 123             if (p[0] == '\\' && p[1] == '$')
 124             {
 125                 g_string_append_c (q, '$');
 126                 p += 2;
 127             }
 128             else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
 129             {
 130                 g_string_append_c (q, *p);
 131                 p++;
 132             }
 133             else
 134                 state = subst_var;
 135             break;
 136 
 137         case subst_var:
 138             {
 139                 char *s = NULL;
 140                 char c;
 141                 const char *t = NULL;
 142 
 143                 /* skip dollar */
 144                 p++;
 145 
 146                 if (p[0] == '{')
 147                 {
 148                     p++;
 149                     s = strchr (p, '}');
 150                 }
 151                 if (s == NULL)
 152                     s = strchr (p, PATH_SEP);
 153                 if (s == NULL)
 154                     s = strchr (p, '\0');
 155                 c = *s;
 156                 *s = '\0';
 157                 t = getenv (p);
 158                 *s = c;
 159                 if (t == NULL)
 160                 {
 161                     g_string_append_c (q, '$');
 162                     if (p[-1] != '$')
 163                         g_string_append_c (q, '{');
 164                 }
 165                 else
 166                 {
 167                     g_string_append (q, t);
 168                     p = s;
 169                     if (*s == '}')
 170                         p++;
 171                 }
 172 
 173                 state = copy_sym;
 174                 break;
 175             }
 176 
 177         default:
 178             break;
 179         }
 180     }
 181 
 182     g_free (path_tilde);
 183 
 184     return g_string_free (q, FALSE);
 185 }
 186 
 187 /* --------------------------------------------------------------------------------------------- */
 188 
 189 /* CDPATH handling */
 190 static gboolean
 191 handle_cdpath (const char *path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 192 {
 193     gboolean result = FALSE;
 194 
 195     /* CDPATH handling */
 196     if (!IS_PATH_SEP (*path))
 197     {
 198         char *cdpath, *p;
 199         char c;
 200 
 201         cdpath = g_strdup (getenv ("CDPATH"));
 202         p = cdpath;
 203         c = (p == NULL) ? '\0' : ':';
 204 
 205         while (!result && c == ':')
 206         {
 207             char *s;
 208 
 209             s = strchr (p, ':');
 210             if (s == NULL)
 211                 s = strchr (p, '\0');
 212             c = *s;
 213             *s = '\0';
 214             if (*p != '\0')
 215             {
 216                 vfs_path_t *r_vpath;
 217 
 218                 r_vpath = vfs_path_build_filename (p, path, (char *) NULL);
 219                 result = do_cd (r_vpath, cd_parse_command);
 220                 vfs_path_free (r_vpath);
 221             }
 222             *s = c;
 223             p = s + 1;
 224         }
 225         g_free (cdpath);
 226     }
 227 
 228     return result;
 229 }
 230 
 231 /* --------------------------------------------------------------------------------------------- */
 232 
 233 /** Handle Enter on the command line
 234  *
 235  * @param lc_cmdline string for handling
 236  * @return MSG_HANDLED on sucsess else MSG_NOT_HANDLED
 237  */
 238 
 239 static cb_ret_t
 240 enter (WInput * lc_cmdline)
     /* [previous][next][first][last][top][bottom][index][help]  */
 241 {
 242     char *cmd = lc_cmdline->buffer;
 243 
 244     if (!command_prompt)
 245         return MSG_HANDLED;
 246 
 247     /* Any initial whitespace should be removed at this point */
 248     while (whiteness (*cmd))
 249         cmd++;
 250 
 251     if (*cmd == '\0')
 252         return MSG_HANDLED;
 253 
 254     if (strncmp (cmd, "cd ", 3) == 0 || strcmp (cmd, "cd") == 0)
 255     {
 256         do_cd_command (cmd);
 257         input_clean (lc_cmdline);
 258         return MSG_HANDLED;
 259     }
 260     else if (strcmp (cmd, "exit") == 0)
 261     {
 262         input_assign_text (lc_cmdline, "");
 263         if (!quiet_quit_cmd ())
 264             return MSG_NOT_HANDLED;
 265     }
 266     else
 267     {
 268         GString *command;
 269         size_t i;
 270 
 271         if (!vfs_current_is_local ())
 272         {
 273             message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
 274             return MSG_NOT_HANDLED;
 275         }
 276 #ifdef ENABLE_SUBSHELL
 277         /* Check this early before we clean command line
 278          * (will be checked again by shell_execute) */
 279         if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
 280         {
 281             message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
 282             return MSG_NOT_HANDLED;
 283         }
 284 #endif
 285         command = g_string_sized_new (32);
 286 
 287         for (i = 0; cmd[i] != '\0'; i++)
 288         {
 289             if (cmd[i] != '%')
 290                 g_string_append_c (command, cmd[i]);
 291             else
 292             {
 293                 char *s;
 294 
 295                 s = expand_format (NULL, cmd[++i], TRUE);
 296                 g_string_append (command, s);
 297                 g_free (s);
 298             }
 299         }
 300 
 301         input_clean (lc_cmdline);
 302         shell_execute (command->str, 0);
 303         g_string_free (command, TRUE);
 304 
 305 #ifdef ENABLE_SUBSHELL
 306         if ((quit & SUBSHELL_EXIT) != 0)
 307         {
 308             if (quiet_quit_cmd ())
 309                 return MSG_HANDLED;
 310 
 311             quit = 0;
 312             /* restart subshell */
 313             if (mc_global.tty.use_subshell)
 314                 init_subshell ();
 315         }
 316 
 317         if (mc_global.tty.use_subshell)
 318             do_load_prompt ();
 319 #endif
 320     }
 321     return MSG_HANDLED;
 322 }
 323 
 324 /* --------------------------------------------------------------------------------------------- */
 325 
 326 /**
 327  * Default command line callback
 328  *
 329  * @param w Widget object
 330  * @param msg message for handling
 331  * @param parm extra parameter such as key code
 332  *
 333  * @return MSG_NOT_HANDLED on fail else MSG_HANDLED
 334  */
 335 
 336 static cb_ret_t
 337 command_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 338 {
 339     switch (msg)
 340     {
 341     case MSG_KEY:
 342         /* Special case: we handle the enter key */
 343         if (parm == '\n')
 344             return enter (INPUT (w));
 345         MC_FALLTHROUGH;
 346 
 347     default:
 348         return input_callback (w, sender, msg, parm, data);
 349     }
 350 }
 351 
 352 /* --------------------------------------------------------------------------------------------- */
 353 /*** public functions ****************************************************************************/
 354 /* --------------------------------------------------------------------------------------------- */
 355 
 356 /** Execute the cd command on the command line
 357  *
 358  * @param orig_cmd command for execution
 359  */
 360 
 361 void
 362 do_cd_command (char *orig_cmd)
     /* [previous][next][first][last][top][bottom][index][help]  */
 363 {
 364     int len;
 365     int operand_pos = CD_OPERAND_OFFSET;
 366     const char *cmd;
 367 
 368     /* Any final whitespace should be removed here
 369        (to see why, try "cd fred "). */
 370     /* NOTE: I think we should not remove the extra space,
 371        that way, we can cd into hidden directories */
 372     /* FIXME: what about interpreting quoted strings like the shell.
 373        so one could type "cd <tab> M-a <enter>" and it would work. */
 374     len = strlen (orig_cmd) - 1;
 375     while (len >= 0 && whiteness (orig_cmd[len]))
 376     {
 377         orig_cmd[len] = '\0';
 378         len--;
 379     }
 380 
 381     cmd = orig_cmd;
 382     if (cmd[CD_OPERAND_OFFSET - 1] == '\0')
 383         cmd = "cd ";            /* 0..2 => given text, 3 => \0 */
 384 
 385     /* allow any amount of white space in front of the path operand */
 386     while (whitespace (cmd[operand_pos]))
 387         operand_pos++;
 388 
 389     if (get_current_type () == view_tree)
 390     {
 391         vfs_path_t *new_vpath = NULL;
 392 
 393         if (cmd[0] == '\0')
 394         {
 395             new_vpath = vfs_path_from_str (mc_config_get_home_dir ());
 396             sync_tree (new_vpath);
 397         }
 398         else if (DIR_IS_DOTDOT (cmd + operand_pos))
 399         {
 400             if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
 401                 strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
 402             {
 403                 vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
 404 
 405                 current_panel->cwd_vpath =
 406                     vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
 407                 vfs_path_free (tmp_vpath);
 408             }
 409             sync_tree (current_panel->cwd_vpath);
 410         }
 411         else
 412         {
 413             if (IS_PATH_SEP (cmd[operand_pos]))
 414                 new_vpath = vfs_path_from_str (cmd + operand_pos);
 415             else
 416                 new_vpath =
 417                     vfs_path_append_new (current_panel->cwd_vpath, cmd + operand_pos,
 418                                          (char *) NULL);
 419 
 420             sync_tree (new_vpath);
 421         }
 422 
 423         vfs_path_free (new_vpath);
 424     }
 425     else
 426     {
 427         char *path;
 428         vfs_path_t *q_vpath;
 429         gboolean ok;
 430 
 431         path = examine_cd (&cmd[operand_pos]);
 432 
 433         if (*path == '\0')
 434             q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
 435         else
 436             q_vpath = vfs_path_from_str_flags (path, VPF_NO_CANON);
 437 
 438         ok = do_cd (q_vpath, cd_parse_command);
 439         if (!ok)
 440             ok = handle_cdpath (path);
 441 
 442         if (!ok)
 443         {
 444             char *d;
 445 
 446             d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
 447             message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), d,
 448                      unix_error_string (errno));
 449             g_free (d);
 450         }
 451 
 452         vfs_path_free (q_vpath);
 453         g_free (path);
 454     }
 455 }
 456 
 457 /* --------------------------------------------------------------------------------------------- */
 458 
 459 WInput *
 460 command_new (int y, int x, int cols)
     /* [previous][next][first][last][top][bottom][index][help]  */
 461 {
 462     WInput *cmd;
 463     Widget *w;
 464 
 465     cmd = input_new (y, x, command_colors, cols, "", "cmdline",
 466                      INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
 467                      | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
 468                      INPUT_COMPLETE_SHELL_ESC);
 469     w = WIDGET (cmd);
 470     /* Don't set WOP_SELECTABLE up, otherwise panels will be unselected */
 471     widget_set_options (w, WOP_SELECTABLE, FALSE);
 472     /* Add our hooks */
 473     w->callback = command_callback;
 474 
 475     return cmd;
 476 }
 477 
 478 /* --------------------------------------------------------------------------------------------- */
 479 /**
 480  * Set colors for the command line.
 481  */
 482 
 483 void
 484 command_set_default_colors (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 485 {
 486     command_colors[WINPUTC_MAIN] = DEFAULT_COLOR;
 487     command_colors[WINPUTC_MARK] = COMMAND_MARK_COLOR;
 488     command_colors[WINPUTC_UNCHANGED] = DEFAULT_COLOR;
 489     command_colors[WINPUTC_HISTORY] = COMMAND_HISTORY_COLOR;
 490 }
 491 
 492 /* --------------------------------------------------------------------------------------------- */
 493 /**
 494  * Insert quoted text in input line.  The function is meant for the
 495  * command line, so the percent sign is quoted as well.
 496  *
 497  * @param in WInput object
 498  * @param text string for insertion
 499  * @param insert_extra_space add extra space
 500  */
 501 
 502 void
 503 command_insert (WInput * in, const char *text, gboolean insert_extra_space)
     /* [previous][next][first][last][top][bottom][index][help]  */
 504 {
 505     char *quoted_text;
 506 
 507     quoted_text = name_quote (text, TRUE);
 508     input_insert (in, quoted_text, insert_extra_space);
 509     g_free (quoted_text);
 510 }
 511 
 512 /* --------------------------------------------------------------------------------------------- */

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