(enter): use GString instead of hand-made memory (re)allocation.
[midnight-commander.git] / src / filemanager / command.c
blobd98b7725d8ebfb0fc9a709d710203187788d8907
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.
85 static char *
86 examine_cd (const char *_path)
88 typedef enum
89 { copy_sym, subst_var } state_t;
91 state_t state = copy_sym;
92 char *q;
93 size_t qlen;
94 char *path_tilde, *path;
95 char *p, *r;
97 /* Tilde expansion */
98 path = strutils_shell_unescape (_path);
99 path_tilde = tilde_expand (path);
100 g_free (path);
102 /* Leave space for further expansion */
103 qlen = strlen (path_tilde) + MC_MAXPATHLEN;
104 q = g_malloc (qlen);
106 /* Variable expansion */
107 for (p = path_tilde, r = q; *p != '\0' && r < q + MC_MAXPATHLEN;)
110 switch (state)
112 case copy_sym:
113 if (p[0] == '\\' && p[1] == '$')
115 /* skip backslash */
116 p++;
117 /* copy dollar */
118 *(r++) = *(p++);
120 else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
121 *(r++) = *(p++);
122 else
123 state = subst_var;
124 break;
126 case subst_var:
128 char *s;
129 char c;
130 const char *t;
132 /* skip dollar */
133 p++;
135 if (p[0] != '{')
136 s = NULL;
137 else
139 p++;
140 s = strchr (p, '}');
142 if (s == NULL)
143 s = strchr (p, PATH_SEP);
144 if (s == NULL)
145 s = strchr (p, '\0');
146 c = *s;
147 *s = '\0';
148 t = getenv (p);
149 *s = c;
150 if (t == NULL)
152 *(r++) = '$';
153 if (p[-1] != '$')
154 *(r++) = '{';
156 else
158 size_t tlen;
160 tlen = strlen (t);
162 if (r + tlen < q + MC_MAXPATHLEN)
164 strncpy (r, t, tlen + 1);
165 r += tlen;
167 p = s;
168 if (*s == '}')
169 p++;
172 state = copy_sym;
173 break;
178 g_free (path_tilde);
180 *r = '\0';
182 return q;
185 /* --------------------------------------------------------------------------------------------- */
187 /* CDPATH handling */
188 static gboolean
189 handle_cdpath (const char *path)
191 gboolean result = FALSE;
193 /* CDPATH handling */
194 if (*path != PATH_SEP)
196 char *cdpath, *p;
197 char c;
199 cdpath = g_strdup (getenv ("CDPATH"));
200 p = cdpath;
201 c = (p == NULL) ? '\0' : ':';
203 while (!result && c == ':')
205 char *s;
207 s = strchr (p, ':');
208 if (s == NULL)
209 s = strchr (p, '\0');
210 c = *s;
211 *s = '\0';
212 if (*p != '\0')
214 vfs_path_t *r_vpath;
216 r_vpath = vfs_path_build_filename (p, path, NULL);
217 result = do_cd (r_vpath, cd_parse_command);
218 vfs_path_free (r_vpath);
220 *s = c;
221 p = s + 1;
223 g_free (cdpath);
226 return result;
229 /* --------------------------------------------------------------------------------------------- */
230 /** Handle Enter on the command line */
232 static cb_ret_t
233 enter (WInput * lc_cmdline)
235 char *cmd = lc_cmdline->buffer;
237 if (!command_prompt)
238 return MSG_HANDLED;
240 /* Any initial whitespace should be removed at this point */
241 while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n')
242 cmd++;
244 if (!*cmd)
245 return MSG_HANDLED;
247 if (strncmp (cmd, "cd ", 3) == 0 || strcmp (cmd, "cd") == 0)
249 do_cd_command (cmd);
250 input_clean (lc_cmdline);
251 return MSG_HANDLED;
253 else if (strcmp (cmd, "exit") == 0)
255 input_assign_text (lc_cmdline, "");
256 if (!quiet_quit_cmd ())
257 return MSG_NOT_HANDLED;
259 else
261 GString *command;
262 size_t i;
264 if (!vfs_current_is_local ())
266 message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
267 return MSG_NOT_HANDLED;
269 #ifdef ENABLE_SUBSHELL
270 /* Check this early before we clean command line
271 * (will be checked again by shell_execute) */
272 if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
274 message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
275 return MSG_NOT_HANDLED;
277 #endif
278 command = g_string_sized_new (32);
280 for (i = 0; cmd[i] != '\0'; i++)
282 if (cmd[i] != '%')
283 g_string_append_c (command, cmd[i]);
284 else
286 char *s;
288 s = expand_format (NULL, cmd[++i], TRUE);
289 g_string_append (command, s);
290 g_free (s);
294 input_clean (lc_cmdline);
295 shell_execute (command->str, 0);
296 g_string_free (command, TRUE);
298 #ifdef ENABLE_SUBSHELL
299 if ((quit & SUBSHELL_EXIT) != 0)
301 if (quiet_quit_cmd ())
302 return MSG_HANDLED;
304 quit = 0;
305 /* restart subshell */
306 if (mc_global.tty.use_subshell)
307 init_subshell ();
310 if (mc_global.tty.use_subshell)
311 do_load_prompt ();
312 #endif
314 return MSG_HANDLED;
317 /* --------------------------------------------------------------------------------------------- */
319 static cb_ret_t
320 command_callback (Widget * w, widget_msg_t msg, int parm)
322 WInput *cmd = (WInput *) w;
324 switch (msg)
326 case WIDGET_FOCUS:
327 /* Never accept focus, otherwise panels will be unselected */
328 return MSG_NOT_HANDLED;
330 case WIDGET_KEY:
331 /* Special case: we handle the enter key */
332 if (parm == '\n')
334 return enter (cmd);
336 /* fall through */
338 default:
339 return input_callback (w, msg, parm);
343 /* --------------------------------------------------------------------------------------------- */
344 /*** public functions ****************************************************************************/
345 /* --------------------------------------------------------------------------------------------- */
347 /* --------------------------------------------------------------------------------------------- */
348 /** Execute the cd command on the command line */
349 void
350 do_cd_command (char *orig_cmd)
352 int len;
353 int operand_pos = CD_OPERAND_OFFSET;
354 const char *cmd;
356 /* Any final whitespace should be removed here
357 (to see why, try "cd fred "). */
358 /* NOTE: I think we should not remove the extra space,
359 that way, we can cd into hidden directories */
360 /* FIXME: what about interpreting quoted strings like the shell.
361 so one could type "cd <tab> M-a <enter>" and it would work. */
362 len = strlen (orig_cmd) - 1;
363 while (len >= 0 && (orig_cmd[len] == ' ' || orig_cmd[len] == '\t' || orig_cmd[len] == '\n'))
365 orig_cmd[len] = 0;
366 len--;
369 cmd = orig_cmd;
370 if (cmd[CD_OPERAND_OFFSET - 1] == 0)
371 cmd = "cd "; /* 0..2 => given text, 3 => \0 */
373 /* allow any amount of white space in front of the path operand */
374 while (cmd[operand_pos] == ' ' || cmd[operand_pos] == '\t')
375 operand_pos++;
377 if (get_current_type () == view_tree)
379 if (cmd[0] == 0)
381 sync_tree (mc_config_get_home_dir ());
383 else if (strcmp (cmd + operand_pos, "..") == 0)
385 char *str_path;
387 if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
388 strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
390 vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
392 current_panel->cwd_vpath =
393 vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
394 vfs_path_free (tmp_vpath);
396 str_path = vfs_path_to_str (current_panel->cwd_vpath);
397 sync_tree (str_path);
398 g_free (str_path);
400 else if (cmd[operand_pos] == PATH_SEP)
402 sync_tree (cmd + operand_pos);
404 else
406 char *str_path;
407 vfs_path_t *new_vpath;
409 new_vpath = vfs_path_append_new (current_panel->cwd_vpath, cmd + operand_pos, NULL);
410 str_path = vfs_path_to_str (new_vpath);
411 vfs_path_free (new_vpath);
412 sync_tree (str_path);
413 g_free (str_path);
416 else
418 char *path;
419 vfs_path_t *q_vpath;
420 gboolean ok;
422 path = examine_cd (&cmd[operand_pos]);
424 if (*path == '\0')
425 q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
426 else
427 q_vpath = vfs_path_from_str_flags (path, VPF_NO_CANON);
429 ok = do_cd (q_vpath, cd_parse_command);
430 if (!ok)
431 ok = handle_cdpath (path);
433 if (!ok)
435 char *d;
437 d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
438 message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), d,
439 unix_error_string (errno));
440 g_free (d);
443 vfs_path_free (q_vpath);
444 g_free (path);
448 /* --------------------------------------------------------------------------------------------- */
450 WInput *
451 command_new (int y, int x, int cols)
453 WInput *cmd;
454 const input_colors_t command_colors = {
455 DEFAULT_COLOR,
456 COMMAND_MARK_COLOR,
457 DEFAULT_COLOR,
458 COMMAND_HISTORY_COLOR
461 cmd = input_new (y, x, (int *) command_colors, cols, "", "cmdline",
462 INPUT_COMPLETE_DEFAULT | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
463 INPUT_COMPLETE_SHELL_ESC);
465 /* Add our hooks */
466 cmd->widget.callback = command_callback;
468 return cmd;
471 /* --------------------------------------------------------------------------------------------- */
473 * Insert quoted text in input line. The function is meant for the
474 * command line, so the percent sign is quoted as well.
477 void
478 command_insert (WInput * in, const char *text, gboolean insert_extra_space)
480 char *quoted_text;
482 quoted_text = name_quote (text, 1);
483 input_insert (in, quoted_text, insert_extra_space);
484 g_free (quoted_text);
487 /* --------------------------------------------------------------------------------------------- */