Merge branch '2974_broken_war_opening'
[midnight-commander.git] / src / filemanager / command.c
blob00d787d9d2058b8423fa0eb5fd91467bd3bf1949
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/setup.h" /* quit */
46 #ifdef ENABLE_SUBSHELL
47 #include "src/subshell.h"
48 #endif
49 #include "src/execute.h" /* shell_execute */
51 #include "midnight.h" /* current_panel */
52 #include "layout.h" /* for command_prompt variable */
53 #include "usermenu.h" /* expand_format */
54 #include "tree.h" /* for tree_chdir */
56 #include "command.h"
58 /*** global variables ****************************************************************************/
60 /* This holds the command line */
61 WInput *cmdline;
63 /*** file scope macro definitions ****************************************************************/
65 #define CD_OPERAND_OFFSET 3
67 /*** file scope type declarations ****************************************************************/
69 /*** file scope variables ************************************************************************/
71 /*** file scope functions ************************************************************************/
72 /* --------------------------------------------------------------------------------------------- */
74 /**
75 * Expand the argument to "cd" and change directory. First try tilde
76 * expansion, then variable substitution. If the CDPATH variable is set
77 * (e.g. CDPATH=".:~:/usr"), try all the paths contained there.
78 * We do not support such rare substitutions as ${var:-value} etc.
79 * No quoting is implemented here, so ${VAR} and $VAR will be always
80 * substituted. Wildcards are not supported either.
81 * Advanced users should be encouraged to use "\cd" instead of "cd" if
82 * they want the behavior they are used to in the shell.
84 * @param _path string to examine
85 * @return newly allocated string
88 static char *
89 examine_cd (const char *_path)
91 typedef enum
92 { copy_sym, subst_var } state_t;
94 state_t state = copy_sym;
95 char *q;
96 size_t qlen;
97 char *path_tilde, *path;
98 char *p, *r;
100 /* Tilde expansion */
101 path = strutils_shell_unescape (_path);
102 path_tilde = tilde_expand (path);
103 g_free (path);
105 /* Leave space for further expansion */
106 qlen = strlen (path_tilde) + MC_MAXPATHLEN;
107 q = g_malloc (qlen);
109 /* Variable expansion */
110 for (p = path_tilde, r = q; *p != '\0' && r < q + MC_MAXPATHLEN;)
113 switch (state)
115 case copy_sym:
116 if (p[0] == '\\' && p[1] == '$')
118 /* skip backslash */
119 p++;
120 /* copy dollar */
121 *(r++) = *(p++);
123 else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
124 *(r++) = *(p++);
125 else
126 state = subst_var;
127 break;
129 case subst_var:
131 char *s;
132 char c;
133 const char *t;
135 /* skip dollar */
136 p++;
138 if (p[0] != '{')
139 s = NULL;
140 else
142 p++;
143 s = strchr (p, '}');
145 if (s == NULL)
146 s = strchr (p, PATH_SEP);
147 if (s == NULL)
148 s = strchr (p, '\0');
149 c = *s;
150 *s = '\0';
151 t = getenv (p);
152 *s = c;
153 if (t == NULL)
155 *(r++) = '$';
156 if (p[-1] != '$')
157 *(r++) = '{';
159 else
161 size_t tlen;
163 tlen = strlen (t);
165 if (r + tlen < q + MC_MAXPATHLEN)
167 strncpy (r, t, tlen + 1);
168 r += tlen;
170 p = s;
171 if (*s == '}')
172 p++;
175 state = copy_sym;
176 break;
181 g_free (path_tilde);
183 *r = '\0';
185 return q;
188 /* --------------------------------------------------------------------------------------------- */
190 /* CDPATH handling */
191 static gboolean
192 handle_cdpath (const char *path)
194 gboolean result = FALSE;
196 /* CDPATH handling */
197 if (*path != PATH_SEP)
199 char *cdpath, *p;
200 char c;
202 cdpath = g_strdup (getenv ("CDPATH"));
203 p = cdpath;
204 c = (p == NULL) ? '\0' : ':';
206 while (!result && c == ':')
208 char *s;
210 s = strchr (p, ':');
211 if (s == NULL)
212 s = strchr (p, '\0');
213 c = *s;
214 *s = '\0';
215 if (*p != '\0')
217 vfs_path_t *r_vpath;
219 r_vpath = vfs_path_build_filename (p, path, NULL);
220 result = do_cd (r_vpath, cd_parse_command);
221 vfs_path_free (r_vpath);
223 *s = c;
224 p = s + 1;
226 g_free (cdpath);
229 return result;
232 /* --------------------------------------------------------------------------------------------- */
234 /** Handle Enter on the command line
236 * @param lc_cmdline string for handling
237 * @return MSG_HANDLED on sucsess else MSG_NOT_HANDLED
240 static cb_ret_t
241 enter (WInput * lc_cmdline)
243 char *cmd = lc_cmdline->buffer;
245 if (!command_prompt)
246 return MSG_HANDLED;
248 /* Any initial whitespace should be removed at this point */
249 while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n')
250 cmd++;
252 if (!*cmd)
253 return MSG_HANDLED;
255 if (strncmp (cmd, "cd ", 3) == 0 || strcmp (cmd, "cd") == 0)
257 do_cd_command (cmd);
258 input_clean (lc_cmdline);
259 return MSG_HANDLED;
261 else if (strcmp (cmd, "exit") == 0)
263 input_assign_text (lc_cmdline, "");
264 if (!quiet_quit_cmd ())
265 return MSG_NOT_HANDLED;
267 else
269 GString *command;
270 size_t i;
272 if (!vfs_current_is_local ())
274 message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
275 return MSG_NOT_HANDLED;
277 #ifdef ENABLE_SUBSHELL
278 /* Check this early before we clean command line
279 * (will be checked again by shell_execute) */
280 if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
282 message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
283 return MSG_NOT_HANDLED;
285 #endif
286 command = g_string_sized_new (32);
288 for (i = 0; cmd[i] != '\0'; i++)
290 if (cmd[i] != '%')
291 g_string_append_c (command, cmd[i]);
292 else
294 char *s;
296 s = expand_format (NULL, cmd[++i], TRUE);
297 g_string_append (command, s);
298 g_free (s);
302 input_clean (lc_cmdline);
303 shell_execute (command->str, 0);
304 g_string_free (command, TRUE);
306 #ifdef ENABLE_SUBSHELL
307 if ((quit & SUBSHELL_EXIT) != 0)
309 if (quiet_quit_cmd ())
310 return MSG_HANDLED;
312 quit = 0;
313 /* restart subshell */
314 if (mc_global.tty.use_subshell)
315 init_subshell ();
318 if (mc_global.tty.use_subshell)
319 do_load_prompt ();
320 #endif
322 return MSG_HANDLED;
325 /* --------------------------------------------------------------------------------------------- */
328 * Default command line callback
330 * @param w Widget object
331 * @param msg message for handling
332 * @param parm extra parameter such as key code
334 * @return MSG_NOT_HANDLED on fail else MSG_HANDLED
337 static cb_ret_t
338 command_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
340 switch (msg)
342 case MSG_FOCUS:
343 /* Never accept focus, otherwise panels will be unselected */
344 return MSG_NOT_HANDLED;
346 case MSG_KEY:
347 /* Special case: we handle the enter key */
348 if (parm == '\n')
349 return enter (INPUT (w));
350 /* fall through */
352 default:
353 return input_callback (w, sender, msg, parm, data);
357 /* --------------------------------------------------------------------------------------------- */
358 /*** public functions ****************************************************************************/
359 /* --------------------------------------------------------------------------------------------- */
361 /** Execute the cd command on the command line
363 * @param orig_cmd command for execution
366 void
367 do_cd_command (char *orig_cmd)
369 int len;
370 int operand_pos = CD_OPERAND_OFFSET;
371 const char *cmd;
373 /* Any final whitespace should be removed here
374 (to see why, try "cd fred "). */
375 /* NOTE: I think we should not remove the extra space,
376 that way, we can cd into hidden directories */
377 /* FIXME: what about interpreting quoted strings like the shell.
378 so one could type "cd <tab> M-a <enter>" and it would work. */
379 len = strlen (orig_cmd) - 1;
380 while (len >= 0 && (orig_cmd[len] == ' ' || orig_cmd[len] == '\t' || orig_cmd[len] == '\n'))
382 orig_cmd[len] = 0;
383 len--;
386 cmd = orig_cmd;
387 if (cmd[CD_OPERAND_OFFSET - 1] == 0)
388 cmd = "cd "; /* 0..2 => given text, 3 => \0 */
390 /* allow any amount of white space in front of the path operand */
391 while (cmd[operand_pos] == ' ' || cmd[operand_pos] == '\t')
392 operand_pos++;
394 if (get_current_type () == view_tree)
396 if (cmd[0] == 0)
398 sync_tree (mc_config_get_home_dir ());
400 else if (strcmp (cmd + operand_pos, "..") == 0)
402 char *str_path;
404 if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
405 strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
407 vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
409 current_panel->cwd_vpath =
410 vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
411 vfs_path_free (tmp_vpath);
413 str_path = vfs_path_to_str (current_panel->cwd_vpath);
414 sync_tree (str_path);
415 g_free (str_path);
417 else if (cmd[operand_pos] == PATH_SEP)
419 sync_tree (cmd + operand_pos);
421 else
423 char *str_path;
424 vfs_path_t *new_vpath;
426 new_vpath = vfs_path_append_new (current_panel->cwd_vpath, cmd + operand_pos, NULL);
427 str_path = vfs_path_to_str (new_vpath);
428 vfs_path_free (new_vpath);
429 sync_tree (str_path);
430 g_free (str_path);
433 else
435 char *path;
436 vfs_path_t *q_vpath;
437 gboolean ok;
439 path = examine_cd (&cmd[operand_pos]);
441 if (*path == '\0')
442 q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
443 else
444 q_vpath = vfs_path_from_str_flags (path, VPF_NO_CANON);
446 ok = do_cd (q_vpath, cd_parse_command);
447 if (!ok)
448 ok = handle_cdpath (path);
450 if (!ok)
452 char *d;
454 d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
455 message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), d,
456 unix_error_string (errno));
457 g_free (d);
460 vfs_path_free (q_vpath);
461 g_free (path);
465 /* --------------------------------------------------------------------------------------------- */
467 WInput *
468 command_new (int y, int x, int cols)
470 WInput *cmd;
471 const input_colors_t command_colors = {
472 DEFAULT_COLOR,
473 COMMAND_MARK_COLOR,
474 DEFAULT_COLOR,
475 COMMAND_HISTORY_COLOR
478 cmd = input_new (y, x, (int *) command_colors, cols, "", "cmdline",
479 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
480 | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
481 INPUT_COMPLETE_SHELL_ESC);
483 /* Add our hooks */
484 WIDGET (cmd)->callback = command_callback;
486 return cmd;
489 /* --------------------------------------------------------------------------------------------- */
491 * Insert quoted text in input line. The function is meant for the
492 * command line, so the percent sign is quoted as well.
494 * @param in WInput object
495 * @param text string for insertion
496 * @param insert_extra_space add extra space
499 void
500 command_insert (WInput * in, const char *text, gboolean insert_extra_space)
502 char *quoted_text;
504 quoted_text = name_quote (text, 1);
505 input_insert (in, quoted_text, insert_extra_space);
506 g_free (quoted_text);
509 /* --------------------------------------------------------------------------------------------- */