Support of disable of shell variables substitution in the command line.
[midnight-commander.git] / src / filemanager / command.c
blob9f969814eeabbf134f9eb037d06600605ecdfa4a
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.
7 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
8 2007, 2011
9 The Free Software Foundation, Inc.
11 This file is part of the Midnight Commander.
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /** \file command.c
28 * \brief Source: command line widget
31 #include <config.h>
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <string.h>
37 #include "lib/global.h" /* home_dir */
38 #include "lib/tty/tty.h"
39 #include "lib/vfs/vfs.h"
40 #include "lib/strescape.h"
41 #include "lib/skin.h" /* DEFAULT_COLOR */
42 #include "lib/util.h"
43 #include "lib/widget.h"
45 #include "src/main.h" /* do_cd */
46 #include "src/subshell.h" /* SUBSHELL_EXIT */
47 #include "src/execute.h" /* shell_execute */
49 #include "midnight.h" /* current_panel */
50 #include "layout.h" /* for command_prompt variable */
51 #include "usermenu.h" /* expand_format */
52 #include "tree.h" /* for tree_chdir */
54 #include "command.h"
56 /*** global variables ****************************************************************************/
58 /* This holds the command line */
59 WInput *cmdline;
61 /*** file scope macro definitions ****************************************************************/
63 #define CD_OPERAND_OFFSET 3
65 /*** file scope type declarations ****************************************************************/
67 /*** file scope variables ************************************************************************/
69 /*** file scope functions ************************************************************************/
70 /* --------------------------------------------------------------------------------------------- */
72 /**
73 * Expand the argument to "cd" and change directory. First try tilde
74 * expansion, then variable substitution. If the CDPATH variable is set
75 * (e.g. CDPATH=".:~:/usr"), try all the paths contained there.
76 * We do not support such rare substitutions as ${var:-value} etc.
77 * No quoting is implemented here, so ${VAR} and $VAR will be always
78 * substituted. Wildcards are not supported either.
79 * Advanced users should be encouraged to use "\cd" instead of "cd" if
80 * they want the behavior they are used to in the shell.
83 static gboolean
84 examine_cd (const char *_path)
86 typedef enum { copy_sym, subst_var } state_t;
88 state_t state = copy_sym;
89 gboolean result;
90 size_t qlen;
91 char *path_tilde, *path;
92 char *p, *q, *r, *s, c;
94 /* Tilde expansion */
95 path = strutils_shell_unescape (_path);
96 path_tilde = tilde_expand (path);
97 g_free (path);
99 /* Leave space for further expansion */
100 qlen = strlen (path_tilde) + MC_MAXPATHLEN;
101 q = g_malloc (qlen);
103 /* Variable expansion */
104 for (p = path_tilde, r = q; *p != '\0' && r < q + MC_MAXPATHLEN;)
106 switch (state)
108 case copy_sym:
109 if (p[0] == '\\' && p[1] == '$')
111 /* skip backslash */
112 p++;
113 /* copy dollar */
114 *(r++) = *(p++);
116 else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
117 *(r++) = *(p++);
118 else
119 state = subst_var;
120 break;
122 case subst_var:
124 const char *t;
126 /* skip dollar */
127 p++;
129 if (p[0] != '{')
130 s = NULL;
131 else
133 p++;
134 s = strchr (p, '}');
136 if (s == NULL)
137 s = strchr (p, PATH_SEP);
138 if (s == NULL)
139 s = strchr (p, '\0');
140 c = *s;
141 *s = '\0';
142 t = getenv (p);
143 *s = c;
144 if (t == NULL)
146 *(r++) = '$';
147 if (p[-1] != '$')
148 *(r++) = '{';
150 else
152 size_t tlen;
154 tlen = strlen (t);
156 if (r + tlen < q + MC_MAXPATHLEN)
158 strncpy (r, t, tlen + 1);
159 r += tlen;
161 p = s;
162 if (*s == '}')
163 p++;
166 state = copy_sym;
167 break;
172 g_free (path_tilde);
174 *r = '\0';
176 result = do_cd (q, cd_parse_command);
178 /* CDPATH handling */
179 if (!result && *q != PATH_SEP)
181 char *cdpath;
183 cdpath = g_strdup (getenv ("CDPATH"));
184 p = cdpath;
185 if (p == NULL)
186 c = '\0';
187 else
188 c = ':';
189 while (!result && c == ':')
191 s = strchr (p, ':');
192 if (s == NULL)
193 s = strchr (p, '\0');
194 c = *s;
195 *s = '\0';
196 if (*p != '\0')
198 r = mc_build_filename (p, q, (char *) NULL);
199 result = do_cd (r, cd_parse_command);
200 g_free (r);
202 *s = c;
203 p = s + 1;
205 g_free (cdpath);
208 g_free (q);
210 return result;
213 /* --------------------------------------------------------------------------------------------- */
214 /** Handle Enter on the command line */
216 static cb_ret_t
217 enter (WInput * lc_cmdline)
219 char *cmd = lc_cmdline->buffer;
221 if (!command_prompt)
222 return MSG_HANDLED;
224 /* Any initial whitespace should be removed at this point */
225 while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n')
226 cmd++;
228 if (!*cmd)
229 return MSG_HANDLED;
231 if (strncmp (cmd, "cd ", 3) == 0 || strcmp (cmd, "cd") == 0)
233 do_cd_command (cmd);
234 input_clean (lc_cmdline);
235 return MSG_HANDLED;
237 else if (strcmp (cmd, "exit") == 0)
239 input_assign_text (lc_cmdline, "");
240 if (!quiet_quit_cmd ())
241 return MSG_NOT_HANDLED;
243 else
245 char *command, *s;
246 size_t i, j, cmd_len;
248 if (!vfs_current_is_local ())
250 message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
251 return MSG_NOT_HANDLED;
253 #ifdef HAVE_SUBSHELL_SUPPORT
254 /* Check this early before we clean command line
255 * (will be checked again by shell_execute) */
256 if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
258 message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
259 return MSG_NOT_HANDLED;
261 #endif
262 cmd_len = strlen (cmd);
263 command = g_malloc (cmd_len + 1);
264 command[0] = 0;
265 for (i = j = 0; i < cmd_len; i++)
267 if (cmd[i] == '%')
269 i++;
270 s = expand_format (NULL, cmd[i], TRUE);
271 command = g_realloc (command, j + strlen (s) + cmd_len - i + 1);
272 strcpy (command + j, s);
273 g_free (s);
274 j = strlen (command);
276 else
278 command[j] = cmd[i];
279 j++;
281 command[j] = 0;
283 input_clean (lc_cmdline);
284 shell_execute (command, 0);
285 g_free (command);
287 #ifdef HAVE_SUBSHELL_SUPPORT
288 if ((quit & SUBSHELL_EXIT) != 0)
290 if (quiet_quit_cmd ())
291 return MSG_HANDLED;
293 quit = 0;
294 /* restart subshell */
295 if (mc_global.tty.use_subshell)
296 init_subshell ();
299 if (mc_global.tty.use_subshell)
300 do_load_prompt ();
301 #endif
303 return MSG_HANDLED;
306 /* --------------------------------------------------------------------------------------------- */
308 static cb_ret_t
309 command_callback (Widget * w, widget_msg_t msg, int parm)
311 WInput *cmd = (WInput *) w;
313 switch (msg)
315 case WIDGET_FOCUS:
316 /* Never accept focus, otherwise panels will be unselected */
317 return MSG_NOT_HANDLED;
319 case WIDGET_KEY:
320 /* Special case: we handle the enter key */
321 if (parm == '\n')
323 return enter (cmd);
325 /* fall through */
327 default:
328 return input_callback (w, msg, parm);
332 /* --------------------------------------------------------------------------------------------- */
333 /*** public functions ****************************************************************************/
334 /* --------------------------------------------------------------------------------------------- */
336 /* --------------------------------------------------------------------------------------------- */
337 /** Execute the cd command on the command line */
338 void
339 do_cd_command (char *orig_cmd)
341 int len;
342 int operand_pos = CD_OPERAND_OFFSET;
343 const char *cmd;
345 /* Any final whitespace should be removed here
346 (to see why, try "cd fred "). */
347 /* NOTE: I think we should not remove the extra space,
348 that way, we can cd into hidden directories */
349 /* FIXME: what about interpreting quoted strings like the shell.
350 so one could type "cd <tab> M-a <enter>" and it would work. */
351 len = strlen (orig_cmd) - 1;
352 while (len >= 0 && (orig_cmd[len] == ' ' || orig_cmd[len] == '\t' || orig_cmd[len] == '\n'))
354 orig_cmd[len] = 0;
355 len--;
358 cmd = orig_cmd;
359 if (cmd[CD_OPERAND_OFFSET - 1] == 0)
360 cmd = "cd "; /* 0..2 => given text, 3 => \0 */
362 /* allow any amount of white space in front of the path operand */
363 while (cmd[operand_pos] == ' ' || cmd[operand_pos] == '\t')
364 operand_pos++;
366 if (get_current_type () == view_tree)
368 if (cmd[0] == 0)
370 sync_tree (mc_config_get_home_dir ());
372 else if (strcmp (cmd + operand_pos, "..") == 0)
374 char *dir = current_panel->cwd;
375 len = strlen (dir);
376 while (len && dir[--len] != PATH_SEP);
377 dir[len] = 0;
378 if (len)
379 sync_tree (dir);
380 else
381 sync_tree (PATH_SEP_STR);
383 else if (cmd[operand_pos] == PATH_SEP)
385 sync_tree (cmd + operand_pos);
387 else
389 char *old = current_panel->cwd;
390 char *new;
391 new = concat_dir_and_file (old, cmd + operand_pos);
392 sync_tree (new);
393 g_free (new);
396 else if (!examine_cd (&cmd[operand_pos]))
398 char *d = strip_password (g_strdup (&cmd[operand_pos]), 1);
399 message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), d, unix_error_string (errno));
400 g_free (d);
401 return;
405 /* --------------------------------------------------------------------------------------------- */
407 WInput *
408 command_new (int y, int x, int cols)
410 WInput *cmd;
411 const input_colors_t command_colors = {
412 DEFAULT_COLOR,
413 COMMAND_MARK_COLOR,
414 DEFAULT_COLOR,
415 COMMAND_HISTORY_COLOR
418 cmd = input_new (y, x, (int *) command_colors, cols, "", "cmdline",
419 INPUT_COMPLETE_DEFAULT | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
420 INPUT_COMPLETE_SHELL_ESC);
422 /* Add our hooks */
423 cmd->widget.callback = command_callback;
425 return cmd;
428 /* --------------------------------------------------------------------------------------------- */
430 * Insert quoted text in input line. The function is meant for the
431 * command line, so the percent sign is quoted as well.
434 void
435 command_insert (WInput * in, const char *text, gboolean insert_extra_space)
437 char *quoted_text;
439 quoted_text = name_quote (text, 1);
440 input_insert (in, quoted_text, insert_extra_space);
441 g_free (quoted_text);
444 /* --------------------------------------------------------------------------------------------- */