Don't focus widget that doesn't have the WOP_SELECTABLE option.
[midnight-commander.git] / src / filemanager / command.c
bloba4f13c876abe2c5462ce347d9218ded48aecbe15
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-2016
8 Free Software Foundation, Inc.
10 Written by:
11 Slava Zanko <slavazanko@gmail.com>, 2013
13 This file is part of the Midnight Commander.
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.
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.
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/>.
29 /** \file command.c
30 * \brief Source: command line widget
33 #include <config.h>
35 #include <stdlib.h>
36 #include <errno.h>
37 #include <string.h>
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"
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 */
53 #include "midnight.h" /* current_panel */
54 #include "layout.h" /* for command_prompt variable */
55 #include "usermenu.h" /* expand_format */
56 #include "tree.h" /* sync_tree() */
58 #include "command.h"
60 /*** global variables ****************************************************************************/
62 /* This holds the command line */
63 WInput *cmdline;
65 /*** file scope macro definitions ****************************************************************/
67 #define CD_OPERAND_OFFSET 3
69 /*** file scope type declarations ****************************************************************/
71 /*** file scope variables ************************************************************************/
73 /* Color styles command line */
74 static input_colors_t command_colors;
76 /* --------------------------------------------------------------------------------------------- */
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
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.
90 * @param _path string to examine
91 * @return newly allocated string
94 static char *
95 examine_cd (const char *_path)
97 typedef enum
98 { copy_sym, subst_var } state_t;
100 state_t state = copy_sym;
101 char *q;
102 size_t qlen;
103 char *path_tilde, *path;
104 char *p, *r;
106 /* Tilde expansion */
107 path = strutils_shell_unescape (_path);
108 path_tilde = tilde_expand (path);
109 g_free (path);
111 /* Leave space for further expansion */
112 qlen = strlen (path_tilde) + MC_MAXPATHLEN;
113 q = g_malloc (qlen);
115 /* Variable expansion */
116 for (p = path_tilde, r = q; *p != '\0' && r < q + MC_MAXPATHLEN;)
118 switch (state)
120 case copy_sym:
121 if (p[0] == '\\' && p[1] == '$')
123 /* skip backslash */
124 p++;
125 /* copy dollar */
126 *(r++) = *(p++);
128 else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
129 *(r++) = *(p++);
130 else
131 state = subst_var;
132 break;
134 case subst_var:
136 char *s;
137 char c;
138 const char *t;
140 /* skip dollar */
141 p++;
143 if (p[0] != '{')
144 s = NULL;
145 else
147 p++;
148 s = strchr (p, '}');
150 if (s == NULL)
151 s = strchr (p, PATH_SEP);
152 if (s == NULL)
153 s = strchr (p, '\0');
154 c = *s;
155 *s = '\0';
156 t = getenv (p);
157 *s = c;
158 if (t == NULL)
160 *(r++) = '$';
161 if (p[-1] != '$')
162 *(r++) = '{';
164 else
166 size_t tlen;
168 tlen = strlen (t);
170 if (r + tlen < q + MC_MAXPATHLEN)
172 strncpy (r, t, tlen + 1);
173 r += tlen;
175 p = s;
176 if (*s == '}')
177 p++;
180 state = copy_sym;
181 break;
184 default:
185 break;
189 g_free (path_tilde);
191 *r = '\0';
193 return q;
196 /* --------------------------------------------------------------------------------------------- */
198 /* CDPATH handling */
199 static gboolean
200 handle_cdpath (const char *path)
202 gboolean result = FALSE;
204 /* CDPATH handling */
205 if (!IS_PATH_SEP (*path))
207 char *cdpath, *p;
208 char c;
210 cdpath = g_strdup (getenv ("CDPATH"));
211 p = cdpath;
212 c = (p == NULL) ? '\0' : ':';
214 while (!result && c == ':')
216 char *s;
218 s = strchr (p, ':');
219 if (s == NULL)
220 s = strchr (p, '\0');
221 c = *s;
222 *s = '\0';
223 if (*p != '\0')
225 vfs_path_t *r_vpath;
227 r_vpath = vfs_path_build_filename (p, path, (char *) NULL);
228 result = do_cd (r_vpath, cd_parse_command);
229 vfs_path_free (r_vpath);
231 *s = c;
232 p = s + 1;
234 g_free (cdpath);
237 return result;
240 /* --------------------------------------------------------------------------------------------- */
242 /** Handle Enter on the command line
244 * @param lc_cmdline string for handling
245 * @return MSG_HANDLED on sucsess else MSG_NOT_HANDLED
248 static cb_ret_t
249 enter (WInput * lc_cmdline)
251 char *cmd = lc_cmdline->buffer;
253 if (!command_prompt)
254 return MSG_HANDLED;
256 /* Any initial whitespace should be removed at this point */
257 while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n')
258 cmd++;
260 if (!*cmd)
261 return MSG_HANDLED;
263 if (strncmp (cmd, "cd ", 3) == 0 || strcmp (cmd, "cd") == 0)
265 do_cd_command (cmd);
266 input_clean (lc_cmdline);
267 return MSG_HANDLED;
269 else if (strcmp (cmd, "exit") == 0)
271 input_assign_text (lc_cmdline, "");
272 if (!quiet_quit_cmd ())
273 return MSG_NOT_HANDLED;
275 else
277 GString *command;
278 size_t i;
280 if (!vfs_current_is_local ())
282 message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
283 return MSG_NOT_HANDLED;
285 #ifdef ENABLE_SUBSHELL
286 /* Check this early before we clean command line
287 * (will be checked again by shell_execute) */
288 if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
290 message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
291 return MSG_NOT_HANDLED;
293 #endif
294 command = g_string_sized_new (32);
296 for (i = 0; cmd[i] != '\0'; i++)
298 if (cmd[i] != '%')
299 g_string_append_c (command, cmd[i]);
300 else
302 char *s;
304 s = expand_format (NULL, cmd[++i], TRUE);
305 g_string_append (command, s);
306 g_free (s);
310 input_clean (lc_cmdline);
311 shell_execute (command->str, 0);
312 g_string_free (command, TRUE);
314 #ifdef ENABLE_SUBSHELL
315 if ((quit & SUBSHELL_EXIT) != 0)
317 if (quiet_quit_cmd ())
318 return MSG_HANDLED;
320 quit = 0;
321 /* restart subshell */
322 if (mc_global.tty.use_subshell)
323 init_subshell ();
326 if (mc_global.tty.use_subshell)
327 do_load_prompt ();
328 #endif
330 return MSG_HANDLED;
333 /* --------------------------------------------------------------------------------------------- */
336 * Default command line callback
338 * @param w Widget object
339 * @param msg message for handling
340 * @param parm extra parameter such as key code
342 * @return MSG_NOT_HANDLED on fail else MSG_HANDLED
345 static cb_ret_t
346 command_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
348 switch (msg)
350 case MSG_KEY:
351 /* Special case: we handle the enter key */
352 if (parm == '\n')
353 return enter (INPUT (w));
354 /* fall through */
356 default:
357 return input_callback (w, sender, msg, parm, data);
361 /* --------------------------------------------------------------------------------------------- */
362 /*** public functions ****************************************************************************/
363 /* --------------------------------------------------------------------------------------------- */
365 /** Execute the cd command on the command line
367 * @param orig_cmd command for execution
370 void
371 do_cd_command (char *orig_cmd)
373 int len;
374 int operand_pos = CD_OPERAND_OFFSET;
375 const char *cmd;
377 /* Any final whitespace should be removed here
378 (to see why, try "cd fred "). */
379 /* NOTE: I think we should not remove the extra space,
380 that way, we can cd into hidden directories */
381 /* FIXME: what about interpreting quoted strings like the shell.
382 so one could type "cd <tab> M-a <enter>" and it would work. */
383 len = strlen (orig_cmd) - 1;
384 while (len >= 0 && (orig_cmd[len] == ' ' || orig_cmd[len] == '\t' || orig_cmd[len] == '\n'))
386 orig_cmd[len] = 0;
387 len--;
390 cmd = orig_cmd;
391 if (cmd[CD_OPERAND_OFFSET - 1] == 0)
392 cmd = "cd "; /* 0..2 => given text, 3 => \0 */
394 /* allow any amount of white space in front of the path operand */
395 while (cmd[operand_pos] == ' ' || cmd[operand_pos] == '\t')
396 operand_pos++;
398 if (get_current_type () == view_tree)
400 vfs_path_t *new_vpath = NULL;
402 if (cmd[0] == '\0')
404 new_vpath = vfs_path_from_str (mc_config_get_home_dir ());
405 sync_tree (new_vpath);
407 else if (DIR_IS_DOTDOT (cmd + operand_pos))
409 if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
410 strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
412 vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
414 current_panel->cwd_vpath =
415 vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
416 vfs_path_free (tmp_vpath);
418 sync_tree (current_panel->cwd_vpath);
420 else
422 if (IS_PATH_SEP (cmd[operand_pos]))
423 new_vpath = vfs_path_from_str (cmd + operand_pos);
424 else
425 new_vpath =
426 vfs_path_append_new (current_panel->cwd_vpath, cmd + operand_pos,
427 (char *) NULL);
429 sync_tree (new_vpath);
432 vfs_path_free (new_vpath);
434 else
436 char *path;
437 vfs_path_t *q_vpath;
438 gboolean ok;
440 path = examine_cd (&cmd[operand_pos]);
442 if (*path == '\0')
443 q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
444 else
445 q_vpath = vfs_path_from_str_flags (path, VPF_NO_CANON);
447 ok = do_cd (q_vpath, cd_parse_command);
448 if (!ok)
449 ok = handle_cdpath (path);
451 if (!ok)
453 char *d;
455 d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
456 message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), d,
457 unix_error_string (errno));
458 g_free (d);
461 vfs_path_free (q_vpath);
462 g_free (path);
466 /* --------------------------------------------------------------------------------------------- */
468 WInput *
469 command_new (int y, int x, int cols)
471 WInput *cmd;
472 Widget *w;
474 cmd = input_new (y, x, command_colors, cols, "", "cmdline",
475 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
476 | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
477 INPUT_COMPLETE_SHELL_ESC);
478 w = WIDGET (cmd);
479 /* Don't set WOP_SELECTABLE up, otherwise panels will be unselected */
480 widget_set_options (w, WOP_SELECTABLE, FALSE);
481 /* Add our hooks */
482 w->callback = command_callback;
484 return cmd;
487 /* --------------------------------------------------------------------------------------------- */
489 * Set colors for the command line.
492 void
493 command_set_default_colors (void)
495 command_colors[WINPUTC_MAIN] = DEFAULT_COLOR;
496 command_colors[WINPUTC_MARK] = COMMAND_MARK_COLOR;
497 command_colors[WINPUTC_UNCHANGED] = DEFAULT_COLOR;
498 command_colors[WINPUTC_HISTORY] = COMMAND_HISTORY_COLOR;
501 /* --------------------------------------------------------------------------------------------- */
503 * Insert quoted text in input line. The function is meant for the
504 * command line, so the percent sign is quoted as well.
506 * @param in WInput object
507 * @param text string for insertion
508 * @param insert_extra_space add extra space
511 void
512 command_insert (WInput * in, const char *text, gboolean insert_extra_space)
514 char *quoted_text;
516 quoted_text = name_quote (text, TRUE);
517 input_insert (in, quoted_text, insert_extra_space);
518 g_free (quoted_text);
521 /* --------------------------------------------------------------------------------------------- */