1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "cmd_handlers.h"
21 
22 #include <regex.h>
23 
24 #include <curses.h>
25 
26 #include <sys/stat.h> /* gid_t uid_t */
27 
28 #include <assert.h> /* assert() */
29 #include <ctype.h> /* isdigit() */
30 #include <errno.h>
31 #include <signal.h>
32 #include <stddef.h> /* NULL size_t */
33 #include <stdio.h> /* pclose() popen() snprintf() */
34 #include <stdlib.h> /* EXIT_SUCCESS atoi() free() realloc() */
35 #include <string.h> /* strchr() strcmp() strcspn() strcasecmp() strcpy()
36                        strdup() strlen() strrchr() */
37 #include <wctype.h> /* iswspace() */
38 #include <wchar.h> /* wcslen() wcsncmp() */
39 
40 #include "cfg/config.h"
41 #include "cfg/info.h"
42 #include "compat/fs_limits.h"
43 #include "compat/os.h"
44 #include "engine/abbrevs.h"
45 #include "engine/autocmds.h"
46 #include "engine/cmds.h"
47 #include "engine/keys.h"
48 #include "engine/mode.h"
49 #include "engine/options.h"
50 #include "engine/parsing.h"
51 #include "engine/text_buffer.h"
52 #include "engine/var.h"
53 #include "engine/variables.h"
54 #include "int/path_env.h"
55 #include "int/vim.h"
56 #include "modes/dialogs/attr_dialog.h"
57 #include "modes/dialogs/change_dialog.h"
58 #include "modes/dialogs/msg_dialog.h"
59 #include "modes/dialogs/sort_dialog.h"
60 #include "menus/all.h"
61 #include "menus/menus.h"
62 #include "modes/modes.h"
63 #include "modes/wk.h"
64 #include "ui/color_manager.h"
65 #include "ui/color_scheme.h"
66 #include "ui/colors.h"
67 #include "ui/fileview.h"
68 #include "ui/quickview.h"
69 #include "ui/statusbar.h"
70 #include "ui/tabs.h"
71 #include "ui/ui.h"
72 #include "utils/env.h"
73 #include "utils/filter.h"
74 #include "utils/fs.h"
75 #include "utils/hist.h"
76 #include "utils/log.h"
77 #include "utils/matcher.h"
78 #include "utils/matchers.h"
79 #include "utils/path.h"
80 #include "utils/regexp.h"
81 #include "utils/str.h"
82 #include "utils/string_array.h"
83 #include "utils/test_helpers.h"
84 #include "utils/trie.h"
85 #include "utils/utils.h"
86 #include "background.h"
87 #include "bmarks.h"
88 #include "bracket_notation.h"
89 #include "cmd_completion.h"
90 #include "cmd_core.h"
91 #include "compare.h"
92 #include "dir_stack.h"
93 #include "filelist.h"
94 #include "filetype.h"
95 #include "filtering.h"
96 #include "flist_hist.h"
97 #include "flist_pos.h"
98 #include "flist_sel.h"
99 #include "fops_cpmv.h"
100 #include "fops_misc.h"
101 #include "fops_put.h"
102 #include "fops_rename.h"
103 #include "instance.h"
104 #include "macros.h"
105 #include "marks.h"
106 #include "ops.h"
107 #include "opt_handlers.h"
108 #include "registers.h"
109 #include "running.h"
110 #include "trash.h"
111 #include "undo.h"
112 #include "vifm.h"
113 
114 static int goto_cmd(const cmd_info_t *cmd_info);
115 static int emark_cmd(const cmd_info_t *cmd_info);
116 static int alink_cmd(const cmd_info_t *cmd_info);
117 static int apropos_cmd(const cmd_info_t *cmd_info);
118 static int autocmd_cmd(const cmd_info_t *cmd_info);
119 static void aucmd_list_cb(const char event[], const char pattern[], int negated,
120 		const char action[], void *arg);
121 static void aucmd_action_handler(const char action[], void *arg);
122 static int bmark_cmd(const cmd_info_t *cmd_info);
123 static int bmarks_cmd(const cmd_info_t *cmd_info);
124 static int bmgo_cmd(const cmd_info_t *cmd_info);
125 static int bmarks_do(const cmd_info_t *cmd_info, int go);
126 static char * make_tags_list(const cmd_info_t *cmd_info);
127 static char * args_to_csl(const cmd_info_t *cmd_info);
128 static int cabbrev_cmd(const cmd_info_t *cmd_info);
129 static int cnoreabbrev_cmd(const cmd_info_t *cmd_info);
130 static int handle_cabbrevs(const cmd_info_t *cmd_info, int no_remap);
131 static int list_abbrevs(const char prefix[]);
132 static int add_cabbrev(const cmd_info_t *cmd_info, int no_remap);
133 static int cd_cmd(const cmd_info_t *cmd_info);
134 static int cds_cmd(const cmd_info_t *cmd_info);
135 static int change_cmd(const cmd_info_t *cmd_info);
136 static int chmod_cmd(const cmd_info_t *cmd_info);
137 #ifndef _WIN32
138 static int chown_cmd(const cmd_info_t *cmd_info);
139 #endif
140 static int clone_cmd(const cmd_info_t *cmd_info);
141 static int cmap_cmd(const cmd_info_t *cmd_info);
142 static int cnoremap_cmd(const cmd_info_t *cmd_info);
143 static int copy_cmd(const cmd_info_t *cmd_info);
144 static int cquit_cmd(const cmd_info_t *cmd_info);
145 static int cunabbrev_cmd(const cmd_info_t *cmd_info);
146 static int colorscheme_cmd(const cmd_info_t *cmd_info);
147 static int is_colorscheme_assoc_form(const cmd_info_t *cmd_info);
148 static int assoc_colorscheme(const char name[], const char path[]);
149 static void set_colorscheme(char *names[], int count);
150 static int command_cmd(const cmd_info_t *cmd_info);
151 static int compare_cmd(const cmd_info_t *cmd_info);
152 static int copen_cmd(const cmd_info_t *cmd_info);
153 static int parse_compare_properties(const cmd_info_t *cmd_info, CompareType *ct,
154 		ListType *lt, int *single_pane, int *group_ids, int *skip_empty);
155 static int cunmap_cmd(const cmd_info_t *cmd_info);
156 static int delete_cmd(const cmd_info_t *cmd_info);
157 static int delmarks_cmd(const cmd_info_t *cmd_info);
158 static int delbmarks_cmd(const cmd_info_t *cmd_info);
159 static void remove_bmark(const char path[], const char tags[], time_t timestamp,
160 		void *arg);
161 static char * get_bmark_dir(const cmd_info_t *cmd_info);
162 static char * make_bmark_path(const char path[]);
163 static int delsession_cmd(const cmd_info_t *cmd_info);
164 static int dirs_cmd(const cmd_info_t *cmd_info);
165 static int dmap_cmd(const cmd_info_t *cmd_info);
166 static int dnoremap_cmd(const cmd_info_t *cmd_info);
167 static int dialog_map(const cmd_info_t *cmd_info, int no_remap);
168 static int dunmap_cmd(const cmd_info_t *cmd_info);
169 static int echo_cmd(const cmd_info_t *cmd_info);
170 static int edit_cmd(const cmd_info_t *cmd_info);
171 static int else_cmd(const cmd_info_t *cmd_info);
172 static int elseif_cmd(const cmd_info_t *cmd_info);
173 static int empty_cmd(const cmd_info_t *cmd_info);
174 static int endif_cmd(const cmd_info_t *cmd_info);
175 static int exe_cmd(const cmd_info_t *cmd_info);
176 static char * try_eval_arglist(const cmd_info_t *cmd_info);
177 static int file_cmd(const cmd_info_t *cmd_info);
178 static int filetype_cmd(const cmd_info_t *cmd_info);
179 static int filextype_cmd(const cmd_info_t *cmd_info);
180 static int fileviewer_cmd(const cmd_info_t *cmd_info);
181 static int add_assoc(const cmd_info_t *cmd_info, int viewer, int for_x);
182 static int filter_cmd(const cmd_info_t *cmd_info);
183 static int update_filter(view_t *view, const cmd_info_t *cmd_info);
184 static void display_filters_info(const view_t *view);
185 static char * get_filter_info(const char name[], const filter_t *filter);
186 static char * get_matcher_info(const char name[], const matcher_t *matcher);
187 static int set_view_filter(view_t *view, const char filter[],
188 		const char fallback[], int invert);
189 static int get_filter_inversion_state(const cmd_info_t *cmd_info);
190 static int find_cmd(const cmd_info_t *cmd_info);
191 static int finish_cmd(const cmd_info_t *cmd_info);
192 static int goto_path_cmd(const cmd_info_t *cmd_info);
193 static int grep_cmd(const cmd_info_t *cmd_info);
194 static int help_cmd(const cmd_info_t *cmd_info);
195 static int hideui_cmd(const cmd_info_t *cmd_info);
196 static int highlight_cmd(const cmd_info_t *cmd_info);
197 static int highlight_clear(const cmd_info_t *cmd_info);
198 static int highlight_file(const cmd_info_t *cmd_info);
199 static void display_file_highlights(const matchers_t *matchers);
200 static int highlight_group(const cmd_info_t *cmd_info);
201 static const char * get_all_highlights(void);
202 static const char * get_group_str(int group, const col_attr_t *col);
203 static const char * get_file_hi_str(const matchers_t *matchers,
204 		const col_attr_t *col);
205 static const char * get_hi_str(const char title[], const col_attr_t *col);
206 static int parse_file_highlight(const cmd_info_t *cmd_info,
207 		col_attr_t *color);
208 static int try_parse_color_name_value(const char str[], int fg,
209 		col_attr_t *color);
210 static int parse_color_name_value(const char str[], int fg, int *attr);
211 static int get_attrs(const char *text);
212 static int history_cmd(const cmd_info_t *cmd_info);
213 static int histnext_cmd(const cmd_info_t *cmd_info);
214 static int histprev_cmd(const cmd_info_t *cmd_info);
215 static int if_cmd(const cmd_info_t *cmd_info);
216 static int eval_if_condition(const cmd_info_t *cmd_info);
217 static int invert_cmd(const cmd_info_t *cmd_info);
218 static void print_inversion_state(char state_type);
219 static void invert_state(char state_type);
220 static int jobs_cmd(const cmd_info_t *cmd_info);
221 static int let_cmd(const cmd_info_t *cmd_info);
222 static int locate_cmd(const cmd_info_t *cmd_info);
223 static int ls_cmd(const cmd_info_t *cmd_info);
224 static int lstrash_cmd(const cmd_info_t *cmd_info);
225 static int map_cmd(const cmd_info_t *cmd_info);
226 static int mark_cmd(const cmd_info_t *cmd_info);
227 static int marks_cmd(const cmd_info_t *cmd_info);
228 #ifndef _WIN32
229 static int media_cmd(const cmd_info_t *cmd_info);
230 #endif
231 static int messages_cmd(const cmd_info_t *cmd_info);
232 static int mkdir_cmd(const cmd_info_t *cmd_info);
233 static int mmap_cmd(const cmd_info_t *cmd_info);
234 static int mnoremap_cmd(const cmd_info_t *cmd_info);
235 static int move_cmd(const cmd_info_t *cmd_info);
236 static int cpmv_cmd(const cmd_info_t *cmd_info, int move);
237 static int munmap_cmd(const cmd_info_t *cmd_info);
238 static int nmap_cmd(const cmd_info_t *cmd_info);
239 static int nnoremap_cmd(const cmd_info_t *cmd_info);
240 static int nohlsearch_cmd(const cmd_info_t *cmd_info);
241 static int noremap_cmd(const cmd_info_t *cmd_info);
242 static int map_or_remap(const cmd_info_t *cmd_info, int no_remap);
243 static int normal_cmd(const cmd_info_t *cmd_info);
244 static int nunmap_cmd(const cmd_info_t *cmd_info);
245 static int only_cmd(const cmd_info_t *cmd_info);
246 static int popd_cmd(const cmd_info_t *cmd_info);
247 static int pushd_cmd(const cmd_info_t *cmd_info);
248 static int put_cmd(const cmd_info_t *cmd_info);
249 static int pwd_cmd(const cmd_info_t *cmd_info);
250 static int qmap_cmd(const cmd_info_t *cmd_info);
251 static int qnoremap_cmd(const cmd_info_t *cmd_info);
252 static int qunmap_cmd(const cmd_info_t *cmd_info);
253 static int redraw_cmd(const cmd_info_t *cmd_info);
254 static int registers_cmd(const cmd_info_t *cmd_info);
255 static int regular_cmd(const cmd_info_t *cmd_info);
256 static int rename_cmd(const cmd_info_t *cmd_info);
257 static int restart_cmd(const cmd_info_t *cmd_info);
258 static int restore_cmd(const cmd_info_t *cmd_info);
259 static int rlink_cmd(const cmd_info_t *cmd_info);
260 static int link_cmd(const cmd_info_t *cmd_info, int absolute);
261 static int screen_cmd(const cmd_info_t *cmd_info);
262 static int select_cmd(const cmd_info_t *cmd_info);
263 static int session_cmd(const cmd_info_t *cmd_info);
264 static int restart_into_session(const char session[], int full);
265 static int set_cmd(const cmd_info_t *cmd_info);
266 static int setlocal_cmd(const cmd_info_t *cmd_info);
267 static int setglobal_cmd(const cmd_info_t *cmd_info);
268 static int shell_cmd(const cmd_info_t *cmd_info);
269 static int siblnext_cmd(const cmd_info_t *cmd_info);
270 static int siblprev_cmd(const cmd_info_t *cmd_info);
271 static int sort_cmd(const cmd_info_t *cmd_info);
272 static int source_cmd(const cmd_info_t *cmd_info);
273 static int split_cmd(const cmd_info_t *cmd_info);
274 static int substitute_cmd(const cmd_info_t *cmd_info);
275 static int sync_cmd(const cmd_info_t *cmd_info);
276 static int sync_selectively(const cmd_info_t *cmd_info);
277 static int parse_sync_properties(const cmd_info_t *cmd_info, int *location,
278 		int *cursor_pos, int *local_options, int *filters, int *filelist,
279 		int *tree);
280 static void sync_location(const char path[], int cv, int sync_cursor_pos,
281 		int sync_filters, int tree);
282 static void sync_local_opts(int defer_slow);
283 static void sync_filters(void);
284 static int tabclose_cmd(const cmd_info_t *cmd_info);
285 static int tabmove_cmd(const cmd_info_t *cmd_info);
286 static int tabname_cmd(const cmd_info_t *cmd_info);
287 static int tabnew_cmd(const cmd_info_t *cmd_info);
288 static int tabnext_cmd(const cmd_info_t *cmd_info);
289 static int tabonly_cmd(const cmd_info_t *cmd_info);
290 static int tabprevious_cmd(const cmd_info_t *cmd_info);
291 static int touch_cmd(const cmd_info_t *cmd_info);
292 static int get_at(const view_t *view, const cmd_info_t *cmd_info);
293 static int tr_cmd(const cmd_info_t *cmd_info);
294 static int trashes_cmd(const cmd_info_t *cmd_info);
295 static int tree_cmd(const cmd_info_t *cmd_info);
296 static int undolist_cmd(const cmd_info_t *cmd_info);
297 static int unlet_cmd(const cmd_info_t *cmd_info);
298 static int unmap_cmd(const cmd_info_t *cmd_info);
299 static int unselect_cmd(const cmd_info_t *cmd_info);
300 static int view_cmd(const cmd_info_t *cmd_info);
301 static int vifm_cmd(const cmd_info_t *cmd_info);
302 static int vmap_cmd(const cmd_info_t *cmd_info);
303 static int vnoremap_cmd(const cmd_info_t *cmd_info);
304 #ifdef _WIN32
305 static int volumes_cmd(const cmd_info_t *cmd_info);
306 #endif
307 static int vsplit_cmd(const cmd_info_t *cmd_info);
308 static int do_split(const cmd_info_t *cmd_info, SPLIT orientation);
309 static int do_map(const cmd_info_t *cmd_info, const char map_type[], int mode,
310 		int no_remap);
311 static int parse_map_args(const char **args);
312 static int vunmap_cmd(const cmd_info_t *cmd_info);
313 static int do_unmap(const char *keys, int mode);
314 static int wincmd_cmd(const cmd_info_t *cmd_info);
315 static int windo_cmd(const cmd_info_t *cmd_info);
316 static int winrun_cmd(const cmd_info_t *cmd_info);
317 static int winrun(view_t *view, const char cmd[]);
318 static int write_cmd(const cmd_info_t *cmd_info);
319 static int qall_cmd(const cmd_info_t *cmd_info);
320 static int quit_cmd(const cmd_info_t *cmd_info);
321 static int wq_cmd(const cmd_info_t *cmd_info);
322 static int wqall_cmd(const cmd_info_t *cmd_info);
323 static int yank_cmd(const cmd_info_t *cmd_info);
324 static int get_reg_and_count(const cmd_info_t *cmd_info, int *reg);
325 static int get_reg(const char arg[], int *reg);
326 static int usercmd_cmd(const cmd_info_t* cmd_info);
327 static int parse_bg_mark(char cmd[]);
328 TSTATIC void cmds_drop_state(void);
329 
330 const cmd_add_t cmds_list[] = {
331 	{ .name = "",                  .abbr = NULL,    .id = COM_GOTO,
332 	  .descr = "put cursor at specific line",
333 	  .flags = HAS_RANGE | HAS_COMMENT,
334 	  .handler = &goto_cmd,        .min_args = 0,   .max_args = 0, },
335 	{ .name = "!",                 .abbr = NULL,    .id = COM_EXECUTE,
336 	  .descr = "execute external command",
337 	  .flags = HAS_EMARK | HAS_RANGE | HAS_BG_FLAG | HAS_MACROS_FOR_SHELL
338 	         | HAS_SELECTION_SCOPE,
339 	  .handler = &emark_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
340 	{ .name = "alink",             .abbr = NULL,    .id = COM_ALINK,
341 	  .descr = "create absolute links",
342 	  .flags = HAS_EMARK | HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT
343 	         | HAS_QMARK_NO_ARGS | HAS_SELECTION_SCOPE,
344 	  .handler = &alink_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
345 	{ .name = "apropos",           .abbr = NULL,    .id = -1,
346 	  .descr = "query apropos results",
347 	  .flags = 0,
348 	  .handler = &apropos_cmd,     .min_args = 0,   .max_args = NOT_DEF, },
349 	{ .name = "autocmd",           .abbr = "au",    .id = COM_AUTOCMD,
350 	  .descr = "manage autocommands",
351 	  .flags = HAS_EMARK | HAS_QUOTED_ARGS,
352 	  .handler = &autocmd_cmd,     .min_args = 0,   .max_args = NOT_DEF, },
353 	{ .name = "bmark",             .abbr = NULL,    .id = COM_BMARKS,
354 	  .descr = "set bookmark",
355 	  .flags = HAS_EMARK | HAS_QUOTED_ARGS | HAS_COMMENT,
356 	  .handler = &bmark_cmd,       .min_args = 1,   .max_args = NOT_DEF, },
357 	{ .name = "bmarks",            .abbr = NULL,    .id = COM_BMARKS,
358 	  .descr = "list bookmarks",
359 	  .flags = HAS_COMMENT,
360 	  .handler = &bmarks_cmd,      .min_args = 0,   .max_args = NOT_DEF, },
361 	{ .name = "bmgo",              .abbr = NULL,    .id = COM_BMARKS,
362 	  .descr = "navigate to a bookmark",
363 	  .flags = HAS_COMMENT,
364 	  .handler = &bmgo_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
365 	{ .name = "cabbrev",           .abbr = "ca",    .id = COM_CABBR,
366 	  .descr = "display/create cmdline abbrevs",
367 	  .flags = 0,
368 	  .handler = &cabbrev_cmd,     .min_args = 0,   .max_args = NOT_DEF, },
369 	{ .name = "cnoreabbrev",       .abbr = "cnorea", .id = COM_CABBR,
370 	  .descr = "display/create noremap cmdline abbrevs",
371 	  .flags = 0,
372 	  .handler = &cnoreabbrev_cmd, .min_args = 0,   .max_args = NOT_DEF, },
373 	{ .name = "cd",                .abbr = NULL,    .id = COM_CD,
374 	  .descr = "navigate to a directory",
375 	  .flags = HAS_EMARK | HAS_QUOTED_ARGS | HAS_COMMENT | HAS_MACROS_FOR_CMD
376 	         | HAS_ENVVARS,
377 	  .handler = &cd_cmd,          .min_args = 0,   .max_args = 2, },
378 	{ .name = "cds",               .abbr = NULL,    .id = COM_CDS,
379 	  .descr = "navigate to path obtained by substitution in current path",
380 	  .flags = HAS_EMARK | HAS_REGEXP_ARGS | HAS_CUST_SEP,
381 	  .handler = &cds_cmd,         .min_args = 2,   .max_args = 3, },
382 	{ .name = "change",            .abbr = "c",     .id = -1,
383 	  .descr = "change file traits",
384 	  .flags = HAS_COMMENT,
385 	  .handler = &change_cmd,      .min_args = 0,   .max_args = 0, },
386 #ifndef _WIN32
387 	{ .name = "chmod",             .abbr = NULL,    .id = -1,
388 	  .descr = "change permissions",
389 	  .flags = HAS_EMARK | HAS_RANGE | HAS_COMMENT | HAS_SELECTION_SCOPE,
390 	  .handler = &chmod_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
391 	{ .name = "chown",             .abbr = NULL,    .id = COM_CHOWN,
392 	  .descr = "change owner/group",
393 	  .flags = HAS_RANGE | HAS_COMMENT | HAS_SELECTION_SCOPE,
394 	  .handler = &chown_cmd,       .min_args = 0,   .max_args = 1, },
395 #else
396 	{ .name = "chmod",             .abbr = NULL,    .id = -1,
397 	  .descr = "change attributes",
398 	  .flags = HAS_EMARK | HAS_RANGE | HAS_COMMENT | HAS_SELECTION_SCOPE,
399 	  .handler = &chmod_cmd,       .min_args = 0,   .max_args = 0, },
400 #endif
401 	{ .name = "clone",             .abbr = NULL,    .id = COM_CLONE,
402 	  .descr = "clone selection",
403 	  .flags = HAS_EMARK | HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT
404 	         | HAS_QMARK_NO_ARGS | HAS_MACROS_FOR_CMD | HAS_SELECTION_SCOPE,
405 	  .handler = &clone_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
406 	{ .name = "cmap",              .abbr = "cm",    .id = COM_CMAP,
407 	  .descr = "map keys in cmdline mode",
408 	  .flags = HAS_RAW_ARGS,
409 	  .handler = &cmap_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
410 	{ .name = "cnoremap",          .abbr = "cno",   .id = COM_CNOREMAP,
411 	  .descr = "noremap keys in cmdline mode",
412 	  .flags = HAS_RAW_ARGS,
413 	  .handler = &cnoremap_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
414 	{ .name = "colorscheme",       .abbr = "colo",  .id = COM_COLORSCHEME,
415 	  .descr = "display/select color schemes",
416 	  .flags = HAS_QUOTED_ARGS | HAS_COMMENT | HAS_QMARK_NO_ARGS,
417 	  .handler = &colorscheme_cmd, .min_args = 0,   .max_args = NOT_DEF, },
418 	{ .name = "command",           .abbr = "com",   .id = COM_COMMAND,
419 	  .descr = "display/define :commands",
420 	  .flags = HAS_EMARK,
421 	  .handler = &command_cmd,     .min_args = 0,   .max_args = NOT_DEF, },
422 	{ .name = "compare",           .abbr = NULL,    .id = COM_COMPARE,
423 	  .descr = "compare directories in two panes",
424 	  .flags = HAS_COMMENT,
425 	  .handler = &compare_cmd,     .min_args = 0,   .max_args = NOT_DEF, },
426 	{ .name = "copen",             .abbr = "cope",  .id = -1,
427 	  .descr = "reopen last displayed navigation menu",
428 	  .flags = HAS_COMMENT,
429 	  .handler = &copen_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
430 	{ .name = "copy",              .abbr = "co",    .id = COM_COPY,
431 	  .descr = "copy files",
432 	  .flags = HAS_EMARK | HAS_RANGE | HAS_BG_FLAG | HAS_QUOTED_ARGS | HAS_COMMENT
433 	         | HAS_QMARK_NO_ARGS | HAS_SELECTION_SCOPE,
434 	  .handler = &copy_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
435 	{ .name = "cquit",             .abbr = "cq",    .id = -1,
436 	  .descr = "quit with error",
437 	  .flags = HAS_EMARK | HAS_COMMENT,
438 	  .handler = &cquit_cmd,       .min_args = 0,   .max_args = 0, },
439 	{ .name = "cunabbrev",         .abbr = "cuna",  .id = COM_CABBR,
440 	  .descr = "remove cmdline abbrev",
441 	  .flags = 0,
442 	  .handler = &cunabbrev_cmd,   .min_args = 1,   .max_args = NOT_DEF, },
443 	{ .name = "cunmap",            .abbr = "cu",    .id = -1,
444 	  .descr = "unmap user keys in cmdline mode",
445 	  .flags = HAS_RAW_ARGS,
446 	  .handler = &cunmap_cmd,      .min_args = 1,   .max_args = 1, },
447 	{ .name = "delete",            .abbr = "d",     .id = -1,
448 	  .descr = "delete files",
449 	  .flags = HAS_EMARK | HAS_RANGE | HAS_BG_FLAG | HAS_SELECTION_SCOPE,
450 	  .handler = &delete_cmd,      .min_args = 0,   .max_args = 2, },
451 	{ .name = "delmarks",          .abbr = "delm",  .id = -1,
452 	  .descr = "delete marks",
453 	  .flags = HAS_EMARK | HAS_COMMENT,
454 	  .handler = &delmarks_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
455 	{ .name = "delbmarks",         .abbr = NULL,    .id = COM_DELBMARKS,
456 	  .descr = "delete bookmarks",
457 	  .flags = HAS_EMARK | HAS_COMMENT,
458 	  .handler = &delbmarks_cmd,   .min_args = 0,   .max_args = NOT_DEF, },
459 	{ .name = "delsession",        .abbr = NULL,    .id = COM_DELSESSION,
460 	  .descr = "remove a session",
461 	  .flags = HAS_COMMENT,
462 	  .handler = &delsession_cmd,  .min_args = 1,   .max_args = 1, },
463 	{ .name = "display",           .abbr = "di",    .id = -1,
464 	  .descr = "display registers",
465 	  .flags = 0,
466 	  .handler = &registers_cmd,   .min_args = 0,   .max_args = NOT_DEF, },
467 	{ .name = "dirs",              .abbr = NULL,    .id = -1,
468 	  .descr = "display directory stack",
469 	  .flags = HAS_COMMENT,
470 	  .handler = &dirs_cmd,        .min_args = 0,   .max_args = 0, },
471 	{ .name = "dmap",              .abbr = NULL,    .id = COM_DMAP,
472 	  .descr = "map keys in dialog modes",
473 	  .flags = HAS_RAW_ARGS,
474 	  .handler = &dmap_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
475 	{ .name = "dnoremap",          .abbr = NULL,    .id = COM_DNOREMAP,
476 	  .descr = "noremap keys in dialog modes",
477 	  .flags = HAS_RAW_ARGS,
478 	  .handler = &dnoremap_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
479 	{ .name = "dunmap",            .abbr = NULL,    .id = -1,
480 	  .descr = "unmap user keys in dialog modes",
481 	  .flags = HAS_RAW_ARGS,
482 	  .handler = &dunmap_cmd,      .min_args = 1,   .max_args = 1, },
483 	{ .name = "echo",             .abbr = "ec",     .id = COM_ECHO,
484 	  .descr = "eval and print expressions",
485 	  .flags = 0,
486 	  .handler = &echo_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
487 	{ .name = "edit",              .abbr = "e",     .id = COM_EDIT,
488 	  .descr = "edit files",
489 	  .flags = HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT | HAS_MACROS_FOR_CMD
490 	         | HAS_SELECTION_SCOPE | HAS_ENVVARS,
491 	  .handler = &edit_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
492 	{ .name = "else",              .abbr = "el",    .id = COM_ELSE_STMT,
493 	  .descr = "start alternative control-flow",
494 	  .flags = HAS_COMMENT,
495 	  .handler = &else_cmd,        .min_args = 0,   .max_args = 0, },
496 	/* engine/parsing unit handles comments to resolve parsing ambiguity. */
497 	{ .name = "elseif",            .abbr = "elsei", .id = COM_ELSEIF_STMT,
498 	  .descr = "conditional control-flow branching",
499 	  .flags = 0,
500 	  .handler = &elseif_cmd,      .min_args = 1,   .max_args = NOT_DEF, },
501 	{ .name = "empty",             .abbr = NULL,    .id = -1,
502 	  .descr = "start emptying trashes in background",
503 	  .flags = HAS_COMMENT,
504 	  .handler = &empty_cmd,       .min_args = 0,   .max_args = 0, },
505 	{ .name = "endif",             .abbr = "en",    .id = COM_ENDIF_STMT,
506 	  .descr = "if-else construction terminator",
507 	  .flags = HAS_COMMENT,
508 	  .handler = &endif_cmd,       .min_args = 0,   .max_args = 0, },
509 	{ .name = "execute",           .abbr = "exe",   .id = COM_EXE,
510 	  .descr = "execute expressions as :commands",
511 	  .flags = 0,
512 	  .handler = &exe_cmd,         .min_args = 0,   .max_args = NOT_DEF, },
513 	{ .name = "exit",              .abbr = "exi",   .id = -1,
514 	  .descr = "exit the application",
515 	  .flags = HAS_EMARK | HAS_COMMENT,
516 	  .handler = &quit_cmd,        .min_args = 0,   .max_args = 0, },
517 	{ .name = "file",              .abbr = "f",     .id = COM_FILE,
518 	  .descr = "display/apply file associations",
519 	  .flags = HAS_BG_FLAG | HAS_COMMENT,
520 	  .handler = &file_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
521 	{ .name = "filetype",          .abbr = "filet", .id = COM_FILETYPE,
522 	  .descr = "display/define file associations",
523 	  .flags = 0,
524 	  .handler = &filetype_cmd,    .min_args = 1,   .max_args = NOT_DEF, },
525 	{ .name = "fileviewer",        .abbr = "filev", .id = COM_FILEVIEWER,
526 	  .descr = "display/define file viewers",
527 	  .flags = 0,
528 	  .handler = &fileviewer_cmd,  .min_args = 1,   .max_args = NOT_DEF, },
529 	{ .name = "filextype",         .abbr = "filex", .id = COM_FILEXTYPE,
530 	  .descr = "display/define file associations in X",
531 	  .flags = 0,
532 	  .handler = &filextype_cmd,   .min_args = 1,   .max_args = NOT_DEF, },
533 	{ .name = "filter",            .abbr = NULL,    .id = COM_FILTER,
534 	  .descr = "set/reset file filter",
535 	  .flags = HAS_EMARK | HAS_REGEXP_ARGS | HAS_QMARK_NO_ARGS,
536 	  .handler = &filter_cmd,      .min_args = 0,   .max_args = NOT_DEF, },
537 	{ .name = "find",              .abbr = "fin",   .id = COM_FIND,
538 	  .descr = "query find results",
539 	  .flags = HAS_RANGE | HAS_QUOTED_ARGS | HAS_MACROS_FOR_CMD
540 	         | HAS_SELECTION_SCOPE,
541 	  .handler = &find_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
542 	{ .name = "finish",            .abbr = "fini",  .id = -1,
543 	  .descr = "stop script processing",
544 	  .flags = HAS_COMMENT,
545 	  .handler = &finish_cmd,      .min_args = 0,   .max_args = 0, },
546 	{ .name = "goto",              .abbr = "go",    .id = COM_GOTO_PATH,
547 	  .descr = "navigate to specified file/directory",
548 	  .flags = HAS_ENVVARS | HAS_COMMENT | HAS_MACROS_FOR_CMD | HAS_QUOTED_ARGS,
549 	  .handler = &goto_path_cmd,   .min_args = 1,   .max_args = 1, },
550 	{ .name = "grep",              .abbr = "gr",    .id = COM_GREP,
551 	  .descr = "query grep results",
552 	  .flags = HAS_EMARK | HAS_RANGE | HAS_SELECTION_SCOPE,
553 	  .handler = &grep_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
554 	{ .name = "help",              .abbr = "h",     .id = COM_HELP,
555 	  .descr = "display help",
556 	  .flags = HAS_QUOTED_ARGS,
557 	  .handler = &help_cmd,        .min_args = 0,   .max_args = 1, },
558 	{ .name = "hideui",            .abbr = NULL,    .id = -1,
559 	  .descr = "hide interface to show previous commands' output",
560 	  .flags = HAS_COMMENT,
561 	  .handler = &hideui_cmd,      .min_args = 0,   .max_args = 0, },
562 	{ .name = "highlight",         .abbr = "hi",    .id = COM_HIGHLIGHT,
563 	  .descr = "display/define TUI highlighting",
564 	  .flags = HAS_COMMENT,
565 	  .handler = &highlight_cmd,   .min_args = 0,   .max_args = 4, },
566 	{ .name = "history",           .abbr = "his",   .id = COM_HISTORY,
567 	  .descr = "display/use history items",
568 	  .flags = HAS_QUOTED_ARGS | HAS_COMMENT,
569 	  .handler = &history_cmd,     .min_args = 0,   .max_args = 1, },
570 	{ .name = "histnext",          .abbr = NULL,    .id = -1,
571 	  .descr = "go forward through directory history",
572 	  .flags = HAS_COMMENT,
573 	  .handler = &histnext_cmd,    .min_args = 0,   .max_args = 0, },
574 	{ .name = "histprev",          .abbr = NULL,    .id = -1,
575 	  .descr = "go backward through directory history",
576 	  .flags = HAS_COMMENT,
577 	  .handler = &histprev_cmd,    .min_args = 0,   .max_args = 0, },
578 	/* engine/parsing unit handles comments to resolve parsing ambiguity. */
579 	{ .name = "if",                .abbr = NULL,    .id = COM_IF_STMT,
580 	  .descr = "start conditional statement",
581 	  .flags = 0,
582 	  .handler = &if_cmd,          .min_args = 1,   .max_args = NOT_DEF, },
583 	{ .name = "invert",            .abbr = NULL,    .id = COM_INVERT,
584 	  .descr = "invert filter/selection/sorting",
585 	  .flags = HAS_COMMENT | HAS_QMARK_WITH_ARGS,
586 	  .handler = &invert_cmd,      .min_args = 0,   .max_args = 1, },
587 	{ .name = "jobs",              .abbr = NULL,    .id = -1,
588 	  .descr = "display active jobs",
589 	  .flags = HAS_COMMENT,
590 	  .handler = &jobs_cmd,        .min_args = 0,   .max_args = 0, },
591 	/* engine/parsing unit handles comments to resolve parsing ambiguity. */
592 	{ .name = "let",               .abbr = NULL,    .id = COM_LET,
593 	  .descr = "assign variables",
594 	  .flags = 0,
595 	  .handler = &let_cmd,         .min_args = 1,   .max_args = NOT_DEF, },
596 	{ .name = "locate",            .abbr = NULL,    .id = -1,
597 	  .descr = "query locate results",
598 	  .flags = 0,
599 	  .handler = &locate_cmd,      .min_args = 0,   .max_args = NOT_DEF, },
600 	{ .name = "ls",                .abbr = NULL,    .id = -1,
601 	  .descr = "list terminal multiplexer windows",
602 	  .flags = HAS_COMMENT,
603 	  .handler = &ls_cmd,          .min_args = 0,   .max_args = 0, },
604 	{ .name = "lstrash",           .abbr = NULL,    .id = -1,
605 	  .descr = "list files in trashes",
606 	  .flags = HAS_COMMENT,
607 	  .handler = &lstrash_cmd,     .min_args = 0,   .max_args = 0, },
608 	{ .name = "map",               .abbr = NULL,    .id = COM_MAP,
609 	  .descr = "map keys in normal and visual modes",
610 	  .flags = HAS_EMARK | HAS_RAW_ARGS,
611 	  .handler = &map_cmd,         .min_args = 2,   .max_args = NOT_DEF, },
612 	{ .name = "mark",              .abbr = "ma",    .id = -1,
613 	  .descr = "set mark",
614 	  .flags = HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT | HAS_QMARK_WITH_ARGS
615 	         | HAS_MACROS_FOR_CMD,
616 	  .handler = &mark_cmd,        .min_args = 1,   .max_args = 3, },
617 	{ .name = "marks",             .abbr = NULL,    .id = -1,
618 	  .descr = "display marks",
619 	  .flags = HAS_COMMENT,
620 	  .handler = &marks_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
621 #ifndef _WIN32
622 	{ .name = "media",             .abbr = NULL,    .id = -1,
623 	  .descr = "list and manage media devices",
624 	  .flags = HAS_COMMENT,
625 	  .handler = &media_cmd,       .min_args = 0,   .max_args = 0, },
626 #endif
627 	{ .name = "messages",          .abbr = "mes",   .id = -1,
628 	  .descr = "display previous status bar messages",
629 	  .flags = HAS_COMMENT,
630 	  .handler = &messages_cmd,    .min_args = 0,   .max_args = 0, },
631 	{ .name = "mkdir",             .abbr = NULL,    .id = COM_MKDIR,
632 	  .descr = "create directories",
633 	  .flags = HAS_EMARK | HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT
634 	         | HAS_MACROS_FOR_CMD,
635 	  .handler = &mkdir_cmd,       .min_args = 1,   .max_args = NOT_DEF, },
636 	{ .name = "mmap",              .abbr = "mm",    .id = COM_MMAP,
637 	  .descr = "map keys in menu mode",
638 	  .flags = HAS_RAW_ARGS,
639 	  .handler = &mmap_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
640 	{ .name = "mnoremap",          .abbr = "mno",   .id = COM_MNOREMAP,
641 	  .descr = "noremap keys in menu mode",
642 	  .flags = HAS_RAW_ARGS,
643 	  .handler = &mnoremap_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
644 	{ .name = "move",              .abbr = "m",     .id = COM_MOVE,
645 	  .descr = "move files",
646 	  .flags = HAS_EMARK | HAS_RANGE | HAS_BG_FLAG | HAS_QUOTED_ARGS | HAS_COMMENT
647 	         | HAS_QMARK_NO_ARGS | HAS_SELECTION_SCOPE,
648 	  .handler = &move_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
649 	{ .name = "munmap",            .abbr = "mu",    .id = -1,
650 	  .descr = "unmap user keys in menu mode",
651 	  .flags = HAS_RAW_ARGS,
652 	  .handler = &munmap_cmd,      .min_args = 1,   .max_args = 1, },
653 	{ .name = "nmap",              .abbr = "nm",    .id = COM_NMAP,
654 	  .descr = "map keys in normal mode",
655 	  .flags = HAS_RAW_ARGS,
656 	  .handler = &nmap_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
657 	{ .name = "nnoremap",          .abbr = "nn",    .id = COM_NNOREMAP,
658 	  .descr = "noremap keys in normal mode",
659 	  .flags = HAS_RAW_ARGS,
660 	  .handler = &nnoremap_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
661 	{ .name = "nohlsearch",        .abbr = "noh",   .id = -1,
662 	  .descr = "reset highlighting of search matches",
663 	  .flags = HAS_COMMENT,
664 	  .handler = &nohlsearch_cmd,  .min_args = 0,   .max_args = 0, },
665 	{ .name = "noremap",           .abbr = "no",    .id = COM_NOREMAP,
666 	  .descr = "noremap keys in normal and visual modes",
667 	  .flags = HAS_EMARK | HAS_RAW_ARGS,
668 	  .handler = &noremap_cmd,     .min_args = 2,   .max_args = NOT_DEF, },
669 	{ .name = "normal",            .abbr = "norm",  .id = COM_NORMAL,
670 	  .descr = "emulate keys typed in normal mode",
671 	  .flags = HAS_EMARK,
672 	  .handler = &normal_cmd,      .min_args = 1,   .max_args = NOT_DEF, },
673 	{ .name = "nunmap",            .abbr = "nun",   .id = -1,
674 	  .descr = "unmap user keys in normal mode",
675 	  .flags = HAS_RAW_ARGS,
676 	  .handler = &nunmap_cmd,      .min_args = 1,   .max_args = 1, },
677 	{ .name = "only",              .abbr = "on",    .id = -1,
678 	  .descr = "switch to single-view mode",
679 	  .flags = HAS_COMMENT,
680 	  .handler = &only_cmd,        .min_args = 0,   .max_args = 0, },
681 	{ .name = "popd",              .abbr = NULL,    .id = -1,
682 	  .descr = "pop top of directory stack",
683 	  .flags = HAS_COMMENT,
684 	  .handler = &popd_cmd,        .min_args = 0,   .max_args = 0, },
685 	{ .name = "pushd",             .abbr = NULL,    .id = COM_PUSHD,
686 	  .descr = "push onto directory stack",
687 	  .flags = HAS_EMARK | HAS_QUOTED_ARGS | HAS_COMMENT | HAS_ENVVARS,
688 	  .handler = &pushd_cmd,       .min_args = 0,   .max_args = 2, },
689 	{ .name = "put",               .abbr = "pu",    .id = -1,
690 	  .descr = "paste files from a register",
691 	  .flags = HAS_EMARK | HAS_RANGE | HAS_BG_FLAG,
692 	  .handler = &put_cmd,         .min_args = 0,   .max_args = 1, },
693 	{ .name = "pwd",               .abbr = "pw",    .id = -1,
694 	  .descr = "display current location",
695 	  .flags = HAS_COMMENT,
696 	  .handler = &pwd_cmd,         .min_args = 0,   .max_args = 0, },
697 	{ .name = "qall",              .abbr = "qa",    .id = -1,
698 	  .descr = "close all tabs and exit the application",
699 	  .flags = HAS_EMARK | HAS_COMMENT,
700 	  .handler = &qall_cmd,        .min_args = 0,   .max_args = 0, },
701 	{ .name = "qmap",              .abbr = "qm",    .id = COM_QMAP,
702 	  .descr = "map keys in preview mode",
703 	  .flags = HAS_RAW_ARGS,
704 	  .handler = &qmap_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
705 	{ .name = "qnoremap",          .abbr = "qno",   .id = COM_QNOREMAP,
706 	  .descr = "noremap keys in preview mode",
707 	  .flags = HAS_RAW_ARGS,
708 	  .handler = &qnoremap_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
709 	{ .name = "quit",              .abbr = "q",     .id = -1,
710 	  .descr = "close a tab or exit the application",
711 	  .flags = HAS_EMARK | HAS_COMMENT,
712 	  .handler = &quit_cmd,        .min_args = 0,   .max_args = 0, },
713 	{ .name = "qunmap",            .abbr = "qun",   .id = -1,
714 	  .descr = "unmap user keys in preview mode",
715 	  .flags = HAS_RAW_ARGS,
716 	  .handler = &qunmap_cmd,      .min_args = 1,   .max_args = 1, },
717 	{ .name = "redraw",            .abbr = "redr",  .id = -1,
718 	  .descr = "force screen redraw",
719 	  .flags = HAS_COMMENT,
720 	  .handler = &redraw_cmd,      .min_args = 0,   .max_args = 0, },
721 	{ .name = "registers",         .abbr = "reg",   .id = -1,
722 	  .descr = "display registers",
723 	  .flags = 0,
724 	  .handler = &registers_cmd,   .min_args = 0,   .max_args = NOT_DEF, },
725 	{ .name = "regular",           .abbr = NULL,    .id = -1,
726 	  .descr = "switch to regular view leaving custom view",
727 	  .flags = HAS_COMMENT,
728 	  .handler = &regular_cmd,     .min_args = 0,   .max_args = 0, },
729 	{ .name = "rename",            .abbr = NULL,    .id = COM_RENAME,
730 	  .descr = "rename files",
731 	  .flags = HAS_EMARK | HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT
732 	         | HAS_SELECTION_SCOPE,
733 	  .handler = &rename_cmd,      .min_args = 0,   .max_args = NOT_DEF, },
734 	{ .name = "restart",           .abbr = NULL,    .id = -1,
735 	  .descr = "reset state and reread configuration",
736 	  .flags = HAS_COMMENT,
737 	  .handler = &restart_cmd,     .min_args = 0,   .max_args = 1, },
738 	{ .name = "restore",           .abbr = NULL,    .id = -1,
739 	  .descr = "restore files from a trash",
740 	  .flags = HAS_RANGE | HAS_COMMENT | HAS_SELECTION_SCOPE,
741 	  .handler = &restore_cmd,     .min_args = 0,   .max_args = 0, },
742 	{ .name = "rlink",             .abbr = NULL,    .id = COM_RLINK,
743 	  .descr = "create relative links",
744 	  .flags = HAS_EMARK | HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT
745 	         | HAS_QMARK_NO_ARGS | HAS_SELECTION_SCOPE,
746 	  .handler = &rlink_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
747 	{ .name = "screen",            .abbr = NULL,    .id = -1,
748 	  .descr = "view/toggle terminal multiplexer support",
749 	  .flags = HAS_COMMENT | HAS_EMARK | HAS_QMARK_NO_ARGS,
750 	  .handler = &screen_cmd,      .min_args = 0,   .max_args = 0, },
751 	{ .name = "select",            .abbr = NULL,    .id = COM_SELECT,
752 	  .descr = "select files matching pattern or range",
753 	  .flags = HAS_EMARK | HAS_RANGE | HAS_REGEXP_ARGS,
754 	  .handler = &select_cmd,      .min_args = 0,   .max_args = NOT_DEF, },
755 	{ .name = "session",           .abbr = NULL,    .id = COM_SESSION,
756 	  .descr = "shows, detaches or switches active session",
757 	  .flags = HAS_COMMENT | HAS_QMARK_NO_ARGS,
758 	  .handler = &session_cmd,     .min_args = 0,   .max_args = 1, },
759 	/* engine/options unit handles comments to resolve parsing ambiguity. */
760 	{ .name = "set",               .abbr = "se",    .id = COM_SET,
761 	  .descr = "set global and local options",
762 	  .flags = 0,
763 	  .handler = &set_cmd,         .min_args = 0,   .max_args = NOT_DEF, },
764 	{ .name = "setlocal",          .abbr = "setl",  .id = COM_SETLOCAL,
765 	  .descr = "set local options",
766 	  .flags = 0,
767 	  .handler = &setlocal_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
768 	{ .name = "setglobal",         .abbr = "setg",  .id = COM_SET,
769 	  .descr = "set global options",
770 	  .flags = 0,
771 	  .handler = &setglobal_cmd,   .min_args = 0,   .max_args = NOT_DEF, },
772 	{ .name = "shell",             .abbr = "sh",    .id = -1,
773 	  .descr = "spawn shell",
774 	  .flags = HAS_EMARK | HAS_COMMENT,
775 	  .handler = &shell_cmd,       .min_args = 0,   .max_args = 0, },
776 	{ .name = "siblnext",          .abbr = NULL,    .id = -1,
777 	  .descr = "navigate to next sibling directory",
778 	  .flags = HAS_RANGE | HAS_EMARK | HAS_COMMENT,
779 	  .handler = &siblnext_cmd,    .min_args = 0,   .max_args = 0, },
780 	{ .name = "siblprev",          .abbr = NULL,    .id = -1,
781 	  .descr = "navigate to previous sibling directory",
782 	  .flags = HAS_RANGE | HAS_EMARK | HAS_COMMENT,
783 	  .handler = &siblprev_cmd,    .min_args = 0,   .max_args = 0, },
784 	{ .name = "sort",              .abbr = "sor",   .id = -1,
785 	  .descr = "display sorting dialog",
786 	  .flags = HAS_COMMENT,
787 	  .handler = &sort_cmd,        .min_args = 0,   .max_args = 0, },
788 	{ .name = "source",            .abbr = "so",    .id = COM_SOURCE,
789 	  .descr = "source file with :commands",
790 	  .flags = HAS_QUOTED_ARGS | HAS_COMMENT | HAS_ENVVARS,
791 	  .handler = &source_cmd,      .min_args = 1,   .max_args = 1, },
792 	{ .name = "split",             .abbr = "sp",    .id = COM_SPLIT,
793 	  .descr = "horizontal split layout",
794 	  .flags = HAS_EMARK | HAS_COMMENT,
795 	  .handler = &split_cmd,       .min_args = 0,   .max_args = 1, },
796 	{ .name = "substitute",        .abbr = "s",     .id = COM_SUBSTITUTE,
797 	  .descr = "perform substitutions in file names",
798 	  .flags = HAS_RANGE | HAS_REGEXP_ARGS | HAS_COMMENT | HAS_CUST_SEP
799 	         | HAS_SELECTION_SCOPE,
800 	  .handler = &substitute_cmd,  .min_args = 0,   .max_args = 3, },
801 	{ .name = "sync",              .abbr = NULL,    .id = COM_SYNC,
802 	  .descr = "synchronize properties of views",
803 	  .flags = HAS_EMARK | HAS_COMMENT | HAS_MACROS_FOR_CMD,
804 	  .handler = &sync_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
805 	{ .name = "tabclose",          .abbr = "tabc",  .id = -1,
806 	  .descr = "close current tab unless it's the only one",
807 	  .flags = HAS_COMMENT,
808 	  .handler = &tabclose_cmd,    .min_args = 0,   .max_args = 0, },
809 	{ .name = "tabmove",           .abbr = "tabm",  .id = -1,
810 	  .descr = "position current tab after another tab",
811 	  .flags = HAS_COMMENT,
812 	  .handler = &tabmove_cmd,     .min_args = 0,   .max_args = 1, },
813 	{ .name = "tabname",           .abbr = NULL,    .id = -1,
814 	  .descr = "set name of current tab",
815 	  .flags = HAS_COMMENT,
816 	  .handler = &tabname_cmd,     .min_args = 0,   .max_args = 1, },
817 	{ .name = "tabnew",            .abbr = NULL,    .id = COM_TABNEW,
818 	  .descr = "make new tab and switch to it",
819 	  .flags = HAS_QUOTED_ARGS | HAS_ENVVARS | HAS_MACROS_FOR_CMD | HAS_COMMENT,
820 	  .handler = &tabnew_cmd,      .min_args = 0,   .max_args = 1, },
821 	{ .name = "tabnext",           .abbr = "tabn",  .id = -1,
822 	  .descr = "go to next or n-th tab",
823 	  .flags = HAS_COMMENT,
824 	  .handler = &tabnext_cmd,     .min_args = 0,   .max_args = 1, },
825 	{ .name = "tabonly",           .abbr = "tabo",  .id = -1,
826 	  .descr = "close all tabs but the current one",
827 	  .flags = HAS_COMMENT,
828 	  .handler = &tabonly_cmd,     .min_args = 0,   .max_args = 0, },
829 	{ .name = "tabprevious",       .abbr = "tabp",  .id = -1,
830 	  .descr = "go to previous or n-th previous tab",
831 	  .flags = HAS_COMMENT,
832 	  .handler = &tabprevious_cmd, .min_args = 0,   .max_args = 1, },
833 	{ .name = "touch",             .abbr = NULL,    .id = COM_TOUCH,
834 	  .descr = "create files",
835 	  .flags = HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT | HAS_MACROS_FOR_CMD,
836 	  .handler = &touch_cmd,       .min_args = 1,   .max_args = NOT_DEF, },
837 	{ .name = "tr",                .abbr = NULL,    .id = COM_TR,
838 	  .descr = "replace characters in file names",
839 	  .flags = HAS_RANGE | HAS_REGEXP_ARGS | HAS_COMMENT | HAS_CUST_SEP
840 	         | HAS_SELECTION_SCOPE,
841 	  .handler = &tr_cmd,          .min_args = 2,   .max_args = 2, },
842 	{ .name = "trashes",           .abbr = NULL,    .id = -1,
843 	  .descr = "display trash directories",
844 	  .flags = HAS_COMMENT | HAS_QMARK_NO_ARGS,
845 	  .handler = &trashes_cmd,     .min_args = 0,   .max_args = 0, },
846 	{ .name = "tree",              .abbr = NULL,    .id = -1,
847 	  .descr = "display filesystem as a tree",
848 	  .flags = HAS_EMARK | HAS_COMMENT,
849 	  .handler = &tree_cmd,        .min_args = 0,   .max_args = 0, },
850 	{ .name = "undolist",          .abbr = "undol", .id = -1,
851 	  .descr = "display list of operations",
852 	  .flags = HAS_EMARK | HAS_COMMENT,
853 	  .handler = &undolist_cmd,    .min_args = 0,   .max_args = 0, },
854 	{ .name = "unlet",             .abbr = "unl",   .id = COM_UNLET,
855 	  .descr = "undefine variable",
856 	  .flags = HAS_EMARK | HAS_COMMENT,
857 	  .handler = &unlet_cmd,       .min_args = 1,   .max_args = NOT_DEF, },
858 	{ .name = "unmap",             .abbr = "unm",   .id = -1,
859 	  .descr = "unmap user keys in normal and visual modes",
860 	  .flags = HAS_EMARK | HAS_RAW_ARGS,
861 	  .handler = &unmap_cmd,       .min_args = 1,   .max_args = 1, },
862 	{ .name = "unselect",          .abbr = NULL,    .id = COM_SELECT,
863 	  .descr = "unselect files matching pattern or range",
864 	  .flags = HAS_RANGE | HAS_REGEXP_ARGS,
865 	  .handler = &unselect_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
866 	{ .name = "version",           .abbr = "ve",    .id = -1,
867 	  .descr = "display version information",
868 	  .flags = HAS_COMMENT,
869 	  .handler = &vifm_cmd,        .min_args = 0,   .max_args = 0, },
870 	{ .name = "view",              .abbr = "vie",   .id = -1,
871 	  .descr = "control visibility of preview",
872 	  .flags = HAS_EMARK | HAS_COMMENT,
873 	  .handler = &view_cmd,        .min_args = 0,   .max_args = 0, },
874 	{ .name = "vifm",              .abbr = NULL,    .id = -1,
875 	  .descr = "display version information",
876 	  .flags = HAS_COMMENT,
877 	  .handler = &vifm_cmd,        .min_args = 0,   .max_args = 0, },
878 	{ .name = "vmap",              .abbr = "vm",    .id = COM_VMAP,
879 	  .descr = "map keys in visual mode",
880 	  .flags = HAS_RAW_ARGS,
881 	  .handler = &vmap_cmd,        .min_args = 0,   .max_args = NOT_DEF, },
882 	{ .name = "vnoremap",          .abbr = "vn",    .id = COM_VNOREMAP,
883 	  .descr = "noremap keys in visual mode",
884 	  .flags = HAS_RAW_ARGS,
885 	  .handler = &vnoremap_cmd,    .min_args = 0,   .max_args = NOT_DEF, },
886 #ifdef _WIN32
887 	{ .name = "volumes",           .abbr = NULL,    .id = -1,
888 	  .descr = "display list of drives",
889 	  .flags = HAS_COMMENT,
890 	  .handler = &volumes_cmd,     .min_args = 0,   .max_args = 0, },
891 #endif
892 	{ .name = "vsplit",            .abbr = "vs",    .id = COM_VSPLIT,
893 	  .descr = "vertical split layout",
894 	  .flags = HAS_EMARK | HAS_COMMENT,
895 	  .handler = &vsplit_cmd,      .min_args = 0,   .max_args = 1, },
896 	{ .name = "vunmap",            .abbr = "vu",    .id = -1,
897 	  .descr = "unmap user keys in visual mode",
898 	  .flags = HAS_RAW_ARGS,
899 	  .handler = &vunmap_cmd,      .min_args = 1,   .max_args = 1, },
900 	{ .name = "wincmd",            .abbr = "winc",  .id = COM_WINCMD,
901 	  .descr = "cmdline Ctrl-W substitute",
902 	  .flags = HAS_RANGE,
903 	  .handler = &wincmd_cmd,      .min_args = 1,   .max_args = 1, },
904 	{ .name = "windo",             .abbr = NULL,    .id = COM_WINDO,
905 	  .descr = "run command for each pane",
906 	  .flags = 0,
907 	  .handler = &windo_cmd,       .min_args = 0,   .max_args = NOT_DEF, },
908 	{ .name = "winrun",            .abbr = NULL,    .id = COM_WINRUN,
909 	  .descr = "run command for specific pane(s)",
910 	  .flags = 0,
911 	  .handler = &winrun_cmd,      .min_args = 0,   .max_args = NOT_DEF, },
912 	{ .name = "write",             .abbr = "w",     .id = -1,
913 	  .descr = "write vifminfo file",
914 	  .flags = HAS_COMMENT,
915 	  .handler = &write_cmd,       .min_args = 0,   .max_args = 0, },
916 	{ .name = "wq",                .abbr = NULL,    .id = -1,
917 	  .descr = "close a tab or exit the application",
918 	  .flags = HAS_EMARK | HAS_COMMENT,
919 	  .handler = &wq_cmd,          .min_args = 0,   .max_args = 0, },
920 	{ .name = "wqall",             .abbr = "wqa",   .id = -1,
921 	  .descr = "exit the application",
922 	  .flags = HAS_EMARK | HAS_COMMENT,
923 	  .handler = &wqall_cmd,       .min_args = 0,   .max_args = 0, },
924 	{ .name = "xall",              .abbr = "xa",    .id = -1,
925 	  .descr = "exit the application",
926 	  .flags = HAS_COMMENT,
927 	  .handler = &qall_cmd,        .min_args = 0,   .max_args = 0, },
928 	{ .name = "xit",               .abbr = "x",     .id = -1,
929 	  .descr = "exit the application",
930 	  .flags = HAS_COMMENT,
931 	  .handler = &quit_cmd,        .min_args = 0,   .max_args = 0, },
932 	{ .name = "yank",              .abbr = "y",     .id = -1,
933 	  .descr = "yank files",
934 	  .flags = HAS_RANGE | HAS_SELECTION_SCOPE,
935 	  .handler = &yank_cmd,        .min_args = 0,   .max_args = 2, },
936 
937 	{ .name = "<USERCMD>",         .abbr = NULL,    .id = -1,
938 	  .descr = "user-defined command",
939 	  .flags = HAS_RANGE | HAS_QUOTED_ARGS | HAS_MACROS_FOR_CMD
940 	         | HAS_SELECTION_SCOPE,
941 	  .handler = &usercmd_cmd,     .min_args = 0,   .max_args = NOT_DEF, },
942 };
943 const size_t cmds_list_size = ARRAY_LEN(cmds_list);
944 
945 /* Holds global state of command handlers. */
946 static struct
947 {
948 	/* For :find command. */
949 	struct
950 	{
951 		char *last_args;   /* Last arguments passed to the command */
952 		int includes_path; /* Whether last_args contains path to search in. */
953 	}
954 	find;
955 }
956 cmds_state;
957 
958 /* Return value of all functions below which name ends with "_cmd" mean:
959  *  - <0 -- one of CMDS_* errors from cmds.h;
960  *  - =0 -- nothing was outputted to the status bar, don't need to save its
961  *          state;
962  *  - <0 -- something was outputted to the status bar, need to save its
963  *          state. */
964 static int
goto_cmd(const cmd_info_t * cmd_info)965 goto_cmd(const cmd_info_t *cmd_info)
966 {
967 	cmds_preserve_selection();
968 	fpos_set_pos(curr_view, cmd_info->end);
969 	return 0;
970 }
971 
972 /* Handles :! command, which executes external command via shell. */
973 static int
emark_cmd(const cmd_info_t * cmd_info)974 emark_cmd(const cmd_info_t *cmd_info)
975 {
976 	int save_msg = 0;
977 	const char *com = cmd_info->args;
978 	char buf[COMMAND_GROUP_INFO_LEN];
979 
980 	if(cmd_info->argc == 0)
981 	{
982 		if(cmd_info->emark)
983 		{
984 			const char *const last_cmd = curr_stats.last_cmdline_command;
985 			if(last_cmd == NULL)
986 			{
987 				ui_sb_msg("No previous command-line command");
988 				return 1;
989 			}
990 			return exec_commands(last_cmd, curr_view, CIT_COMMAND) != 0;
991 		}
992 		return CMDS_ERR_TOO_FEW_ARGS;
993 	}
994 
995 	com = skip_whitespace(com);
996 	if(com[0] == '\0')
997 	{
998 		return 0;
999 	}
1000 
1001 	MacroFlags flags = (MacroFlags)cmd_info->usr1;
1002 	char *title = format_str("!%s", cmd_info->raw_args);
1003 	int handled = rn_ext(com, title, flags, cmd_info->bg, &save_msg);
1004 	free(title);
1005 
1006 	if(handled > 0)
1007 	{
1008 		/* Do nothing. */
1009 	}
1010 	else if(handled < 0)
1011 	{
1012 		return save_msg;
1013 	}
1014 	else if(cmd_info->bg)
1015 	{
1016 		bg_run_external(com, 0, SHELL_BY_USER);
1017 	}
1018 	else
1019 	{
1020 		const int use_term_mux = flags != MF_NO_TERM_MUX;
1021 
1022 		flist_sel_stash(curr_view);
1023 		if(cfg.fast_run)
1024 		{
1025 			char *const buf = fast_run_complete(com);
1026 			if(buf != NULL)
1027 			{
1028 				(void)rn_shell(buf, cmd_info->emark ? PAUSE_ALWAYS : PAUSE_ON_ERROR,
1029 						use_term_mux, SHELL_BY_USER);
1030 				free(buf);
1031 			}
1032 		}
1033 		else
1034 		{
1035 			(void)rn_shell(com, cmd_info->emark ? PAUSE_ALWAYS : PAUSE_ON_ERROR,
1036 					use_term_mux, SHELL_BY_USER);
1037 		}
1038 	}
1039 
1040 	snprintf(buf, sizeof(buf), "in %s: !%s",
1041 			replace_home_part(flist_get_dir(curr_view)), cmd_info->raw_args);
1042 	un_group_open(buf);
1043 	un_group_add_op(OP_USR, strdup(com), NULL, "", "");
1044 	un_group_close();
1045 
1046 	return save_msg;
1047 }
1048 
1049 /* Creates symbolic links with absolute paths to files. */
1050 static int
alink_cmd(const cmd_info_t * cmd_info)1051 alink_cmd(const cmd_info_t *cmd_info)
1052 {
1053 	return link_cmd(cmd_info, 1);
1054 }
1055 
1056 static int
apropos_cmd(const cmd_info_t * cmd_info)1057 apropos_cmd(const cmd_info_t *cmd_info)
1058 {
1059 	static char *last_args;
1060 
1061 	if(cmd_info->argc > 0)
1062 	{
1063 		(void)replace_string(&last_args, cmd_info->args);
1064 	}
1065 	else if(last_args == NULL)
1066 	{
1067 		ui_sb_err("Nothing to repeat");
1068 		return 1;
1069 	}
1070 
1071 	return show_apropos_menu(curr_view, last_args) != 0;
1072 }
1073 
1074 /* Adds/lists/removes autocommands. */
1075 static int
autocmd_cmd(const cmd_info_t * cmd_info)1076 autocmd_cmd(const cmd_info_t *cmd_info)
1077 {
1078 	enum { ADDITION, LISTING, REMOVAL } type = cmd_info->emark
1079 	                                         ? REMOVAL : (cmd_info->argc < 3)
1080 	                                         ? LISTING : ADDITION;
1081 
1082 	const char *event = NULL;
1083 	const char *patterns = NULL;
1084 	const char *action;
1085 
1086 	/* Check usage. */
1087 	if(cmd_info->emark && cmd_info->argc > 2)
1088 	{
1089 		return CMDS_ERR_TRAILING_CHARS;
1090 	}
1091 
1092 	/* Parse event and patterns. */
1093 	if(cmd_info->argc > 0)
1094 	{
1095 		if(type == ADDITION || strcmp(cmd_info->argv[0], "*") != 0)
1096 		{
1097 			event = cmd_info->argv[0];
1098 		}
1099 		if(cmd_info->argc > 1)
1100 		{
1101 			patterns = cmd_info->argv[1];
1102 		}
1103 	}
1104 
1105 	/* Check validity of event. */
1106 	if(event != NULL)
1107 	{
1108 		/* Non-const for is_in_string_array_case(). */
1109 		static char *events[] = { "DirEnter" };
1110 		if(!is_in_string_array_case(events, ARRAY_LEN(events), event))
1111 		{
1112 			ui_sb_errf("No such event: %s", event);
1113 			return 1;
1114 		}
1115 	}
1116 
1117 	if(type == REMOVAL)
1118 	{
1119 		vle_aucmd_remove(event, patterns);
1120 		return 0;
1121 	}
1122 
1123 	if(type == LISTING)
1124 	{
1125 		vle_textbuf *const msg = vle_tb_create();
1126 		vle_aucmd_list(event, patterns, &aucmd_list_cb, msg);
1127 		ui_sb_msg(vle_tb_get_data(msg));
1128 		vle_tb_free(msg);
1129 		return 1;
1130 	}
1131 
1132 	/* Addition. */
1133 
1134 	action = &cmd_info->args[cmd_info->argvp[2][0]];
1135 
1136 	if(vle_aucmd_on_execute(event, patterns, action, &aucmd_action_handler) != 0)
1137 	{
1138 		ui_sb_err("Failed to register autocommand");
1139 		return 1;
1140 	}
1141 
1142 	return 0;
1143 }
1144 
1145 /* Implementation of autocommand action. */
1146 static void
aucmd_action_handler(const char action[],void * arg)1147 aucmd_action_handler(const char action[], void *arg)
1148 {
1149 	view_t *view = arg;
1150 	view_t *tmp_curr, *tmp_other;
1151 	ui_view_pick(view, &tmp_curr, &tmp_other);
1152 
1153 	char *saved_cwd = save_cwd();
1154 	(void)vifm_chdir(flist_get_dir(view));
1155 	(void)exec_commands(action, view, CIT_COMMAND);
1156 	restore_cwd(saved_cwd);
1157 
1158 	ui_view_unpick(view, tmp_curr, tmp_other);
1159 }
1160 
1161 /* Handler of list callback for autocommands. */
1162 static void
aucmd_list_cb(const char event[],const char pattern[],int negated,const char action[],void * arg)1163 aucmd_list_cb(const char event[], const char pattern[], int negated,
1164 		const char action[], void *arg)
1165 {
1166 	vle_textbuf *msg = arg;
1167 	const char *fmt = (strlen(pattern) <= 10)
1168 	                ? "%-10s %s%-10s %s"
1169 	                : "%-10s %s%-10s\n                      %s";
1170 
1171 	vle_tb_append_linef(msg, fmt, event, negated ? "!" : "", pattern, action);
1172 }
1173 
1174 /* Marks directory with set of tags. */
1175 static int
bmark_cmd(const cmd_info_t * cmd_info)1176 bmark_cmd(const cmd_info_t *cmd_info)
1177 {
1178 	char *const tags = make_tags_list(cmd_info);
1179 	char *const path = get_bmark_dir(cmd_info);
1180 	const int err = (tags == NULL || bmarks_set(path, tags) != 0);
1181 	if(err && tags != NULL)
1182 	{
1183 		ui_sb_err("Failed to add bookmark");
1184 	}
1185 	free(path);
1186 	free(tags);
1187 	return err;
1188 }
1189 
1190 /* Lists either all bookmarks or those matching specified tags. */
1191 static int
bmarks_cmd(const cmd_info_t * cmd_info)1192 bmarks_cmd(const cmd_info_t *cmd_info)
1193 {
1194 	return bmarks_do(cmd_info, 0);
1195 }
1196 
1197 /* When there are more than 1 match acts like :bmarks, otherwise navigates to
1198  * single match immediately. */
1199 static int
bmgo_cmd(const cmd_info_t * cmd_info)1200 bmgo_cmd(const cmd_info_t *cmd_info)
1201 {
1202 	return bmarks_do(cmd_info, 1);
1203 }
1204 
1205 /* Runs bookmarks menu in either view or go mode (different only on single
1206  * match). */
1207 static int
bmarks_do(const cmd_info_t * cmd_info,int go)1208 bmarks_do(const cmd_info_t *cmd_info, int go)
1209 {
1210 	char *const tags = args_to_csl(cmd_info);
1211 	const int result = (show_bmarks_menu(curr_view, tags, go) != 0);
1212 	free(tags);
1213 	return result;
1214 }
1215 
1216 /* Converts command arguments into comma-separated list of tags.  Returns newly
1217  * allocated string or NULL on error or invalid tag name (in which case an error
1218  * is printed on the status bar). */
1219 static char *
make_tags_list(const cmd_info_t * cmd_info)1220 make_tags_list(const cmd_info_t *cmd_info)
1221 {
1222 	int i;
1223 
1224 	if(cmd_info->emark && cmd_info->argc == 1)
1225 	{
1226 		ui_sb_err("Too few arguments");
1227 		return NULL;
1228 	}
1229 
1230 	for(i = cmd_info->emark ? 1 : 0; i < cmd_info->argc; ++i)
1231 	{
1232 		if(strpbrk(cmd_info->argv[i], ", \t") != NULL)
1233 		{
1234 			ui_sb_errf("Tags can't include comma or whitespace: %s",
1235 					cmd_info->argv[i]);
1236 			return NULL;
1237 		}
1238 	}
1239 
1240 	return args_to_csl(cmd_info);
1241 }
1242 
1243 /* Makes comma-separated list from command line arguments.  Returns newly
1244  * allocated string or NULL on absence of arguments. */
1245 static char *
args_to_csl(const cmd_info_t * cmd_info)1246 args_to_csl(const cmd_info_t *cmd_info)
1247 {
1248 	int i;
1249 	char *tags = NULL;
1250 	size_t len = 0U;
1251 
1252 	if(cmd_info->argc == 0)
1253 	{
1254 		return NULL;
1255 	}
1256 
1257 	i = cmd_info->emark ? 1 : 0;
1258 	strappend(&tags, &len, cmd_info->argv[i]);
1259 	for(++i; i < cmd_info->argc; ++i)
1260 	{
1261 		strappendch(&tags, &len, ',');
1262 		strappend(&tags, &len, cmd_info->argv[i]);
1263 	}
1264 
1265 	return tags;
1266 }
1267 
1268 /* Registers command-line mode abbreviation. */
1269 static int
cabbrev_cmd(const cmd_info_t * cmd_info)1270 cabbrev_cmd(const cmd_info_t *cmd_info)
1271 {
1272 	return handle_cabbrevs(cmd_info, 0);
1273 }
1274 
1275 /* Registers command-line mode abbreviation of noremap kind. */
1276 static int
cnoreabbrev_cmd(const cmd_info_t * cmd_info)1277 cnoreabbrev_cmd(const cmd_info_t *cmd_info)
1278 {
1279 	return handle_cabbrevs(cmd_info, 1);
1280 }
1281 
1282 /* Handles command-line mode abbreviation of both kinds in one place.  Returns
1283  * value to be returned by command handler. */
1284 static int
handle_cabbrevs(const cmd_info_t * cmd_info,int no_remap)1285 handle_cabbrevs(const cmd_info_t *cmd_info, int no_remap)
1286 {
1287 	if(cmd_info->argc == 0)
1288 	{
1289 		return show_cabbrevs_menu(curr_view) != 0;
1290 	}
1291 	if(cmd_info->argc == 1)
1292 	{
1293 		return list_abbrevs(cmd_info->argv[0]);
1294 	}
1295 	return add_cabbrev(cmd_info, no_remap);
1296 }
1297 
1298 /* List command-line mode abbreviations that start with specified prefix.
1299  * Returns value to be returned by command handler. */
1300 static int
list_abbrevs(const char prefix[])1301 list_abbrevs(const char prefix[])
1302 {
1303 	size_t prefix_len;
1304 	void *state;
1305 	const wchar_t *lhs, *rhs;
1306 	int no_remap;
1307 	vle_textbuf *msg;
1308 	int seen_match;
1309 
1310 	wchar_t *wide_prefix = to_wide(prefix);
1311 	if(wide_prefix == NULL)
1312 	{
1313 		show_error_msgf("Abbrevs Error", "Failed to convert to wide string: %s",
1314 				prefix);
1315 		return 0;
1316 	}
1317 
1318 	state = NULL;
1319 	if(!vle_abbr_iter(&lhs, &rhs, &no_remap, &state))
1320 	{
1321 		ui_sb_msg("No abbreviation found");
1322 		return 1;
1323 	}
1324 
1325 	msg = vle_tb_create();
1326 	vle_tb_append_line(msg, "Abbreviation -- N -- Replacement");
1327 
1328 	prefix_len = wcslen(wide_prefix);
1329 
1330 	seen_match = 0;
1331 	state = NULL;
1332 	while(vle_abbr_iter(&lhs, &rhs, &no_remap, &state))
1333 	{
1334 		if(wcsncmp(lhs, wide_prefix, prefix_len) == 0)
1335 		{
1336 			char *const descr = describe_abbrev(lhs, rhs, no_remap, 0);
1337 			vle_tb_append_line(msg, descr);
1338 			free(descr);
1339 			seen_match = 1;
1340 		}
1341 	}
1342 
1343 	if(seen_match)
1344 	{
1345 		ui_sb_msg(vle_tb_get_data(msg));
1346 	}
1347 	else
1348 	{
1349 		ui_sb_msg("No abbreviation found");
1350 	}
1351 	vle_tb_free(msg);
1352 
1353 	free(wide_prefix);
1354 	return 1;
1355 }
1356 
1357 /* Registers command-line mode abbreviation.  Returns value to be returned by
1358  * command handler. */
1359 static int
add_cabbrev(const cmd_info_t * cmd_info,int no_remap)1360 add_cabbrev(const cmd_info_t *cmd_info, int no_remap)
1361 {
1362 	int result;
1363 	wchar_t *subst;
1364 	wchar_t *wargs = to_wide(cmd_info->args);
1365 	wchar_t *rhs = wargs;
1366 
1367 	if(wargs == NULL)
1368 	{
1369 		show_error_msgf("Abbrevs Error", "Failed to convert to wide string: %s",
1370 				cmd_info->args);
1371 		return 0;
1372 	}
1373 
1374 	while(cfg_is_word_wchar(*rhs))
1375 	{
1376 		++rhs;
1377 	}
1378 	while(iswspace(*rhs))
1379 	{
1380 		*rhs++ = L'\0';
1381 	}
1382 
1383 	subst = substitute_specsw(rhs);
1384 	result = no_remap
1385 	       ? vle_abbr_add_no_remap(wargs, subst)
1386 	       : vle_abbr_add(wargs, subst);
1387 	free(subst);
1388 	free(wargs);
1389 
1390 	if(result != 0)
1391 	{
1392 		ui_sb_err("Failed to register abbreviation");
1393 	}
1394 
1395 	return result;
1396 }
1397 
1398 /* Changes location of a view or both views.  Handle multiple configurations of
1399  * the command (with/without !, none/one/two arguments). */
1400 static int
cd_cmd(const cmd_info_t * cmd_info)1401 cd_cmd(const cmd_info_t *cmd_info)
1402 {
1403 	int result;
1404 
1405 	char *const curr_dir = strdup(flist_get_dir(curr_view));
1406 	char *const other_dir = strdup(flist_get_dir(other_view));
1407 
1408 	if(!cfg.auto_ch_pos)
1409 	{
1410 		flist_hist_clear(curr_view);
1411 		curr_stats.ch_pos = 0;
1412 	}
1413 
1414 	if(cmd_info->argc == 0)
1415 	{
1416 		result = cd(curr_view, curr_dir, cfg.home_dir);
1417 		if(result == 0 && cmd_info->emark)
1418 		{
1419 			result += cd(other_view, other_dir, cfg.home_dir);
1420 		}
1421 	}
1422 	else if(cmd_info->argc == 1)
1423 	{
1424 		result = cd(curr_view, curr_dir, cmd_info->argv[0]);
1425 		if(cmd_info->emark)
1426 		{
1427 			if(!is_path_absolute(cmd_info->argv[0]) && cmd_info->argv[0][0] != '~' &&
1428 					strcmp(cmd_info->argv[0], "-") != 0)
1429 			{
1430 				char dir[PATH_MAX + 1];
1431 				snprintf(dir, sizeof(dir), "%s/%s", curr_dir, cmd_info->argv[0]);
1432 				result += cd(other_view, other_dir, dir);
1433 			}
1434 			else if(strcmp(cmd_info->argv[0], "-") == 0)
1435 			{
1436 				result += cd(other_view, other_dir, curr_dir);
1437 			}
1438 			else
1439 			{
1440 				result += cd(other_view, other_dir, cmd_info->argv[0]);
1441 			}
1442 			refresh_view_win(other_view);
1443 		}
1444 	}
1445 	else
1446 	{
1447 		result = cd(curr_view, curr_dir, cmd_info->argv[0]);
1448 		if(!is_path_absolute(cmd_info->argv[1]) && cmd_info->argv[1][0] != '~')
1449 		{
1450 			char dir[PATH_MAX + 1];
1451 			snprintf(dir, sizeof(dir), "%s/%s", curr_dir, cmd_info->argv[1]);
1452 			result += cd(other_view, other_dir, dir);
1453 		}
1454 		else
1455 		{
1456 			result += cd(other_view, other_dir, cmd_info->argv[1]);
1457 		}
1458 		refresh_view_win(other_view);
1459 	}
1460 
1461 	if(!cfg.auto_ch_pos)
1462 	{
1463 		curr_stats.ch_pos = 1;
1464 	}
1465 
1466 	free(curr_dir);
1467 	free(other_dir);
1468 
1469 	return result;
1470 }
1471 
1472 /* Performs substitution on current path and navigates to the result. */
1473 static int
cds_cmd(const cmd_info_t * cmd_info)1474 cds_cmd(const cmd_info_t *cmd_info)
1475 {
1476 	int case_sensitive = !regexp_should_ignore_case(cmd_info->argv[0]);
1477 	if(cmd_info->argc > 2)
1478 	{
1479 		cmd_info->argv[2][strcspn(cmd_info->argv[2], " \t")] = '\0';
1480 		if(parse_case_flag(cmd_info->argv[2], &case_sensitive) != 0)
1481 		{
1482 			ui_sb_errf("Failed to parse flags: %s", cmd_info->argv[2]);
1483 			return CMDS_ERR_CUSTOM;
1484 		}
1485 	}
1486 
1487 	const char *const curr_dir = flist_get_dir(curr_view);
1488 	char *const new_path = strdup(regexp_replace(curr_dir, cmd_info->argv[0],
1489 				cmd_info->argv[1], 0, !case_sensitive));
1490 
1491 	if(strcmp(curr_dir, new_path) == 0)
1492 	{
1493 		free(new_path);
1494 		ui_sb_msgf("No \"%s\" found in CWD", cmd_info->argv[0]);
1495 		return 1;
1496 	}
1497 
1498 	int result = cd(curr_view, curr_dir, new_path);
1499 	if(result == 0 && cmd_info->emark)
1500 	{
1501 		result += cd(other_view, curr_dir, new_path);
1502 	}
1503 
1504 	free(new_path);
1505 
1506 	return result;
1507 }
1508 
1509 static int
change_cmd(const cmd_info_t * cmd_info)1510 change_cmd(const cmd_info_t *cmd_info)
1511 {
1512 	enter_change_mode(curr_view);
1513 	cmds_preserve_selection();
1514 	return 0;
1515 }
1516 
1517 static int
chmod_cmd(const cmd_info_t * cmd_info)1518 chmod_cmd(const cmd_info_t *cmd_info)
1519 {
1520 #ifndef _WIN32
1521 	regex_t re;
1522 	int err;
1523 	int i;
1524 #endif
1525 
1526 	if(cmd_info->argc == 0)
1527 	{
1528 		enter_attr_mode(curr_view);
1529 		cmds_preserve_selection();
1530 		return 0;
1531 	}
1532 
1533 #ifndef _WIN32
1534 	if((err = regcomp(&re, "^([ugoa]*([-+=]([rwxXst]*|[ugo]))+)|([0-7]{3,4})$",
1535 			REG_EXTENDED)) != 0)
1536 	{
1537 		ui_sb_errf("Regexp error: %s", get_regexp_error(err, &re));
1538 		regfree(&re);
1539 		return 1;
1540 	}
1541 
1542 	for(i = 0; i < cmd_info->argc; i++)
1543 	{
1544 		if(regexec(&re, cmd_info->argv[i], 0, NULL, 0) == REG_NOMATCH)
1545 		{
1546 			break;
1547 		}
1548 	}
1549 	regfree(&re);
1550 
1551 	if(i < cmd_info->argc)
1552 	{
1553 		ui_sb_errf("Invalid argument: %s", cmd_info->argv[i]);
1554 		return 1;
1555 	}
1556 
1557 	flist_set_marking(curr_view, 0);
1558 	files_chmod(curr_view, cmd_info->args, cmd_info->emark);
1559 #endif
1560 	return 0;
1561 }
1562 
1563 #ifndef _WIN32
1564 static int
chown_cmd(const cmd_info_t * cmd_info)1565 chown_cmd(const cmd_info_t *cmd_info)
1566 {
1567 	char *colon, *user, *group;
1568 	int u, g;
1569 	uid_t uid = (uid_t)-1;
1570 	gid_t gid = (gid_t)-1;
1571 
1572 	if(cmd_info->argc == 0)
1573 	{
1574 		fops_chuser();
1575 		return 0;
1576 	}
1577 
1578 	colon = strchr(cmd_info->argv[0], ':');
1579 	if(colon == NULL)
1580 	{
1581 		user = cmd_info->argv[0];
1582 		group = "";
1583 	}
1584 	else
1585 	{
1586 		*colon = '\0';
1587 		user = cmd_info->argv[0];
1588 		group = colon + 1;
1589 	}
1590 	u = user[0] != '\0';
1591 	g = group[0] != '\0';
1592 
1593 	if(u && get_uid(user, &uid) != 0)
1594 	{
1595 		ui_sb_errf("Invalid user name: \"%s\"", user);
1596 		return 1;
1597 	}
1598 	if(g && get_gid(group, &gid) != 0)
1599 	{
1600 		ui_sb_errf("Invalid group name: \"%s\"", group);
1601 		return 1;
1602 	}
1603 
1604 	flist_set_marking(curr_view, 0);
1605 	return fops_chown(u, g, uid, gid) != 0;
1606 }
1607 #endif
1608 
1609 /* Clones file [count=1] times. */
1610 static int
clone_cmd(const cmd_info_t * cmd_info)1611 clone_cmd(const cmd_info_t *cmd_info)
1612 {
1613 	flist_set_marking(curr_view, 0);
1614 
1615 	if(cmd_info->qmark)
1616 	{
1617 		if(cmd_info->argc > 0)
1618 		{
1619 			ui_sb_err("No arguments are allowed if you use \"?\"");
1620 			return 1;
1621 		}
1622 		return fops_clone(curr_view, NULL, -1, 0, 1) != 0;
1623 	}
1624 
1625 	return fops_clone(curr_view, cmd_info->argv, cmd_info->argc, cmd_info->emark,
1626 			1) != 0;
1627 }
1628 
1629 static int
cmap_cmd(const cmd_info_t * cmd_info)1630 cmap_cmd(const cmd_info_t *cmd_info)
1631 {
1632 	return do_map(cmd_info, "Command Line", CMDLINE_MODE, 0) != 0;
1633 }
1634 
1635 static int
cnoremap_cmd(const cmd_info_t * cmd_info)1636 cnoremap_cmd(const cmd_info_t *cmd_info)
1637 {
1638 	return do_map(cmd_info, "Command Line", CMDLINE_MODE, 1) != 0;
1639 }
1640 
1641 /* Displays colorscheme menu, current colorscheme, associates colorscheme with a
1642  * subtree or sets primary colorscheme. */
1643 static int
colorscheme_cmd(const cmd_info_t * cmd_info)1644 colorscheme_cmd(const cmd_info_t *cmd_info)
1645 {
1646 	if(cmd_info->qmark)
1647 	{
1648 		ui_sb_msg(cfg.cs.name);
1649 		return 1;
1650 	}
1651 
1652 	if(cmd_info->argc == 0)
1653 	{
1654 		/* Show menu with colorschemes listed. */
1655 		return show_colorschemes_menu(curr_view) != 0;
1656 	}
1657 
1658 	const int assoc_form = is_colorscheme_assoc_form(cmd_info);
1659 	if((cmd_info->argc == 1 || assoc_form) && !cs_exists(cmd_info->argv[0]))
1660 	{
1661 		ui_sb_errf("Cannot find colorscheme %s" , cmd_info->argv[0]);
1662 		return 1;
1663 	}
1664 
1665 	if(assoc_form)
1666 	{
1667 		return assoc_colorscheme(cmd_info->argv[0], cmd_info->argv[1]);
1668 	}
1669 
1670 	set_colorscheme(cmd_info->argv, cmd_info->argc);
1671 	return 0;
1672 }
1673 
1674 /* Checks whether :colorscheme was invoked to associate a color scheme with a
1675  * directory.  Returns non-zero if so, otherwise zero is returned. */
1676 static int
is_colorscheme_assoc_form(const cmd_info_t * cmd_info)1677 is_colorscheme_assoc_form(const cmd_info_t *cmd_info)
1678 {
1679 	if(cmd_info->argc != 2 || cs_exists(cmd_info->argv[1]))
1680 	{
1681 		return 0;
1682 	}
1683 
1684 	/* The path in :colorscheme command cannot be relative in startup scripts. */
1685 	if(curr_stats.load_stage < 3)
1686 	{
1687 		char *path = expand_tilde(cmd_info->argv[1]);
1688 		int is_abs_path = is_path_absolute(path);
1689 		free(path);
1690 		return is_abs_path;
1691 	}
1692 	return 1;
1693 }
1694 
1695 /* Associates colorscheme with a subtree.  Returns value to be returned by
1696  * command handler. */
1697 static int
assoc_colorscheme(const char name[],const char path[])1698 assoc_colorscheme(const char name[], const char path[])
1699 {
1700 	char path_buf[PATH_MAX + 1];
1701 	char *directory = expand_tilde(path);
1702 	if(!is_path_absolute(directory))
1703 	{
1704 		snprintf(path_buf, sizeof(path_buf), "%s/%s", flist_get_dir(curr_view),
1705 				directory);
1706 		(void)replace_string(&directory, path_buf);
1707 	}
1708 	canonicalize_path(directory, path_buf, sizeof(path_buf));
1709 	(void)replace_string(&directory, path_buf);
1710 
1711 	if(!is_dir(directory))
1712 	{
1713 		ui_sb_errf("%s isn't a directory", directory);
1714 		free(directory);
1715 		return 1;
1716 	}
1717 
1718 	cs_assoc_dir(name, directory);
1719 	free(directory);
1720 
1721 	lwin.local_cs = cs_load_local(1, lwin.curr_dir);
1722 	rwin.local_cs = cs_load_local(0, rwin.curr_dir);
1723 	redraw_lists();
1724 	return 0;
1725 }
1726 
1727 /* Sets primary colorscheme. */
1728 static void
set_colorscheme(char * names[],int count)1729 set_colorscheme(char *names[], int count)
1730 {
1731 	if(count == 1)
1732 	{
1733 		cs_load_primary(names[0]);
1734 	}
1735 	else
1736 	{
1737 		cs_load_primary_list(names, count);
1738 	}
1739 
1740 	if(!lwin.local_cs)
1741 	{
1742 		cs_assign(&lwin.cs, &cfg.cs);
1743 	}
1744 	if(!rwin.local_cs)
1745 	{
1746 		cs_assign(&rwin.cs, &cfg.cs);
1747 	}
1748 	redraw_lists();
1749 	update_all_windows();
1750 }
1751 
1752 static int
command_cmd(const cmd_info_t * cmd_info)1753 command_cmd(const cmd_info_t *cmd_info)
1754 {
1755 	char *desc;
1756 
1757 	if(cmd_info->argc == 0)
1758 	{
1759 		cmds_preserve_selection();
1760 		return show_commands_menu(curr_view) != 0;
1761 	}
1762 
1763 	desc = vle_cmds_print_udcs(cmd_info->argv[0]);
1764 	if(desc == NULL)
1765 	{
1766 		ui_sb_msg("No user-defined commands found");
1767 		return 1;
1768 	}
1769 
1770 	ui_sb_msg(desc);
1771 	free(desc);
1772 	return 1;
1773 }
1774 
1775 /* Copies files. */
1776 static int
copy_cmd(const cmd_info_t * cmd_info)1777 copy_cmd(const cmd_info_t *cmd_info)
1778 {
1779 	return cpmv_cmd(cmd_info, 0);
1780 }
1781 
1782 /* Same as :quit, but also aborts directory choosing and mandatory returns
1783  * non-zero exit code. */
1784 static int
cquit_cmd(const cmd_info_t * cmd_info)1785 cquit_cmd(const cmd_info_t *cmd_info)
1786 {
1787 	vifm_try_leave(!cmd_info->emark, 1, cmd_info->emark);
1788 	return 0;
1789 }
1790 
1791 /* Unregisters command-line abbreviation either by its LHS or RHS. */
1792 static int
cunabbrev_cmd(const cmd_info_t * cmd_info)1793 cunabbrev_cmd(const cmd_info_t *cmd_info)
1794 {
1795 	wchar_t *const wargs = to_wide(cmd_info->args);
1796 	if(wargs == NULL)
1797 	{
1798 		show_error_msgf("Abbrevs Error", "Failed to convert to wide string: %s",
1799 				cmd_info->args);
1800 		return 0;
1801 	}
1802 
1803 	const int result = vle_abbr_remove(wargs);
1804 	free(wargs);
1805 	if(result != 0)
1806 	{
1807 		ui_sb_err("No such abbreviation");
1808 	}
1809 	return 0;
1810 }
1811 
1812 static int
cunmap_cmd(const cmd_info_t * cmd_info)1813 cunmap_cmd(const cmd_info_t *cmd_info)
1814 {
1815 	return do_unmap(cmd_info->argv[0], CMDLINE_MODE);
1816 }
1817 
1818 /* Processes :[range]delete command followed by "{reg} [{count}]" or
1819  * "{reg}|{count}" with optional " &". */
1820 static int
delete_cmd(const cmd_info_t * cmd_info)1821 delete_cmd(const cmd_info_t *cmd_info)
1822 {
1823 	int reg = DEFAULT_REG_NAME;
1824 	int result;
1825 
1826 	result = get_reg_and_count(cmd_info, &reg);
1827 	if(result != 0)
1828 	{
1829 		return result;
1830 	}
1831 
1832 	flist_set_marking(curr_view, 0);
1833 	if(cmd_info->bg)
1834 	{
1835 		result = fops_delete_bg(curr_view, !cmd_info->emark) != 0;
1836 	}
1837 	else
1838 	{
1839 		result = fops_delete(curr_view, reg, !cmd_info->emark) != 0;
1840 	}
1841 
1842 	return result;
1843 }
1844 
1845 static int
delmarks_cmd(const cmd_info_t * cmd_info)1846 delmarks_cmd(const cmd_info_t *cmd_info)
1847 {
1848 	int i;
1849 
1850 	if(cmd_info->emark)
1851 	{
1852 		if(cmd_info->argc == 0)
1853 		{
1854 			marks_clear_all();
1855 			return 0;
1856 		}
1857 		else
1858 		{
1859 			ui_sb_err("No arguments are allowed if you use \"!\"");
1860 			return 1;
1861 		}
1862 	}
1863 
1864 	if(cmd_info->argc == 0)
1865 	{
1866 		return CMDS_ERR_TOO_FEW_ARGS;
1867 	}
1868 
1869 	for(i = 0; i < cmd_info->argc; i++)
1870 	{
1871 		int j;
1872 		for(j = 0; cmd_info->argv[i][j] != '\0'; j++)
1873 		{
1874 			if(!char_is_one_of(marks_all, cmd_info->argv[i][j]))
1875 			{
1876 				return CMDS_ERR_INVALID_ARG;
1877 			}
1878 		}
1879 	}
1880 
1881 	for(i = 0; i < cmd_info->argc; i++)
1882 	{
1883 		int j;
1884 		for(j = 0; cmd_info->argv[i][j] != '\0'; j++)
1885 		{
1886 			marks_clear_one(curr_view, cmd_info->argv[i][j]);
1887 		}
1888 	}
1889 	return 0;
1890 }
1891 
1892 /* Removes bookmarks. */
1893 static int
delbmarks_cmd(const cmd_info_t * cmd_info)1894 delbmarks_cmd(const cmd_info_t *cmd_info)
1895 {
1896 	if(cmd_info->emark)
1897 	{
1898 		int i;
1899 
1900 		/* Remove all bookmarks. */
1901 		if(cmd_info->argc == 0)
1902 		{
1903 			bmarks_clear();
1904 			return 0;
1905 		}
1906 
1907 		/* Remove bookmarks from listed paths. */
1908 		for(i = 0; i < cmd_info->argc; ++i)
1909 		{
1910 			char *const path = make_bmark_path(cmd_info->argv[i]);
1911 			bmarks_remove(path);
1912 			free(path);
1913 		}
1914 	}
1915 	else if(cmd_info->argc == 0)
1916 	{
1917 		/* Remove bookmarks from current directory. */
1918 		char *const path = get_bmark_dir(cmd_info);
1919 		bmarks_remove(path);
1920 		free(path);
1921 	}
1922 	else
1923 	{
1924 		/* Remove set of bookmarks that include all of the specified tags. */
1925 		char *const tags = make_tags_list(cmd_info);
1926 		bmarks_find(tags, &remove_bmark, NULL);
1927 		free(tags);
1928 	}
1929 	return 0;
1930 }
1931 
1932 /* bmarks_find() callback that removes bookmarks. */
1933 static void
remove_bmark(const char path[],const char tags[],time_t timestamp,void * arg)1934 remove_bmark(const char path[], const char tags[], time_t timestamp, void *arg)
1935 {
1936 	/* It's safe to remove bookmark in the callback. */
1937 	bmarks_remove(path);
1938 }
1939 
1940 /* Formats path for a bookmark in a unified way for several commands.  Returns
1941  * newly allocated string with the path. */
1942 static char *
get_bmark_dir(const cmd_info_t * cmd_info)1943 get_bmark_dir(const cmd_info_t *cmd_info)
1944 {
1945 	const char *const cwd = flist_get_dir(curr_view);
1946 
1947 	if(cmd_info->emark)
1948 	{
1949 		return make_bmark_path(cmd_info->argv[0]);
1950 	}
1951 
1952 	return is_root_dir(cwd) ? strdup(cwd) : format_str("%s/", cwd);
1953 }
1954 
1955 /* Prepares path for a bookmark.  Returns newly allocated string. */
1956 static char *
make_bmark_path(const char path[])1957 make_bmark_path(const char path[])
1958 {
1959 	char *ret;
1960 	const char *const cwd = flist_get_dir(curr_view);
1961 	char *const expanded = replace_tilde(ma_expand_single(path));
1962 
1963 	if(is_path_absolute(expanded))
1964 	{
1965 		return expanded;
1966 	}
1967 
1968 	ret = format_str("%s%s%s", cwd, is_root_dir(cwd) ? "" : "/", expanded);
1969 	free(expanded);
1970 	return ret;
1971 }
1972 
1973 /* Compares files in one or two panes to produce their diff or lists of
1974  * duplicates or unique files. */
1975 static int
compare_cmd(const cmd_info_t * cmd_info)1976 compare_cmd(const cmd_info_t *cmd_info)
1977 {
1978 	CompareType ct = CT_CONTENTS;
1979 	ListType lt = LT_ALL;
1980 	int single_pane = 0, group_ids = 0, skip_empty = 0;
1981 	if(parse_compare_properties(cmd_info, &ct, &lt, &single_pane,
1982 				&group_ids, &skip_empty) != 0)
1983 	{
1984 		return 1;
1985 	}
1986 
1987 	return single_pane
1988 	     ? (compare_one_pane(curr_view, ct, lt, skip_empty) != 0)
1989 	     : (compare_two_panes(ct, lt, !group_ids, skip_empty) != 0);
1990 }
1991 
1992 /* Opens menu with contents of the last displayed menu with navigation to files
1993  * by default, if any. */
1994 static int
copen_cmd(const cmd_info_t * cmd_info)1995 copen_cmd(const cmd_info_t *cmd_info)
1996 {
1997 	return menus_unstash(curr_view) != 0;
1998 }
1999 
2000 /* Parses comparison properties.  Default values for arguments should be set
2001  * before the call.  Returns zero on success, otherwise non-zero is returned and
2002  * error message is displayed on the status bar. */
2003 static int
parse_compare_properties(const cmd_info_t * cmd_info,CompareType * ct,ListType * lt,int * single_pane,int * group_ids,int * skip_empty)2004 parse_compare_properties(const cmd_info_t *cmd_info, CompareType *ct,
2005 		ListType *lt, int *single_pane, int *group_ids, int *skip_empty)
2006 {
2007 	int i;
2008 	for(i = 0; i < cmd_info->argc; ++i)
2009 	{
2010 		const char *const property = cmd_info->argv[i];
2011 		if     (strcmp(property, "byname") == 0)     *ct = CT_NAME;
2012 		else if(strcmp(property, "bysize") == 0)     *ct = CT_SIZE;
2013 		else if(strcmp(property, "bycontents") == 0) *ct = CT_CONTENTS;
2014 		else if(strcmp(property, "listall") == 0)    *lt = LT_ALL;
2015 		else if(strcmp(property, "listunique") == 0) *lt = LT_UNIQUE;
2016 		else if(strcmp(property, "listdups") == 0)   *lt = LT_DUPS;
2017 		else if(strcmp(property, "ofboth") == 0)     *single_pane = 0;
2018 		else if(strcmp(property, "ofone") == 0)      *single_pane = 1;
2019 		else if(strcmp(property, "groupids") == 0)   *group_ids = 1;
2020 		else if(strcmp(property, "grouppaths") == 0) *group_ids = 0;
2021 		else if(strcmp(property, "skipempty") == 0)  *skip_empty = 1;
2022 		else
2023 		{
2024 			ui_sb_errf("Unknown comparison property: %s", property);
2025 			return 1;
2026 		}
2027 	}
2028 
2029 	return 0;
2030 }
2031 
2032 /* Deletes a session. */
2033 static int
delsession_cmd(const cmd_info_t * cmd_info)2034 delsession_cmd(const cmd_info_t *cmd_info)
2035 {
2036 	const char *session_name = cmd_info->argv[0];
2037 
2038 	if(!sessions_exists(session_name))
2039 	{
2040 		ui_sb_msgf("No stored sessions with such name: %s", session_name);
2041 		return 1;
2042 	}
2043 
2044 	if(sessions_remove(session_name) != 0)
2045 	{
2046 		ui_sb_msgf("Failed to delete a session: %s", session_name);
2047 		return 1;
2048 	}
2049 
2050 	return 0;
2051 }
2052 
2053 static int
dirs_cmd(const cmd_info_t * cmd_info)2054 dirs_cmd(const cmd_info_t *cmd_info)
2055 {
2056 	return show_dirstack_menu(curr_view) != 0;
2057 }
2058 
2059 /* Maps key sequence in dialogs expanding mappings in RHS. */
2060 static int
dmap_cmd(const cmd_info_t * cmd_info)2061 dmap_cmd(const cmd_info_t *cmd_info)
2062 {
2063 	return dialog_map(cmd_info, 0);
2064 }
2065 
2066 /* Maps key sequence in dialogs not expanding mappings in RHS. */
2067 static int
dnoremap_cmd(const cmd_info_t * cmd_info)2068 dnoremap_cmd(const cmd_info_t *cmd_info)
2069 {
2070 	return dialog_map(cmd_info, 1);
2071 }
2072 
2073 /* Implementation of :dmap and :dnoremap.  Returns cmds unit friendly code. */
2074 static int
dialog_map(const cmd_info_t * cmd_info,int no_remap)2075 dialog_map(const cmd_info_t *cmd_info, int no_remap)
2076 {
2077 	int result;
2078 	if(cmd_info->argc <= 1)
2079 	{
2080 		result = do_map(cmd_info, "Dialog", SORT_MODE, no_remap);
2081 	}
2082 	else
2083 	{
2084 		result = do_map(cmd_info, "", SORT_MODE, no_remap);
2085 		result = result == 0 ? do_map(cmd_info, "", ATTR_MODE, no_remap) : result;
2086 		result = result == 0 ? do_map(cmd_info, "", CHANGE_MODE, no_remap) : result;
2087 		result = result == 0
2088 		       ? do_map(cmd_info, "", FILE_INFO_MODE, no_remap)
2089 		       : result;
2090 	}
2091 	return result != 0;
2092 }
2093 
2094 /* Unmaps key sequence in dialogs. */
2095 static int
dunmap_cmd(const cmd_info_t * cmd_info)2096 dunmap_cmd(const cmd_info_t *cmd_info)
2097 {
2098 	const char *lhs = cmd_info->argv[0];
2099 	int result = do_unmap(lhs, SORT_MODE);
2100 	result = result == 0 ? do_unmap(lhs, ATTR_MODE) : result;
2101 	result = result == 0 ? do_unmap(lhs, CHANGE_MODE) : result;
2102 	result = result == 0 ? do_unmap(lhs, FILE_INFO_MODE) : result;
2103 	return result != 0;
2104 }
2105 
2106 /* Evaluates arguments as expression and outputs result to statusbar. */
2107 static int
echo_cmd(const cmd_info_t * cmd_info)2108 echo_cmd(const cmd_info_t *cmd_info)
2109 {
2110 	char *const eval_result = try_eval_arglist(cmd_info);
2111 	ui_sb_msg(eval_result);
2112 	free(eval_result);
2113 	return 1;
2114 }
2115 
2116 /* Edits current/selected/specified file(s) in editor. */
2117 static int
edit_cmd(const cmd_info_t * cmd_info)2118 edit_cmd(const cmd_info_t *cmd_info)
2119 {
2120 	flist_set_marking(curr_view, 1);
2121 
2122 	if(cmd_info->argc != 0)
2123 	{
2124 		if(stats_file_choose_action_set())
2125 		{
2126 			/* The call below does not return. */
2127 			vifm_choose_files(curr_view, cmd_info->argc, cmd_info->argv);
2128 		}
2129 
2130 		vim_edit_files(cmd_info->argc, cmd_info->argv);
2131 		return 0;
2132 	}
2133 
2134 	dir_entry_t *entry = NULL;
2135 	while(iter_marked_entries(curr_view, &entry))
2136 	{
2137 		char full_path[PATH_MAX + 1];
2138 		get_full_path_of(entry, sizeof(full_path), full_path);
2139 
2140 		if(path_exists(full_path, DEREF) && !path_exists(full_path, NODEREF))
2141 		{
2142 			show_error_msgf("Access error",
2143 					"Can't access destination of link \"%s\". It might be broken.",
2144 					full_path);
2145 			return 0;
2146 		}
2147 	}
2148 
2149 	/* Reuse marking second time (for vifm_choose_files() or
2150 	 * vim_edit_marking()). */
2151 	curr_view->pending_marking = 1;
2152 
2153 	if(stats_file_choose_action_set())
2154 	{
2155 		/* The call below does not return. */
2156 		vifm_choose_files(curr_view, 0, NULL);
2157 	}
2158 
2159 	if(vim_edit_marking() != 0)
2160 	{
2161 		show_error_msg("Edit error", "Can't edit selection");
2162 	}
2163 	return 0;
2164 }
2165 
2166 /* This command designates beginning of the alternative part of if-endif
2167  * statement. */
2168 static int
else_cmd(const cmd_info_t * cmd_info)2169 else_cmd(const cmd_info_t *cmd_info)
2170 {
2171 	if(cmds_scoped_else() != 0)
2172 	{
2173 		ui_sb_err("Misplaced :else");
2174 		return CMDS_ERR_CUSTOM;
2175 	}
2176 	return 0;
2177 }
2178 
2179 /* This command designates beginning of the alternative branch of if-endif
2180  * statement with its own condition. */
2181 static int
elseif_cmd(const cmd_info_t * cmd_info)2182 elseif_cmd(const cmd_info_t *cmd_info)
2183 {
2184 	const int x = eval_if_condition(cmd_info);
2185 	if(x < 0)
2186 	{
2187 		return CMDS_ERR_CUSTOM;
2188 	}
2189 
2190 	if(cmds_scoped_elseif(x) != 0)
2191 	{
2192 		ui_sb_err("Misplaced :elseif");
2193 		return CMDS_ERR_CUSTOM;
2194 	}
2195 
2196 	return 0;
2197 }
2198 
2199 /* Starts process of emptying all trashes in background. */
2200 static int
empty_cmd(const cmd_info_t * cmd_info)2201 empty_cmd(const cmd_info_t *cmd_info)
2202 {
2203 	trash_empty_all();
2204 	return 0;
2205 }
2206 
2207 /* This command ends conditional block. */
2208 static int
endif_cmd(const cmd_info_t * cmd_info)2209 endif_cmd(const cmd_info_t *cmd_info)
2210 {
2211 	if(cmds_scoped_endif() != 0)
2212 	{
2213 		ui_sb_err(":endif without :if");
2214 		return CMDS_ERR_CUSTOM;
2215 	}
2216 	return 0;
2217 }
2218 
2219 /* This command composes a string from expressions and runs it as a command. */
2220 static int
exe_cmd(const cmd_info_t * cmd_info)2221 exe_cmd(const cmd_info_t *cmd_info)
2222 {
2223 	int result = 1;
2224 	char *const eval_result = try_eval_arglist(cmd_info);
2225 	if(eval_result != NULL)
2226 	{
2227 		result = exec_commands(eval_result, curr_view, CIT_COMMAND);
2228 		free(eval_result);
2229 	}
2230 	return result != 0;
2231 }
2232 
2233 /* Tries to evaluate a set of expressions and concatenate results with a space.
2234  * Returns pointer to newly allocated string, which should be freed by caller,
2235  * or NULL on error. */
2236 static char *
try_eval_arglist(const cmd_info_t * cmd_info)2237 try_eval_arglist(const cmd_info_t *cmd_info)
2238 {
2239 	char *eval_result;
2240 	const char *error_pos = NULL;
2241 
2242 	if(cmd_info->argc == 0)
2243 	{
2244 		return NULL;
2245 	}
2246 
2247 	vle_tb_clear(vle_err);
2248 	eval_result = eval_arglist(cmd_info->raw_args, &error_pos);
2249 
2250 	if(eval_result == NULL)
2251 	{
2252 		vle_tb_append_linef(vle_err, "%s: %s", "Invalid expression", error_pos);
2253 		ui_sb_err(vle_tb_get_data(vle_err));
2254 	}
2255 
2256 	return eval_result;
2257 }
2258 
2259 /* Displays file handler picking menu. */
2260 static int
file_cmd(const cmd_info_t * cmd_info)2261 file_cmd(const cmd_info_t *cmd_info)
2262 {
2263 	if(cmd_info->argc == 0)
2264 	{
2265 		cmds_preserve_selection();
2266 		return show_file_menu(curr_view, cmd_info->bg) != 0;
2267 	}
2268 
2269 	if(rn_open_with_match(curr_view, cmd_info->argv[0], cmd_info->bg) != 0)
2270 	{
2271 		ui_sb_err("Can't find associated program with requested beginning");
2272 		return 1;
2273 	}
2274 
2275 	return 0;
2276 }
2277 
2278 /* Registers non-x file association handler. */
2279 static int
filetype_cmd(const cmd_info_t * cmd_info)2280 filetype_cmd(const cmd_info_t *cmd_info)
2281 {
2282 	return add_assoc(cmd_info, 0, 0);
2283 }
2284 
2285 /* Registers x file association handler. */
2286 static int
filextype_cmd(const cmd_info_t * cmd_info)2287 filextype_cmd(const cmd_info_t *cmd_info)
2288 {
2289 	return add_assoc(cmd_info, 0, 1);
2290 }
2291 
2292 /* Registers external applications as file viewer scripts for files that match
2293  * name pattern.  Single argument form lists currently registered patterns that
2294  * match specified file name in menu mode. */
2295 static int
fileviewer_cmd(const cmd_info_t * cmd_info)2296 fileviewer_cmd(const cmd_info_t *cmd_info)
2297 {
2298 	return add_assoc(cmd_info, 1, 0);
2299 }
2300 
2301 /* Registers x/non-x or viewer file association handler.  Single argument form
2302  * lists currently registered patterns that match specified file name in menu
2303  * mode.  Returns regular *_cmd handler value. */
2304 static int
add_assoc(const cmd_info_t * cmd_info,int viewer,int for_x)2305 add_assoc(const cmd_info_t *cmd_info, int viewer, int for_x)
2306 {
2307 	char **matchers;
2308 	int nmatchers;
2309 	int i;
2310 	const int in_x = (curr_stats.exec_env_type == EET_EMULATOR_WITH_X);
2311 	const char *const records = vle_cmds_next_arg(cmd_info->args);
2312 
2313 	if(cmd_info->argc == 1)
2314 	{
2315 		return viewer
2316 		     ? (show_fileviewers_menu(curr_view, cmd_info->argv[0]) != 0)
2317 		     : (show_fileprograms_menu(curr_view, cmd_info->argv[0]) != 0);
2318 	}
2319 
2320 	matchers = matchers_list(cmd_info->argv[0], &nmatchers);
2321 	if(matchers == NULL)
2322 	{
2323 		ui_sb_err("Not enough memory.");
2324 		return 1;
2325 	}
2326 
2327 	for(i = 0; i < nmatchers; ++i)
2328 	{
2329 		char *error;
2330 		matchers_t *const ms = matchers_alloc(matchers[i], 0, 1, "", &error);
2331 		if(ms == NULL)
2332 		{
2333 			ui_sb_errf("Wrong pattern (%s): %s", matchers[i], error);
2334 			free(error);
2335 			free_string_array(matchers, nmatchers);
2336 			return 1;
2337 		}
2338 
2339 		if(viewer)
2340 		{
2341 			ft_set_viewers(ms, records);
2342 		}
2343 		else
2344 		{
2345 			ft_set_programs(ms, records, for_x, in_x);
2346 		}
2347 	}
2348 
2349 	free_string_array(matchers, nmatchers);
2350 	return 0;
2351 }
2352 
2353 /* Sets/displays/clears filters. */
2354 static int
filter_cmd(const cmd_info_t * cmd_info)2355 filter_cmd(const cmd_info_t *cmd_info)
2356 {
2357 	int ret;
2358 
2359 	if(cmd_info->qmark)
2360 	{
2361 		display_filters_info(curr_view);
2362 		return 1;
2363 	}
2364 
2365 	ret = update_filter(curr_view, cmd_info);
2366 	if(curr_stats.global_local_settings)
2367 	{
2368 		int i;
2369 		tab_info_t tab_info;
2370 		for(i = 0; tabs_enum_all(i, &tab_info); ++i)
2371 		{
2372 			if(tab_info.view != curr_view)
2373 			{
2374 				ret |= update_filter(tab_info.view, cmd_info);
2375 			}
2376 		}
2377 	}
2378 
2379 	return ret;
2380 }
2381 
2382 /* Updates filters of the view. */
2383 static int
update_filter(view_t * view,const cmd_info_t * cmd_info)2384 update_filter(view_t *view, const cmd_info_t *cmd_info)
2385 {
2386 	const char *fallback = hists_search_last();
2387 
2388 	if(cmd_info->argc == 0)
2389 	{
2390 		if(cmd_info->emark)
2391 		{
2392 			filters_invert(view);
2393 			return 0;
2394 		}
2395 
2396 		/* When no arguments are provided, we don't want to fall back to last
2397 		 * history entry. */
2398 		fallback = "";
2399 	}
2400 
2401 	return set_view_filter(view, cmd_info->args, fallback,
2402 			get_filter_inversion_state(cmd_info)) != 0;
2403 }
2404 
2405 /* Displays state of all filters on the status bar. */
2406 static void
display_filters_info(const view_t * view)2407 display_filters_info(const view_t *view)
2408 {
2409 	char *const localf = get_filter_info("Local", &view->local_filter.filter);
2410 	char *const manualf = get_matcher_info("Explicit", view->manual_filter);
2411 	char *const autof = get_filter_info("Implicit", &view->auto_filter);
2412 
2413 	ui_sb_msgf("  Filter -- Flags -- Value\n%s\n%s\n%s", localf, manualf, autof);
2414 
2415 	free(localf);
2416 	free(manualf);
2417 	free(autof);
2418 }
2419 
2420 /* Composes a description string for given filter.  Returns NULL on out of
2421  * memory error, otherwise a newly allocated string, which should be freed by
2422  * the caller, is returned. */
2423 static char *
get_filter_info(const char name[],const filter_t * filter)2424 get_filter_info(const char name[], const filter_t *filter)
2425 {
2426 	const char *flags_str;
2427 
2428 	if(filter_is_empty(filter))
2429 	{
2430 		flags_str = "";
2431 	}
2432 	else
2433 	{
2434 		flags_str = (filter->cflags & REG_ICASE) ? "i" : "I";
2435 	}
2436 
2437 	return format_str("%-8s    %-5s    %s", name, flags_str, filter->raw);
2438 }
2439 
2440 /* Composes description string for given matcher.  Returns NULL on out of
2441  * memory error, otherwise a newly allocated string, which should be freed by
2442  * the caller, is returned. */
2443 static char *
get_matcher_info(const char name[],const matcher_t * matcher)2444 get_matcher_info(const char name[], const matcher_t *matcher)
2445 {
2446 	const char *const flags = matcher_is_empty(matcher) ? "" : "---->";
2447 	const char *const value = matcher_get_expr(matcher);
2448 	return format_str("%-8s    %-5s    %s", name, flags, value);
2449 }
2450 
2451 /* Returns value for filter inversion basing on current configuration and
2452  * filter command. */
2453 static int
get_filter_inversion_state(const cmd_info_t * cmd_info)2454 get_filter_inversion_state(const cmd_info_t *cmd_info)
2455 {
2456 	int invert_filter = cfg.filter_inverted_by_default;
2457 	if(cmd_info->emark)
2458 	{
2459 		invert_filter = !invert_filter;
2460 	}
2461 	return invert_filter;
2462 }
2463 
2464 /* Tries to update filter of the view rejecting incorrect regular expression.
2465  * On empty pattern fallback is used.  Returns non-zero if message on the
2466  * statusbar should be saved, otherwise zero is returned. */
2467 static int
set_view_filter(view_t * view,const char filter[],const char fallback[],int invert)2468 set_view_filter(view_t *view, const char filter[], const char fallback[],
2469 		int invert)
2470 {
2471 	char *error;
2472 	matcher_t *const matcher = matcher_alloc(filter, FILTER_DEF_CASE_SENSITIVITY,
2473 			0, fallback, &error);
2474 	if(matcher == NULL)
2475 	{
2476 		ui_sb_errf("Name filter not set: %s", error);
2477 		free(error);
2478 		return 1;
2479 	}
2480 
2481 	view->invert = invert;
2482 	matcher_free(view->manual_filter);
2483 	view->manual_filter = matcher;
2484 	(void)filter_clear(&view->auto_filter);
2485 	ui_view_schedule_reload(view);
2486 	return 0;
2487 }
2488 
2489 /* Looks for files matching pattern. */
2490 static int
find_cmd(const cmd_info_t * cmd_info)2491 find_cmd(const cmd_info_t *cmd_info)
2492 {
2493 	if(cmd_info->argc > 0)
2494 	{
2495 		if(cmd_info->argc == 1)
2496 			cmds_state.find.includes_path = 0;
2497 		else if(is_dir(cmd_info->argv[0]))
2498 			cmds_state.find.includes_path = 1;
2499 		else
2500 			cmds_state.find.includes_path = 0;
2501 
2502 		(void)replace_string(&cmds_state.find.last_args, cmd_info->args);
2503 	}
2504 	else if(cmds_state.find.last_args == NULL)
2505 	{
2506 		ui_sb_err("Nothing to repeat");
2507 		return 1;
2508 	}
2509 
2510 	return show_find_menu(curr_view, cmds_state.find.includes_path,
2511 			cmds_state.find.last_args) != 0;
2512 }
2513 
2514 static int
finish_cmd(const cmd_info_t * cmd_info)2515 finish_cmd(const cmd_info_t *cmd_info)
2516 {
2517 	if(curr_stats.sourcing_state != SOURCING_PROCESSING)
2518 	{
2519 		ui_sb_err(":finish used outside of a sourced file");
2520 		return 1;
2521 	}
2522 
2523 	curr_stats.sourcing_state = SOURCING_FINISHING;
2524 	commands_scope_escape();
2525 	return 0;
2526 }
2527 
2528 /* Changes view to have specified file/directory under the cursor. */
2529 static int
goto_path_cmd(const cmd_info_t * cmd_info)2530 goto_path_cmd(const cmd_info_t *cmd_info)
2531 {
2532 	char abs_path[PATH_MAX + 1];
2533 	char *fname;
2534 
2535 	char *const expanded = expand_tilde(cmd_info->argv[0]);
2536 	to_canonic_path(expanded, flist_get_dir(curr_view), abs_path,
2537 			sizeof(abs_path));
2538 	free(expanded);
2539 
2540 	if(is_root_dir(abs_path))
2541 	{
2542 		ui_sb_errf("Can't navigate to root directory: %s", abs_path);
2543 		return 1;
2544 	}
2545 
2546 	if(!path_exists(abs_path, NODEREF))
2547 	{
2548 		ui_sb_errf("Path doesn't exist: %s", abs_path);
2549 		return 1;
2550 	}
2551 
2552 	fname = strdup(get_last_path_component(abs_path));
2553 	remove_last_path_component(abs_path);
2554 	navigate_to_file(curr_view, abs_path, fname, 0);
2555 	free(fname);
2556 	return 0;
2557 }
2558 
2559 static int
grep_cmd(const cmd_info_t * cmd_info)2560 grep_cmd(const cmd_info_t *cmd_info)
2561 {
2562 	static char *last_args;
2563 	static int last_invert;
2564 	int inv;
2565 
2566 	if(cmd_info->argc > 0)
2567 	{
2568 		(void)replace_string(&last_args, cmd_info->args);
2569 		last_invert = cmd_info->emark;
2570 	}
2571 	else if(last_args == NULL)
2572 	{
2573 		ui_sb_err("Nothing to repeat");
2574 		return 1;
2575 	}
2576 
2577 	inv = last_invert;
2578 	if(cmd_info->argc == 0 && cmd_info->emark)
2579 		inv = !inv;
2580 
2581 	return show_grep_menu(curr_view, last_args, inv) != 0;
2582 }
2583 
2584 /* Displays documentation. */
2585 static int
help_cmd(const cmd_info_t * cmd_info)2586 help_cmd(const cmd_info_t *cmd_info)
2587 {
2588 	char cmd[PATH_MAX + 1];
2589 	int bg;
2590 
2591 	if(cfg.use_vim_help)
2592 	{
2593 		const char *topic = (cmd_info->argc > 0) ? cmd_info->args : VIFM_VIM_HELP;
2594 		bg = vim_format_help_cmd(topic, cmd, sizeof(cmd));
2595 	}
2596 	else
2597 	{
2598 		if(cmd_info->argc != 0)
2599 		{
2600 			ui_sb_err("No arguments are allowed when 'vimhelp' option is off");
2601 			return 1;
2602 		}
2603 
2604 		if(!path_exists_at(cfg.config_dir, VIFM_HELP, DEREF))
2605 		{
2606 			show_error_msgf("No help file", "Can't find \"%s/" VIFM_HELP "\" file",
2607 					cfg.config_dir);
2608 			return 0;
2609 		}
2610 
2611 		bg = format_help_cmd(cmd, sizeof(cmd));
2612 	}
2613 
2614 	if(bg)
2615 	{
2616 		bg_run_external(cmd, 0, SHELL_BY_APP);
2617 	}
2618 	else
2619 	{
2620 		display_help(cmd);
2621 	}
2622 	return 0;
2623 }
2624 
2625 /* Hides interface to show previous commands' output. */
2626 static int
hideui_cmd(const cmd_info_t * cmd_info)2627 hideui_cmd(const cmd_info_t *cmd_info)
2628 {
2629 	ui_pause();
2630 	return 0;
2631 }
2632 
2633 /* Handles :highlight command.  There are three forms:
2634  *  - clear all
2635  *  - highlight file
2636  *  - highlight group */
2637 static int
highlight_cmd(const cmd_info_t * cmd_info)2638 highlight_cmd(const cmd_info_t *cmd_info)
2639 {
2640 	if(cmd_info->argc == 0)
2641 	{
2642 		ui_sb_msg(get_all_highlights());
2643 		return 1;
2644 	}
2645 
2646 	if(strcasecmp(cmd_info->argv[0], "clear") == 0)
2647 	{
2648 		return highlight_clear(cmd_info);
2649 	}
2650 
2651 	if(matchers_is_expr(cmd_info->argv[0]))
2652 	{
2653 		return highlight_file(cmd_info);
2654 	}
2655 
2656 	return highlight_group(cmd_info);
2657 }
2658 
2659 /* Handles clear form of :highlight command.  Returns value to be returned by
2660  * command handler. */
2661 static int
highlight_clear(const cmd_info_t * cmd_info)2662 highlight_clear(const cmd_info_t *cmd_info)
2663 {
2664 	if(cmd_info->argc == 2)
2665 	{
2666 		if(!cs_del_file_hi(cmd_info->argv[1]))
2667 		{
2668 			ui_sb_errf("No such group: %s", cmd_info->argv[1]);
2669 			return 1;
2670 		}
2671 
2672 		ui_invalidate_cs(curr_stats.cs);
2673 
2674 		/* Redraw to update filename specific highlights. */
2675 		stats_redraw_later();
2676 
2677 		return 0;
2678 	}
2679 
2680 	if(cmd_info->argc == 1)
2681 	{
2682 		cs_reset(curr_stats.cs);
2683 		ui_invalidate_cs(curr_stats.cs);
2684 
2685 		/* Request full update instead of redraw to force recalculation of mixed
2686 		 * colors like cursor line, which otherwise are not updated. */
2687 		stats_refresh_later();
2688 		return 0;
2689 	}
2690 
2691 	return CMDS_ERR_TRAILING_CHARS;
2692 }
2693 
2694 /* Handles highlight-file form of :highlight command.  Returns value to be
2695  * returned by command handler. */
2696 static int
highlight_file(const cmd_info_t * cmd_info)2697 highlight_file(const cmd_info_t *cmd_info)
2698 {
2699 	char pattern[strlen(cmd_info->args) + 1];
2700 	col_attr_t color = { .fg = -1, .bg = -1, .attr = 0, };
2701 	int result;
2702 	matchers_t *matchers;
2703 	char *error;
2704 
2705 	(void)extract_part(cmd_info->args, " \t", pattern);
2706 
2707 	matchers = matchers_alloc(pattern, 0, 1, "", &error);
2708 	if(matchers == NULL)
2709 	{
2710 		ui_sb_errf("Pattern error: %s", error);
2711 		free(error);
2712 		return CMDS_ERR_CUSTOM;
2713 	}
2714 
2715 	if(cmd_info->argc == 1)
2716 	{
2717 		display_file_highlights(matchers);
2718 		matchers_free(matchers);
2719 		return 1;
2720 	}
2721 
2722 	result = parse_file_highlight(cmd_info, &color);
2723 	if(result != 0)
2724 	{
2725 		matchers_free(matchers);
2726 		return result;
2727 	}
2728 
2729 	cs_add_file_hi(matchers, &color);
2730 	/* We don't need to invalidate anything on startup and while loading a color
2731 	 * scheme. */
2732 	if(curr_stats.load_stage > 1 && curr_stats.cs->state != CSS_LOADING)
2733 	{
2734 		ui_invalidate_cs(curr_stats.cs);
2735 	}
2736 
2737 	/* Redraw to update filename specific highlights. */
2738 	stats_redraw_later();
2739 
2740 	return result;
2741 }
2742 
2743 /* Displays information about filename specific highlight on the status bar. */
2744 static void
display_file_highlights(const matchers_t * matchers)2745 display_file_highlights(const matchers_t *matchers)
2746 {
2747 	int i;
2748 
2749 	const col_scheme_t *cs = ui_view_get_cs(curr_view);
2750 
2751 	for(i = 0; i < cs->file_hi_count; ++i)
2752 	{
2753 		if(matchers_includes(cs->file_hi[i].matchers, matchers))
2754 		{
2755 			break;
2756 		}
2757 	}
2758 
2759 	if(i >= cs->file_hi_count)
2760 	{
2761 		ui_sb_errf("Highlight group not found: %s", matchers_get_expr(matchers));
2762 		return;
2763 	}
2764 
2765 	ui_sb_msg(get_file_hi_str(cs->file_hi[i].matchers, &cs->file_hi[i].hi));
2766 }
2767 
2768 /* Handles highlight-group form of :highlight command.  Returns value to be
2769  * returned by command handler. */
2770 static int
highlight_group(const cmd_info_t * cmd_info)2771 highlight_group(const cmd_info_t *cmd_info)
2772 {
2773 	int result;
2774 	int group_id;
2775 	col_attr_t tmp_color;
2776 	col_attr_t *color;
2777 
2778 	group_id = string_array_pos_case(HI_GROUPS, MAXNUM_COLOR, cmd_info->argv[0]);
2779 	if(group_id < 0)
2780 	{
2781 		ui_sb_errf("Highlight group not found: %s", cmd_info->argv[0]);
2782 		return 1;
2783 	}
2784 
2785 	color = &curr_stats.cs->color[group_id];
2786 
2787 	if(cmd_info->argc == 1)
2788 	{
2789 		ui_sb_msg(get_group_str(group_id, color));
2790 		return 1;
2791 	}
2792 
2793 	tmp_color = *color;
2794 	result = parse_file_highlight(cmd_info, &tmp_color);
2795 	if(result != 0)
2796 	{
2797 		return result;
2798 	}
2799 
2800 	*color = tmp_color;
2801 	curr_stats.cs->pair[group_id] = colmgr_get_pair(color->fg, color->bg);
2802 
2803 	/* Other highlight commands might have finished successfully, so update TUI.
2804 	 * Request full update instead of redraw to force recalculation of mixed
2805 	 * colors like cursor line, which otherwise are not updated. */
2806 	stats_refresh_later();
2807 
2808 	return result;
2809 }
2810 
2811 /* Composes string representation of all highlight group definitions.  Returns
2812  * pointer to statically allocated buffer. */
2813 static const char *
get_all_highlights(void)2814 get_all_highlights(void)
2815 {
2816 	static char msg[256*MAXNUM_COLOR];
2817 
2818 	const col_scheme_t *cs = ui_view_get_cs(curr_view);
2819 	size_t msg_len = 0U;
2820 	int i;
2821 
2822 	msg[0] = '\0';
2823 
2824 	for(i = 0; i < MAXNUM_COLOR; ++i)
2825 	{
2826 		snprintf(msg + msg_len, sizeof(msg) - msg_len, "%s%s",
2827 				get_group_str(i, &cs->color[i]), (i < MAXNUM_COLOR - 1) ? "\n" : "");
2828 		msg_len += strlen(msg + msg_len);
2829 	}
2830 
2831 	if(cs->file_hi_count <= 0)
2832 	{
2833 		return msg;
2834 	}
2835 
2836 	snprintf(msg + msg_len, sizeof(msg) - msg_len, "\n\n");
2837 	msg_len += strlen(msg + msg_len);
2838 
2839 	for(i = 0; i < cs->file_hi_count; ++i)
2840 	{
2841 		const file_hi_t *const file_hi = &cs->file_hi[i];
2842 		const char *const line = get_file_hi_str(file_hi->matchers, &file_hi->hi);
2843 		snprintf(msg + msg_len, sizeof(msg) - msg_len, "%s%s", line,
2844 				(i < cs->file_hi_count - 1) ? "\n" : "");
2845 		msg_len += strlen(msg + msg_len);
2846 	}
2847 
2848 	return msg;
2849 }
2850 
2851 /* Composes string representation of highlight group definition.  Returns
2852  * pointer to a statically allocated buffer. */
2853 static const char *
get_group_str(int group,const col_attr_t * col)2854 get_group_str(int group, const col_attr_t *col)
2855 {
2856 	return get_hi_str(HI_GROUPS[group], col);
2857 }
2858 
2859 /* Composes string representation of filename specific highlight definition.
2860  * Returns pointer to a statically allocated buffer. */
2861 static const char *
get_file_hi_str(const matchers_t * matchers,const col_attr_t * col)2862 get_file_hi_str(const matchers_t *matchers, const col_attr_t *col)
2863 {
2864 	return get_hi_str(matchers_get_expr(matchers), col);
2865 }
2866 
2867 /* Composes string representation of highlight definition.  Returns pointer to a
2868  * statically allocated buffer. */
2869 static const char *
get_hi_str(const char title[],const col_attr_t * col)2870 get_hi_str(const char title[], const col_attr_t *col)
2871 {
2872 	static char buf[256];
2873 
2874 	char fg_buf[16], bg_buf[16];
2875 
2876 	cs_color_to_str(col->fg, sizeof(fg_buf), fg_buf);
2877 	cs_color_to_str(col->bg, sizeof(bg_buf), bg_buf);
2878 
2879 	snprintf(buf, sizeof(buf), "%-10s cterm=%s ctermfg=%-7s ctermbg=%-7s", title,
2880 			cs_attrs_to_str(col->attr), fg_buf, bg_buf);
2881 
2882 	return buf;
2883 }
2884 
2885 /* Parses arguments of :highlight command.  Returns non-zero in case of error
2886  * and prints a message on the status bar, on success zero is returned. */
2887 static int
parse_file_highlight(const cmd_info_t * cmd_info,col_attr_t * color)2888 parse_file_highlight(const cmd_info_t *cmd_info, col_attr_t *color)
2889 {
2890 	int i;
2891 
2892 	for(i = 1; i < cmd_info->argc; ++i)
2893 	{
2894 		const char *const arg = cmd_info->argv[i];
2895 		const char *const equal = strchr(arg, '=');
2896 		char arg_name[16];
2897 
2898 		if(equal == NULL)
2899 		{
2900 			ui_sb_errf("Missing equal sign in \"%s\"", arg);
2901 			return 1;
2902 		}
2903 		if(equal[1] == '\0')
2904 		{
2905 			ui_sb_errf("Missing argument: %s", arg);
2906 			return 1;
2907 		}
2908 
2909 		copy_str(arg_name, MIN(sizeof(arg_name), (size_t)(equal - arg + 1)), arg);
2910 
2911 		if(strcmp(arg_name, "ctermbg") == 0)
2912 		{
2913 			if(try_parse_color_name_value(equal + 1, 0, color) != 0)
2914 			{
2915 				return 1;
2916 			}
2917 		}
2918 		else if(strcmp(arg_name, "ctermfg") == 0)
2919 		{
2920 			if(try_parse_color_name_value(equal + 1, 1, color) != 0)
2921 			{
2922 				return 1;
2923 			}
2924 		}
2925 		else if(strcmp(arg_name, "cterm") == 0)
2926 		{
2927 			int attrs;
2928 			if((attrs = get_attrs(equal + 1)) == -1)
2929 			{
2930 				ui_sb_errf("Illegal argument: %s", equal + 1);
2931 				return 1;
2932 			}
2933 			color->attr = attrs;
2934 			if(curr_stats.exec_env_type == EET_LINUX_NATIVE &&
2935 					(attrs & (A_BOLD | A_REVERSE)) == (A_BOLD | A_REVERSE))
2936 			{
2937 				color->attr |= A_BLINK;
2938 			}
2939 		}
2940 		else
2941 		{
2942 			ui_sb_errf("Illegal argument: %s", arg);
2943 			return 1;
2944 		}
2945 	}
2946 
2947 	return 0;
2948 }
2949 
2950 /* Tries to parse color name value into a number.  Returns non-zero if status
2951  * bar message should be preserved, otherwise zero is returned. */
2952 static int
try_parse_color_name_value(const char str[],int fg,col_attr_t * color)2953 try_parse_color_name_value(const char str[], int fg, col_attr_t *color)
2954 {
2955 	col_scheme_t *const cs = curr_stats.cs;
2956 	const int col_num = parse_color_name_value(str, fg, &color->attr);
2957 
2958 	if(col_num < -1)
2959 	{
2960 		ui_sb_errf("Color name or number not recognized: %s", str);
2961 		if(cs->state == CSS_LOADING)
2962 		{
2963 			cs->state = CSS_BROKEN;
2964 		}
2965 
2966 		return 1;
2967 	}
2968 
2969 	if(fg)
2970 	{
2971 		color->fg = col_num;
2972 	}
2973 	else
2974 	{
2975 		color->bg = col_num;
2976 	}
2977 
2978 	return 0;
2979 }
2980 
2981 /* Parses color string into color number and alters *attr in some cases.
2982  * Returns value less than -1 to indicate error as -1 is valid return value. */
2983 static int
parse_color_name_value(const char str[],int fg,int * attr)2984 parse_color_name_value(const char str[], int fg, int *attr)
2985 {
2986 	int col_pos;
2987 	int light_col_pos;
2988 	int col_num;
2989 
2990 	if(strcmp(str, "-1") == 0 || strcasecmp(str, "default") == 0 ||
2991 			strcasecmp(str, "none") == 0)
2992 	{
2993 		return -1;
2994 	}
2995 
2996 	light_col_pos = string_array_pos_case(LIGHT_COLOR_NAMES,
2997 			ARRAY_LEN(LIGHT_COLOR_NAMES), str);
2998 	if(light_col_pos >= 0)
2999 	{
3000 		*attr |= (!fg && curr_stats.exec_env_type == EET_LINUX_NATIVE) ?
3001 				A_BLINK : A_BOLD;
3002 		return light_col_pos;
3003 	}
3004 
3005 	col_pos = string_array_pos_case(XTERM256_COLOR_NAMES,
3006 			ARRAY_LEN(XTERM256_COLOR_NAMES), str);
3007 	if(col_pos >= 0)
3008 	{
3009 		if(!fg && curr_stats.exec_env_type == EET_LINUX_NATIVE)
3010 		{
3011 			*attr &= ~A_BLINK;
3012 		}
3013 		return col_pos;
3014 	}
3015 
3016 	col_num = isdigit(*str) ? atoi(str) : -1;
3017 	if(col_num >= 0 && col_num < COLORS)
3018 	{
3019 		return col_num;
3020 	}
3021 
3022 	/* Fail if all possible parsing ways failed. */
3023 	return -2;
3024 }
3025 
3026 static int
get_attrs(const char * text)3027 get_attrs(const char *text)
3028 {
3029 #ifdef HAVE_A_ITALIC_DECL
3030 	const int italic_attr = A_ITALIC;
3031 #else
3032 	/* If A_ITALIC is missing (it's an extension), use A_REVERSE instead. */
3033 	const int italic_attr = A_REVERSE;
3034 #endif
3035 
3036 	int result = 0;
3037 	while(*text != '\0')
3038 	{
3039 		const char *const p = until_first(text, ',');
3040 		char buf[64];
3041 
3042 		copy_str(buf, p - text + 1, text);
3043 		if(strcasecmp(buf, "bold") == 0)
3044 			result |= A_BOLD;
3045 		else if(strcasecmp(buf, "underline") == 0)
3046 			result |= A_UNDERLINE;
3047 		else if(strcasecmp(buf, "reverse") == 0)
3048 			result |= A_REVERSE;
3049 		else if(strcasecmp(buf, "inverse") == 0)
3050 			result |= A_REVERSE;
3051 		else if(strcasecmp(buf, "standout") == 0)
3052 			result |= A_STANDOUT;
3053 		else if(strcasecmp(buf, "italic") == 0)
3054 			result |= italic_attr;
3055 		else if(strcasecmp(buf, "none") == 0)
3056 			result = 0;
3057 		else
3058 			return -1;
3059 
3060 		text = (*p == '\0') ? p : p + 1;
3061 	}
3062 	return result;
3063 }
3064 
3065 static int
history_cmd(const cmd_info_t * cmd_info)3066 history_cmd(const cmd_info_t *cmd_info)
3067 {
3068 	const char *const type = (cmd_info->argc == 0) ? "." : cmd_info->argv[0];
3069 	const size_t len = strlen(type);
3070 
3071 	if(strcmp(type, ":") == 0 || starts_withn("cmd", type, len))
3072 		return show_cmdhistory_menu(curr_view) != 0;
3073 	else if(strcmp(type, "/") == 0 || starts_withn("search", type, len) ||
3074 			starts_withn("fsearch", type, len))
3075 		return show_fsearchhistory_menu(curr_view) != 0;
3076 	else if(strcmp(type, "?") == 0 || starts_withn("bsearch", type, len))
3077 		return show_bsearchhistory_menu(curr_view) != 0;
3078 	else if(strcmp(type, "@") == 0 || starts_withn("input", type, len))
3079 		return show_prompthistory_menu(curr_view) != 0;
3080 	else if(strcmp(type, "=") == 0 || starts_withn("filter", type, MAX(2U, len)))
3081 		return show_filterhistory_menu(curr_view) != 0;
3082 	else if(strcmp(type, ".") == 0 || starts_withn("dir", type, len))
3083 		return show_history_menu(curr_view) != 0;
3084 	else
3085 		return CMDS_ERR_TRAILING_CHARS;
3086 }
3087 
3088 /* Goes forward though directory history. */
3089 static int
histnext_cmd(const cmd_info_t * cmd_info)3090 histnext_cmd(const cmd_info_t *cmd_info)
3091 {
3092 	flist_hist_go_forward(curr_view);
3093 	return 0;
3094 }
3095 
3096 /* Goes backward though directory history. */
3097 static int
histprev_cmd(const cmd_info_t * cmd_info)3098 histprev_cmd(const cmd_info_t *cmd_info)
3099 {
3100 	flist_hist_go_back(curr_view);
3101 	return 0;
3102 }
3103 
3104 /* This command starts conditional block. */
3105 static int
if_cmd(const cmd_info_t * cmd_info)3106 if_cmd(const cmd_info_t *cmd_info)
3107 {
3108 	const int x = eval_if_condition(cmd_info);
3109 	if(x < 0)
3110 	{
3111 		return CMDS_ERR_CUSTOM;
3112 	}
3113 
3114 	cmds_scoped_if(x);
3115 	return 0;
3116 }
3117 
3118 /* Evaluates condition for if-endif statement.  Returns negative number on
3119  * error, zero for expression that's evaluated to false and positive number for
3120  * true expressions. */
3121 static int
eval_if_condition(const cmd_info_t * cmd_info)3122 eval_if_condition(const cmd_info_t *cmd_info)
3123 {
3124 	var_t condition;
3125 	int result;
3126 
3127 	vle_tb_clear(vle_err);
3128 	if(parse(cmd_info->args, 1, &condition) != PE_NO_ERROR)
3129 	{
3130 		vle_tb_append_linef(vle_err, "%s: %s", "Invalid expression",
3131 				cmd_info->args);
3132 		ui_sb_err(vle_tb_get_data(vle_err));
3133 		return -1;
3134 	}
3135 
3136 	result = var_to_bool(condition);
3137 	var_free(condition);
3138 	return result;
3139 }
3140 
3141 static int
invert_cmd(const cmd_info_t * cmd_info)3142 invert_cmd(const cmd_info_t *cmd_info)
3143 {
3144 	const char *const type_str = (cmd_info->argc == 0) ? "f" : cmd_info->argv[0];
3145 	const char state_type = type_str[0];
3146 
3147 	if(type_str[1] != '\0' || !char_is_one_of("fso", type_str[0]))
3148 	{
3149 		return CMDS_ERR_INVALID_ARG;
3150 	}
3151 
3152 	if(cmd_info->qmark)
3153 	{
3154 		print_inversion_state(state_type);
3155 		return 1;
3156 	}
3157 	else
3158 	{
3159 		invert_state(state_type);
3160 		return 0;
3161 	}
3162 }
3163 
3164 /* Prints inversion state of the feature specified by the state_type
3165  * argument. */
3166 static void
print_inversion_state(char state_type)3167 print_inversion_state(char state_type)
3168 {
3169 	if(state_type == 'f')
3170 	{
3171 		ui_sb_msgf("Filter is %sinverted", curr_view->invert ? "" : "not ");
3172 	}
3173 	else if(state_type == 's')
3174 	{
3175 		ui_sb_msg("Selection does not have inversion state");
3176 	}
3177 	else if(state_type == 'o')
3178 	{
3179 		ui_sb_msgf("Primary key is sorted in %s order",
3180 				(curr_view->sort[0] > 0) ? "ascending" : "descending");
3181 	}
3182 	else
3183 	{
3184 		assert(0 && "Unexpected state type.");
3185 	}
3186 }
3187 
3188 /* Inverts state of the feature specified by the state_type argument. */
3189 static void
invert_state(char state_type)3190 invert_state(char state_type)
3191 {
3192 	if(state_type == 'f')
3193 	{
3194 		filters_invert(curr_view);
3195 	}
3196 	else if(state_type == 's')
3197 	{
3198 		flist_sel_invert(curr_view);
3199 		redraw_view(curr_view);
3200 		cmds_preserve_selection();
3201 	}
3202 	else if(state_type == 'o')
3203 	{
3204 		invert_sorting_order(curr_view);
3205 		resort_dir_list(1, curr_view);
3206 		redraw_view(curr_view);
3207 	}
3208 	else
3209 	{
3210 		assert(0 && "Unexpected state type.");
3211 	}
3212 }
3213 
3214 static int
jobs_cmd(const cmd_info_t * cmd_info)3215 jobs_cmd(const cmd_info_t *cmd_info)
3216 {
3217 	return show_jobs_menu(curr_view) != 0;
3218 }
3219 
3220 static int
let_cmd(const cmd_info_t * cmd_info)3221 let_cmd(const cmd_info_t *cmd_info)
3222 {
3223 	vle_tb_clear(vle_err);
3224 	if(let_variables(cmd_info->args) != 0)
3225 	{
3226 		ui_sb_err(vle_tb_get_data(vle_err));
3227 		return 1;
3228 	}
3229 	else if(*vle_tb_get_data(vle_err) != '\0')
3230 	{
3231 		ui_sb_msg(vle_tb_get_data(vle_err));
3232 	}
3233 	update_path_env(0);
3234 	return 0;
3235 }
3236 
3237 static int
locate_cmd(const cmd_info_t * cmd_info)3238 locate_cmd(const cmd_info_t *cmd_info)
3239 {
3240 	static char *last_args;
3241 	if(cmd_info->argc > 0)
3242 	{
3243 		(void)replace_string(&last_args, cmd_info->args);
3244 	}
3245 	else if(last_args == NULL)
3246 	{
3247 		ui_sb_err("Nothing to repeat");
3248 		return 1;
3249 	}
3250 	return show_locate_menu(curr_view, last_args) != 0;
3251 }
3252 
3253 /* Lists active windows of terminal multiplexer in use, if any. */
3254 static int
ls_cmd(const cmd_info_t * cmd_info)3255 ls_cmd(const cmd_info_t *cmd_info)
3256 {
3257 	switch(curr_stats.term_multiplexer)
3258 	{
3259 		case TM_NONE:
3260 			ui_sb_msg("No terminal multiplexer is in use");
3261 			return 1;
3262 		case TM_SCREEN:
3263 			(void)vifm_system("screen -X eval windowlist", SHELL_BY_APP);
3264 			return 0;
3265 		case TM_TMUX:
3266 			if(vifm_system("tmux choose-window", SHELL_BY_APP) != EXIT_SUCCESS)
3267 			{
3268 				/* Refresh all windows as failed command outputs message, which can't be
3269 				 * suppressed. */
3270 				update_all_windows();
3271 				/* Fall back to worse way of doing the same for tmux versions < 1.8. */
3272 				(void)vifm_system("tmux command-prompt choose-window", SHELL_BY_APP);
3273 			}
3274 			return 0;
3275 
3276 		default:
3277 			assert(0 && "Unknown active terminal multiplexer value");
3278 			ui_sb_msg("Unknown terminal multiplexer is in use");
3279 			return 1;
3280 	}
3281 }
3282 
3283 /* Lists files in trash. */
3284 static int
lstrash_cmd(const cmd_info_t * cmd_info)3285 lstrash_cmd(const cmd_info_t *cmd_info)
3286 {
3287 	return show_trash_menu(curr_view) != 0;
3288 }
3289 
3290 static int
map_cmd(const cmd_info_t * cmd_info)3291 map_cmd(const cmd_info_t *cmd_info)
3292 {
3293 	return map_or_remap(cmd_info, 0);
3294 }
3295 
3296 static int
mark_cmd(const cmd_info_t * cmd_info)3297 mark_cmd(const cmd_info_t *cmd_info)
3298 {
3299 	int result;
3300 	char *expanded_path;
3301 	const char *file;
3302 	char mark = cmd_info->argv[0][0];
3303 
3304 	if(cmd_info->argv[0][1] != '\0')
3305 		return CMDS_ERR_TRAILING_CHARS;
3306 
3307 	if(cmd_info->qmark)
3308 	{
3309 		if(!marks_is_empty(curr_view, mark))
3310 		{
3311 			ui_sb_errf("Mark isn't empty: %c", mark);
3312 			return 1;
3313 		}
3314 	}
3315 
3316 	if(cmd_info->argc == 1)
3317 	{
3318 		const int pos = (cmd_info->end == NOT_DEF)
3319 		              ? curr_view->list_pos
3320 		              : cmd_info->end;
3321 		const dir_entry_t *const entry = &curr_view->dir_entry[pos];
3322 		return marks_set_user(curr_view, mark, entry->origin, entry->name);
3323 	}
3324 
3325 	expanded_path = expand_tilde(cmd_info->argv[1]);
3326 	if(!is_path_absolute(expanded_path))
3327 	{
3328 		free(expanded_path);
3329 		ui_sb_err("Expected full path to the directory");
3330 		return 1;
3331 	}
3332 
3333 	if(cmd_info->argc == 2)
3334 	{
3335 		if(cmd_info->end == NOT_DEF || !pane_in_dir(curr_view, expanded_path))
3336 		{
3337 			if(curr_stats.load_stage >= 3 && pane_in_dir(curr_view, expanded_path))
3338 			{
3339 				file = get_current_file_name(curr_view);
3340 			}
3341 			else
3342 			{
3343 				file = NO_MARK_FILE;
3344 			}
3345 		}
3346 		else
3347 		{
3348 			file = curr_view->dir_entry[cmd_info->end].name;
3349 		}
3350 	}
3351 	else
3352 	{
3353 		file = cmd_info->argv[2];
3354 	}
3355 	result = marks_set_user(curr_view, mark, expanded_path, file);
3356 	free(expanded_path);
3357 
3358 	return result;
3359 }
3360 
3361 /* Displays all or some of marks. */
3362 static int
marks_cmd(const cmd_info_t * cmd_info)3363 marks_cmd(const cmd_info_t *cmd_info)
3364 {
3365 	char buf[256];
3366 	int i, j;
3367 
3368 	if(cmd_info->argc == 0)
3369 	{
3370 		return show_marks_menu(curr_view, marks_all) != 0;
3371 	}
3372 
3373 	j = 0;
3374 	buf[0] = '\0';
3375 	for(i = 0; i < cmd_info->argc; i++)
3376 	{
3377 		const char *p = cmd_info->argv[i];
3378 		while(*p != '\0')
3379 		{
3380 			if(strchr(buf, *p) == NULL)
3381 			{
3382 				buf[j++] = *p;
3383 				buf[j] = '\0';
3384 			}
3385 			p++;
3386 		}
3387 	}
3388 	return show_marks_menu(curr_view, buf) != 0;
3389 }
3390 
3391 #ifndef _WIN32
3392 
3393 /* Shows a menu for managing media. */
3394 static int
media_cmd(const cmd_info_t * cmd_info)3395 media_cmd(const cmd_info_t *cmd_info)
3396 {
3397 	return (show_media_menu(curr_view) != 0);
3398 }
3399 
3400 #endif
3401 
3402 static int
messages_cmd(const cmd_info_t * cmd_info)3403 messages_cmd(const cmd_info_t *cmd_info)
3404 {
3405 	/* TODO: move this code to ui.c */
3406 	char *lines;
3407 	size_t len;
3408 	int count;
3409 	int t;
3410 
3411 	lines = NULL;
3412 	len = 0;
3413 	count = curr_stats.msg_tail - curr_stats.msg_head;
3414 	if(count < 0)
3415 		count += ARRAY_LEN(curr_stats.msgs);
3416 	t = (curr_stats.msg_head + 1) % ARRAY_LEN(curr_stats.msgs);
3417 	while(count-- > 0)
3418 	{
3419 		const char *msg = curr_stats.msgs[t];
3420 		char *new_lines = realloc(lines, len + 1 + strlen(msg) + 1);
3421 		if(new_lines != NULL)
3422 		{
3423 			lines = new_lines;
3424 			len += sprintf(lines + len, "%s%s", (len == 0) ? "": "\n", msg);
3425 			t = (t + 1) % ARRAY_LEN(curr_stats.msgs);
3426 		}
3427 	}
3428 
3429 	if(lines == NULL)
3430 		return 0;
3431 
3432 	curr_stats.save_msg_in_list = 0;
3433 	curr_stats.allow_sb_msg_truncation = 0;
3434 	ui_sb_msg(lines);
3435 	curr_stats.allow_sb_msg_truncation = 1;
3436 	curr_stats.save_msg_in_list = 1;
3437 
3438 	free(lines);
3439 	return 1;
3440 }
3441 
3442 /* :mkdir creates sub-directory.  :mkdir! creates chain of directories.  In
3443  * second case both relative and absolute paths are allowed. */
3444 static int
mkdir_cmd(const cmd_info_t * cmd_info)3445 mkdir_cmd(const cmd_info_t *cmd_info)
3446 {
3447 	const int at = get_at(curr_view, cmd_info);
3448 	return fops_mkdirs(curr_view, at, cmd_info->argv, cmd_info->argc,
3449 			cmd_info->emark) != 0;
3450 }
3451 
3452 static int
mmap_cmd(const cmd_info_t * cmd_info)3453 mmap_cmd(const cmd_info_t *cmd_info)
3454 {
3455 	return do_map(cmd_info, "Menu", MENU_MODE, 0) != 0;
3456 }
3457 
3458 static int
mnoremap_cmd(const cmd_info_t * cmd_info)3459 mnoremap_cmd(const cmd_info_t *cmd_info)
3460 {
3461 	return do_map(cmd_info, "Menu", MENU_MODE, 1) != 0;
3462 }
3463 
3464 /* Moves files. */
3465 static int
move_cmd(const cmd_info_t * cmd_info)3466 move_cmd(const cmd_info_t *cmd_info)
3467 {
3468 	return cpmv_cmd(cmd_info, 1);
3469 }
3470 
3471 /* Common part of copy and move commands interface implementation. */
3472 static int
cpmv_cmd(const cmd_info_t * cmd_info,int move)3473 cpmv_cmd(const cmd_info_t *cmd_info, int move)
3474 {
3475 	const CopyMoveLikeOp op = move ? CMLO_MOVE : CMLO_COPY;
3476 
3477 	flist_set_marking(curr_view, 0);
3478 
3479 	if(cmd_info->qmark)
3480 	{
3481 		if(cmd_info->argc > 0)
3482 		{
3483 			ui_sb_err("No arguments are allowed if you use \"?\"");
3484 			return 1;
3485 		}
3486 
3487 		if(cmd_info->bg)
3488 		{
3489 			return fops_cpmv_bg(curr_view, NULL, -1, move, cmd_info->emark) != 0;
3490 		}
3491 
3492 		return fops_cpmv(curr_view, NULL, -1, op, 0) != 0;
3493 	}
3494 
3495 	if(cmd_info->bg)
3496 	{
3497 		return fops_cpmv_bg(curr_view, cmd_info->argv, cmd_info->argc, move,
3498 				cmd_info->emark) != 0;
3499 	}
3500 
3501 	return fops_cpmv(curr_view, cmd_info->argv, cmd_info->argc, op,
3502 			cmd_info->emark) != 0;
3503 }
3504 
3505 static int
munmap_cmd(const cmd_info_t * cmd_info)3506 munmap_cmd(const cmd_info_t *cmd_info)
3507 {
3508 	return do_unmap(cmd_info->argv[0], MENU_MODE);
3509 }
3510 
3511 static int
nmap_cmd(const cmd_info_t * cmd_info)3512 nmap_cmd(const cmd_info_t *cmd_info)
3513 {
3514 	return do_map(cmd_info, "Normal", NORMAL_MODE, 0) != 0;
3515 }
3516 
3517 static int
nnoremap_cmd(const cmd_info_t * cmd_info)3518 nnoremap_cmd(const cmd_info_t *cmd_info)
3519 {
3520 	return do_map(cmd_info, "Normal", NORMAL_MODE, 1) != 0;
3521 }
3522 
3523 /* Resets file selection and search highlight. */
3524 static int
nohlsearch_cmd(const cmd_info_t * cmd_info)3525 nohlsearch_cmd(const cmd_info_t *cmd_info)
3526 {
3527 	ui_view_reset_search_highlight(curr_view);
3528 	flist_sel_stash_if_nonempty(curr_view);
3529 	return 0;
3530 }
3531 
3532 static int
noremap_cmd(const cmd_info_t * cmd_info)3533 noremap_cmd(const cmd_info_t *cmd_info)
3534 {
3535 	return map_or_remap(cmd_info, 1);
3536 }
3537 
3538 static int
map_or_remap(const cmd_info_t * cmd_info,int no_remap)3539 map_or_remap(const cmd_info_t *cmd_info, int no_remap)
3540 {
3541 	int result;
3542 	if(cmd_info->emark)
3543 	{
3544 		result = do_map(cmd_info, "", CMDLINE_MODE, no_remap);
3545 	}
3546 	else
3547 	{
3548 		result = do_map(cmd_info, "", NORMAL_MODE, no_remap);
3549 		if(result == 0)
3550 			result = do_map(cmd_info, "", VISUAL_MODE, no_remap);
3551 	}
3552 	return result != 0;
3553 }
3554 
3555 /* Executes normal mode commands. */
3556 static int
normal_cmd(const cmd_info_t * cmd_info)3557 normal_cmd(const cmd_info_t *cmd_info)
3558 {
3559 	wchar_t *const wide = to_wide(cmd_info->args);
3560 	if(wide == NULL)
3561 	{
3562 		show_error_msgf("Command Error", "Failed to convert to wide string: %s",
3563 				cmd_info->args);
3564 		return 0;
3565 	}
3566 
3567 	if(cmd_info->emark)
3568 	{
3569 		(void)vle_keys_exec_timed_out_no_remap(wide);
3570 	}
3571 	else
3572 	{
3573 		(void)vle_keys_exec_timed_out(wide);
3574 	}
3575 
3576 	/* Force leaving command-line mode if the wide contains unfinished ":". */
3577 	if(vle_mode_is(CMDLINE_MODE))
3578 	{
3579 		(void)vle_keys_exec_timed_out(WK_C_c);
3580 	}
3581 
3582 	free(wide);
3583 	cmds_preserve_selection();
3584 	return 0;
3585 }
3586 
3587 static int
nunmap_cmd(const cmd_info_t * cmd_info)3588 nunmap_cmd(const cmd_info_t *cmd_info)
3589 {
3590 	return do_unmap(cmd_info->argv[0], NORMAL_MODE);
3591 }
3592 
3593 static int
only_cmd(const cmd_info_t * cmd_info)3594 only_cmd(const cmd_info_t *cmd_info)
3595 {
3596 	only();
3597 	return 0;
3598 }
3599 
3600 static int
popd_cmd(const cmd_info_t * cmd_info)3601 popd_cmd(const cmd_info_t *cmd_info)
3602 {
3603 	if(dir_stack_pop() != 0)
3604 	{
3605 		ui_sb_msg("Directory stack empty");
3606 		return 1;
3607 	}
3608 	return 0;
3609 }
3610 
3611 static int
pushd_cmd(const cmd_info_t * cmd_info)3612 pushd_cmd(const cmd_info_t *cmd_info)
3613 {
3614 	if(cmd_info->argc == 0)
3615 	{
3616 		if(dir_stack_swap() != 0)
3617 		{
3618 			ui_sb_err("No other directories");
3619 			return 1;
3620 		}
3621 		return 0;
3622 	}
3623 	if(dir_stack_push_current() != 0)
3624 	{
3625 		show_error_msg("Memory Error", "Unable to allocate enough memory");
3626 		return 0;
3627 	}
3628 	cd_cmd(cmd_info);
3629 	return 0;
3630 }
3631 
3632 /* Puts files from the register (default register unless otherwise specified)
3633  * into current directory.  Can operate in background. */
3634 static int
put_cmd(const cmd_info_t * cmd_info)3635 put_cmd(const cmd_info_t *cmd_info)
3636 {
3637 	int reg = DEFAULT_REG_NAME;
3638 	const int at = get_at(curr_view, cmd_info);
3639 
3640 	if(cmd_info->argc == 1)
3641 	{
3642 		const int error = get_reg(cmd_info->argv[0], &reg);
3643 		if(error != 0)
3644 		{
3645 			return error;
3646 		}
3647 	}
3648 
3649 	if(cmd_info->bg)
3650 	{
3651 		return fops_put_bg(curr_view, at, reg, cmd_info->emark) != 0;
3652 	}
3653 
3654 	return fops_put(curr_view, at, reg, cmd_info->emark) != 0;
3655 }
3656 
3657 static int
pwd_cmd(const cmd_info_t * cmd_info)3658 pwd_cmd(const cmd_info_t *cmd_info)
3659 {
3660 	ui_sb_msg(flist_get_dir(curr_view));
3661 	return 1;
3662 }
3663 
3664 static int
qmap_cmd(const cmd_info_t * cmd_info)3665 qmap_cmd(const cmd_info_t *cmd_info)
3666 {
3667 	return do_map(cmd_info, "View", VIEW_MODE, 0) != 0;
3668 }
3669 
3670 static int
qnoremap_cmd(const cmd_info_t * cmd_info)3671 qnoremap_cmd(const cmd_info_t *cmd_info)
3672 {
3673 	return do_map(cmd_info, "View", VIEW_MODE, 1) != 0;
3674 }
3675 
3676 static int
qunmap_cmd(const cmd_info_t * cmd_info)3677 qunmap_cmd(const cmd_info_t *cmd_info)
3678 {
3679 	return do_unmap(cmd_info->argv[0], VIEW_MODE);
3680 }
3681 
3682 /* Immediately redraws the screen. */
3683 static int
redraw_cmd(const cmd_info_t * cmd_info)3684 redraw_cmd(const cmd_info_t *cmd_info)
3685 {
3686 	update_screen(UT_FULL);
3687 	return 0;
3688 }
3689 
3690 /* Displays menu listing contents of registers (all or just specified ones). */
3691 static int
registers_cmd(const cmd_info_t * cmd_info)3692 registers_cmd(const cmd_info_t *cmd_info)
3693 {
3694 	char reg_names[256];
3695 	int i, j;
3696 
3697 	if(cmd_info->argc == 0)
3698 	{
3699 		return show_register_menu(curr_view, valid_registers) != 0;
3700 	}
3701 
3702 	/* Format list of unique register names. */
3703 	j = 0;
3704 	reg_names[0] = '\0';
3705 	for(i = 0; i < cmd_info->argc; ++i)
3706 	{
3707 		const char *p = cmd_info->argv[i];
3708 		while(*p != '\0')
3709 		{
3710 			if(strchr(reg_names, *p) == NULL)
3711 			{
3712 				reg_names[j++] = *p;
3713 				reg_names[j] = '\0';
3714 			}
3715 			++p;
3716 		}
3717 	}
3718 
3719 	return show_register_menu(curr_view, reg_names) != 0;
3720 }
3721 
3722 /* Switches to regular view leaving custom view. */
3723 static int
regular_cmd(const cmd_info_t * cmd_info)3724 regular_cmd(const cmd_info_t *cmd_info)
3725 {
3726 	if(flist_custom_active(curr_view))
3727 	{
3728 		rn_leave(curr_view, 1);
3729 	}
3730 	return 0;
3731 }
3732 
3733 /* Renames selected files of the current view. */
3734 static int
rename_cmd(const cmd_info_t * cmd_info)3735 rename_cmd(const cmd_info_t *cmd_info)
3736 {
3737 	flist_set_marking(curr_view, 0);
3738 	return fops_rename(curr_view, cmd_info->argv, cmd_info->argc,
3739 			cmd_info->emark) != 0;
3740 }
3741 
3742 /* Resets internal state and reloads configuration files. */
3743 static int
restart_cmd(const cmd_info_t * cmd_info)3744 restart_cmd(const cmd_info_t *cmd_info)
3745 {
3746 	int full = 0;
3747 	if(cmd_info->argc == 1)
3748 	{
3749 		if(strcmp(cmd_info->argv[0], "full") != 0)
3750 		{
3751 			ui_sb_errf("Unexpected argument: %s", cmd_info->argv[0]);
3752 			return CMDS_ERR_CUSTOM;
3753 		}
3754 		full = 1;
3755 	}
3756 
3757 	(void)restart_into_session(cfg.session, full);
3758 	return 0;
3759 }
3760 
3761 static int
restore_cmd(const cmd_info_t * cmd_info)3762 restore_cmd(const cmd_info_t *cmd_info)
3763 {
3764 	flist_set_marking(curr_view, 0);
3765 	return fops_restore(curr_view) != 0;
3766 }
3767 
3768 /* Creates symbolic links with relative paths to files. */
3769 static int
rlink_cmd(const cmd_info_t * cmd_info)3770 rlink_cmd(const cmd_info_t *cmd_info)
3771 {
3772 	return link_cmd(cmd_info, 0);
3773 }
3774 
3775 /* Common part of alink and rlink commands interface implementation. */
3776 static int
link_cmd(const cmd_info_t * cmd_info,int absolute)3777 link_cmd(const cmd_info_t *cmd_info, int absolute)
3778 {
3779 	const CopyMoveLikeOp op = absolute ? CMLO_LINK_ABS : CMLO_LINK_REL;
3780 
3781 	flist_set_marking(curr_view, 0);
3782 
3783 	if(cmd_info->qmark)
3784 	{
3785 		if(cmd_info->argc > 0)
3786 		{
3787 			ui_sb_err("No arguments are allowed if you use \"?\"");
3788 			return 1;
3789 		}
3790 		return fops_cpmv(curr_view, NULL, -1, op, 0) != 0;
3791 	}
3792 
3793 	return fops_cpmv(curr_view, cmd_info->argv, cmd_info->argc, op,
3794 			cmd_info->emark) != 0;
3795 }
3796 
3797 /* Shows status of terminal multiplexers support, sets it or toggles it. */
3798 static int
screen_cmd(const cmd_info_t * cmd_info)3799 screen_cmd(const cmd_info_t *cmd_info)
3800 {
3801 	if(cmd_info->qmark)
3802 	{
3803 		if(cfg.use_term_multiplexer)
3804 		{
3805 			if(curr_stats.term_multiplexer != TM_NONE)
3806 			{
3807 				ui_sb_msgf("Integration with %s is active",
3808 						(curr_stats.term_multiplexer == TM_SCREEN) ? "GNU screen" : "tmux");
3809 			}
3810 			else
3811 			{
3812 				ui_sb_msg("Integration with terminal multiplexers is enabled but "
3813 						"inactive");
3814 			}
3815 		}
3816 		else
3817 		{
3818 			ui_sb_msg("Integration with terminal multiplexers is disabled");
3819 		}
3820 		return 1;
3821 	}
3822 
3823 	if(cmd_info->emark)
3824 	{
3825 		cfg_set_use_term_multiplexer(1);
3826 	}
3827 	else
3828 	{
3829 		cfg_set_use_term_multiplexer(!cfg.use_term_multiplexer);
3830 	}
3831 
3832 	return 0;
3833 }
3834 
3835 /* Selects files that match passed in expression or range. */
3836 static int
select_cmd(const cmd_info_t * cmd_info)3837 select_cmd(const cmd_info_t *cmd_info)
3838 {
3839 	int error;
3840 	cmds_preserve_selection();
3841 
3842 	/* If no arguments are passed, select the range. */
3843 	if(cmd_info->argc == 0)
3844 	{
3845 		/* Append to previous selection unless ! is specified. */
3846 		if(cmd_info->emark)
3847 		{
3848 			flist_sel_drop(curr_view);
3849 		}
3850 
3851 		flist_sel_by_range(curr_view, cmd_info->begin, cmd_info->end, 1);
3852 		return 0;
3853 	}
3854 
3855 	if(cmd_info->begin != NOT_DEF)
3856 	{
3857 		ui_sb_err("Either range or argument should be supplied.");
3858 		error = 1;
3859 	}
3860 	else if(cmd_info->args[0] == '!' && !char_is_one_of("/{", cmd_info->args[1]))
3861 	{
3862 		error = flist_sel_by_filter(curr_view, cmd_info->args + 1, cmd_info->emark,
3863 				1);
3864 	}
3865 	else
3866 	{
3867 		error = flist_sel_by_pattern(curr_view, cmd_info->args, cmd_info->emark, 1);
3868 	}
3869 
3870 	return (error ? CMDS_ERR_CUSTOM : 0);
3871 }
3872 
3873 /* Displays current session, detaches from a session or switches to a (possibly
3874  * new) session. */
3875 static int
session_cmd(const cmd_info_t * cmd_info)3876 session_cmd(const cmd_info_t *cmd_info)
3877 {
3878 	if(cmd_info->qmark)
3879 	{
3880 		if(sessions_active())
3881 		{
3882 			ui_sb_msgf("Active session: %s", sessions_current());
3883 		}
3884 		else
3885 		{
3886 			ui_sb_msg("No active session");
3887 		}
3888 		return 1;
3889 	}
3890 
3891 	if(cmd_info->argc == 0)
3892 	{
3893 		char *current = strdup(sessions_current());
3894 		if(sessions_stop() == 0)
3895 		{
3896 			ui_sb_msgf("Detached from session without saving: %s", current);
3897 			free(current);
3898 			return 1;
3899 		}
3900 		ui_sb_msg("No active session");
3901 		free(current);
3902 		return 1;
3903 	}
3904 
3905 	const char *session_name = cmd_info->argv[0];
3906 	if(contains_slash(session_name))
3907 	{
3908 		ui_sb_err("Session name can't include path separators");
3909 		return 1;
3910 	}
3911 
3912 	if(sessions_active())
3913 	{
3914 		if(sessions_current_is(session_name))
3915 		{
3916 			ui_sb_msgf("Already active session: %s", session_name);
3917 			return 1;
3918 		}
3919 
3920 		state_store();
3921 	}
3922 
3923 	if(sessions_create(session_name) == 0)
3924 	{
3925 		ui_sb_msgf("Switched to a new session: %s", sessions_current());
3926 		return 1;
3927 	}
3928 
3929 	if(restart_into_session(session_name, 0) != 0)
3930 	{
3931 		if(sessions_active())
3932 		{
3933 			ui_sb_errf("Session switching has failed, active session: %s",
3934 					sessions_current());
3935 		}
3936 		else
3937 		{
3938 			ui_sb_err("Session switching has failed, no active session");
3939 		}
3940 		return 1;
3941 	}
3942 
3943 	ui_sb_msgf("Loaded session: %s", sessions_current());
3944 	return 1;
3945 }
3946 
3947 /* Performs restart and optional (re)loading of a session.  Returns zero on
3948  * success, otherwise non-zero is returned. */
3949 static int
restart_into_session(const char session[],int full)3950 restart_into_session(const char session[], int full)
3951 {
3952 	instance_start_restart();
3953 
3954 	if(full || session != NULL)
3955 	{
3956 		tabs_reinit();
3957 	}
3958 
3959 	int result;
3960 	if(session == NULL)
3961 	{
3962 		state_load(!full);
3963 		result = 0;
3964 	}
3965 	else
3966 	{
3967 		result = sessions_load(session);
3968 	}
3969 
3970 	instance_finish_restart();
3971 	return result;
3972 }
3973 
3974 /* Updates/displays global and local options. */
3975 static int
set_cmd(const cmd_info_t * cmd_info)3976 set_cmd(const cmd_info_t *cmd_info)
3977 {
3978 	const int result = process_set_args(cmd_info->args, 1, 1);
3979 	return (result < 0) ? CMDS_ERR_CUSTOM : (result != 0);
3980 }
3981 
3982 /* Updates/displays only global options. */
3983 static int
setglobal_cmd(const cmd_info_t * cmd_info)3984 setglobal_cmd(const cmd_info_t *cmd_info)
3985 {
3986 	const int result = process_set_args(cmd_info->args, 1, 0);
3987 	return (result < 0) ? CMDS_ERR_CUSTOM : (result != 0);
3988 }
3989 
3990 /* Updates/displays only local options. */
3991 static int
setlocal_cmd(const cmd_info_t * cmd_info)3992 setlocal_cmd(const cmd_info_t *cmd_info)
3993 {
3994 	const int result = process_set_args(cmd_info->args, 0, 1);
3995 	return (result < 0) ? CMDS_ERR_CUSTOM : (result != 0);
3996 }
3997 
3998 static int
shell_cmd(const cmd_info_t * cmd_info)3999 shell_cmd(const cmd_info_t *cmd_info)
4000 {
4001 	rn_shell(NULL, PAUSE_NEVER, cmd_info->emark ? 0 : 1, SHELL_BY_APP);
4002 	return 0;
4003 }
4004 
4005 /* Navigates to [count]th next sibling directory. */
4006 static int
siblnext_cmd(const cmd_info_t * cmd_info)4007 siblnext_cmd(const cmd_info_t *cmd_info)
4008 {
4009 	const int count = (cmd_info->count == NOT_DEF ? 1 : cmd_info->count);
4010 	return (go_to_sibling_dir(curr_view, count, cmd_info->emark) != 0);
4011 }
4012 
4013 /* Navigates to [count]th previous sibling directory. */
4014 static int
siblprev_cmd(const cmd_info_t * cmd_info)4015 siblprev_cmd(const cmd_info_t *cmd_info)
4016 {
4017 	const int count = (cmd_info->count == NOT_DEF ? 1 : cmd_info->count);
4018 	return (go_to_sibling_dir(curr_view, -count, cmd_info->emark) != 0);
4019 }
4020 
4021 static int
sort_cmd(const cmd_info_t * cmd_info)4022 sort_cmd(const cmd_info_t *cmd_info)
4023 {
4024 	enter_sort_mode(curr_view);
4025 	cmds_preserve_selection();
4026 	return 0;
4027 }
4028 
4029 static int
source_cmd(const cmd_info_t * cmd_info)4030 source_cmd(const cmd_info_t *cmd_info)
4031 {
4032 	int ret = 0;
4033 	char *path = expand_tilde(cmd_info->argv[0]);
4034 	if(!path_exists(path, DEREF))
4035 	{
4036 		ui_sb_errf("File doesn't exist: %s", cmd_info->argv[0]);
4037 		ret = 1;
4038 	}
4039 	if(os_access(path, R_OK) != 0)
4040 	{
4041 		ui_sb_errf("File isn't readable: %s", cmd_info->argv[0]);
4042 		ret = 1;
4043 	}
4044 	if(cfg_source_file(path) != 0)
4045 	{
4046 		ui_sb_errf("Error sourcing file: %s", cmd_info->argv[0]);
4047 		ret = 1;
4048 	}
4049 	free(path);
4050 	return ret;
4051 }
4052 
4053 static int
split_cmd(const cmd_info_t * cmd_info)4054 split_cmd(const cmd_info_t *cmd_info)
4055 {
4056 	return do_split(cmd_info, HSPLIT);
4057 }
4058 
4059 /* :s[ubstitute]/[pat]/[subs]/[flags].  Replaces matches of regular expression
4060  * in names of files.  Empty pattern is replaced with the latest search pattern.
4061  * New pattern is saved in search pattern history.  Empty substitution part is
4062  * replaced with the previously used one. */
4063 static int
substitute_cmd(const cmd_info_t * cmd_info)4064 substitute_cmd(const cmd_info_t *cmd_info)
4065 {
4066 	/* TODO: Vim preserves these two values across sessions. */
4067 	static char *last_pattern;
4068 	static char *last_sub;
4069 
4070 	int ic = 0;
4071 	int glob = cfg.gdefault;
4072 
4073 	if(cmd_info->argc == 3)
4074 	{
4075 		/* TODO: maybe extract into a function to generalize code with
4076 		 * parse_case_flag(). */
4077 		const char *flags = cmd_info->argv[2];
4078 		while(*flags != '\0')
4079 		{
4080 			switch(*flags)
4081 			{
4082 				case 'i': ic =  1; break;
4083 				case 'I': ic = -1; break;
4084 
4085 				case 'g': glob = !glob; break;
4086 
4087 				default:
4088 					return CMDS_ERR_TRAILING_CHARS;
4089 			};
4090 
4091 			++flags;
4092 		}
4093 	}
4094 
4095 	if(cmd_info->argc >= 1)
4096 	{
4097 		if(cmd_info->argv[0][0] == '\0')
4098 		{
4099 			(void)replace_string(&last_pattern, hists_search_last());
4100 		}
4101 		else
4102 		{
4103 			(void)replace_string(&last_pattern, cmd_info->argv[0]);
4104 			hists_search_save(last_pattern);
4105 		}
4106 	}
4107 
4108 	if(cmd_info->argc >= 2)
4109 	{
4110 		(void)replace_string(&last_sub, cmd_info->argv[1]);
4111 	}
4112 	else if(cmd_info->argc == 1)
4113 	{
4114 		(void)replace_string(&last_sub, "");
4115 	}
4116 
4117 	if(is_null_or_empty(last_pattern))
4118 	{
4119 		ui_sb_err("No previous pattern");
4120 		return 1;
4121 	}
4122 
4123 	flist_set_marking(curr_view, 0);
4124 	return fops_subst(curr_view, last_pattern, last_sub, ic, glob) != 0;
4125 }
4126 
4127 /* Synchronizes path/cursor position of the other pane with corresponding
4128  * properties of the current one, possibly including some relative path
4129  * changes. */
4130 static int
sync_cmd(const cmd_info_t * cmd_info)4131 sync_cmd(const cmd_info_t *cmd_info)
4132 {
4133 	if(cmd_info->emark && cmd_info->argc != 0)
4134 	{
4135 		return sync_selectively(cmd_info);
4136 	}
4137 
4138 	if(cmd_info->argc > 1)
4139 	{
4140 		return CMDS_ERR_TRAILING_CHARS;
4141 	}
4142 
4143 	if(cmd_info->argc > 0)
4144 	{
4145 		char dst_path[PATH_MAX + 1];
4146 		to_canonic_path(cmd_info->argv[0], flist_get_dir(curr_view), dst_path,
4147 				sizeof(dst_path));
4148 		sync_location(dst_path, 0, cmd_info->emark, 0, 0);
4149 	}
4150 	else
4151 	{
4152 		sync_location(flist_get_dir(curr_view), 0, cmd_info->emark, 0, 0);
4153 	}
4154 
4155 	return 0;
4156 }
4157 
4158 /* Mirrors requested properties of current view with the other one.  Returns
4159  * value to be returned by command handler. */
4160 static int
sync_selectively(const cmd_info_t * cmd_info)4161 sync_selectively(const cmd_info_t *cmd_info)
4162 {
4163 	int location = 0, cursor_pos = 0, local_options = 0, filters = 0,
4164 			filelist = 0, tree = 0;
4165 	if(parse_sync_properties(cmd_info, &location, &cursor_pos, &local_options,
4166 				&filters, &filelist, &tree) != 0)
4167 	{
4168 		return 1;
4169 	}
4170 
4171 	if(!cv_tree(curr_view->custom.type))
4172 	{
4173 		tree = 0;
4174 	}
4175 
4176 	if(local_options)
4177 	{
4178 		sync_local_opts(location);
4179 	}
4180 	if(location)
4181 	{
4182 		filelist = filelist && flist_custom_active(curr_view);
4183 		sync_location(flist_get_dir(curr_view), filelist, cursor_pos, filters,
4184 				tree);
4185 	}
4186 	if(filters)
4187 	{
4188 		sync_filters();
4189 	}
4190 
4191 	return 0;
4192 }
4193 
4194 /* Parses selective view synchronization properties.  Default values for
4195  * arguments should be set before the call.  Returns zero on success, otherwise
4196  * non-zero is returned and error message is displayed on the status bar. */
4197 static int
parse_sync_properties(const cmd_info_t * cmd_info,int * location,int * cursor_pos,int * local_options,int * filters,int * filelist,int * tree)4198 parse_sync_properties(const cmd_info_t *cmd_info, int *location,
4199 		int *cursor_pos, int *local_options, int *filters, int *filelist, int *tree)
4200 {
4201 	int i;
4202 	for(i = 0; i < cmd_info->argc; ++i)
4203 	{
4204 		const char *const property = cmd_info->argv[i];
4205 		if(strcmp(property, "location") == 0)
4206 		{
4207 			*location = 1;
4208 		}
4209 		else if(strcmp(property, "cursorpos") == 0)
4210 		{
4211 			*cursor_pos = 1;
4212 		}
4213 		else if(strcmp(property, "localopts") == 0)
4214 		{
4215 			*local_options = 1;
4216 		}
4217 		else if(strcmp(property, "filters") == 0)
4218 		{
4219 			*filters = 1;
4220 		}
4221 		else if(strcmp(property, "filelist") == 0)
4222 		{
4223 			*location = 1;
4224 			*filelist = 1;
4225 		}
4226 		else if(strcmp(property, "tree") == 0)
4227 		{
4228 			*location = 1;
4229 			*tree = 1;
4230 		}
4231 		else if(strcmp(property, "all") == 0)
4232 		{
4233 			*location = 1;
4234 			*cursor_pos = 1;
4235 			*local_options = 1;
4236 			*filters = 1;
4237 			*filelist = 1;
4238 			*tree = 1;
4239 		}
4240 		else
4241 		{
4242 			ui_sb_errf("Unknown selective sync property: %s", property);
4243 			return 1;
4244 		}
4245 	}
4246 
4247 	return 0;
4248 }
4249 
4250 /* Mirrors location (directory and maybe cursor position plus local filter) of
4251  * the current view to the other one. */
4252 static void
sync_location(const char path[],int cv,int sync_cursor_pos,int sync_filters,int tree)4253 sync_location(const char path[], int cv, int sync_cursor_pos, int sync_filters,
4254 		int tree)
4255 {
4256 	if(!cd_is_possible(path) || change_directory(other_view, path) < 0)
4257 	{
4258 		return;
4259 	}
4260 
4261 	if(tree)
4262 	{
4263 		/* Normally changing location resets local filter.  Prevent this by
4264 		 * synchronizing it here (after directory changing, but before loading list
4265 		 * of files, hence no extra work). */
4266 		if(sync_filters)
4267 		{
4268 			local_filter_apply(other_view, curr_view->local_filter.filter.raw);
4269 		}
4270 
4271 		(void)flist_clone_tree(other_view, curr_view);
4272 	}
4273 	else if(cv)
4274 	{
4275 		flist_custom_clone(other_view, curr_view, 0);
4276 		if(sync_filters)
4277 		{
4278 			local_filter_apply(other_view, curr_view->local_filter.filter.raw);
4279 			replace_dir_entries(other_view, &other_view->custom.entries,
4280 					&other_view->custom.entry_count, other_view->dir_entry,
4281 					other_view->list_rows);
4282 		}
4283 	}
4284 	else
4285 	{
4286 		/* Normally changing location resets local filter.  Prevent this by
4287 		 * synchronizing it here (after directory changing, but before loading list
4288 		 * of files, hence no extra work). */
4289 		if(sync_filters)
4290 		{
4291 			local_filter_apply(other_view, curr_view->local_filter.filter.raw);
4292 		}
4293 
4294 		(void)populate_dir_list(other_view, 0);
4295 	}
4296 
4297 	if(sync_cursor_pos)
4298 	{
4299 		if(flist_custom_active(curr_view) && !flist_custom_active(other_view))
4300 		{
4301 			flist_hist_lookup(other_view, curr_view);
4302 		}
4303 		else
4304 		{
4305 			char curr_file_path[PATH_MAX + 1];
4306 
4307 			const int offset = (curr_view->list_pos - curr_view->top_line);
4308 			const int shift = (offset*other_view->window_rows)/curr_view->window_rows;
4309 
4310 			get_current_full_path(curr_view, sizeof(curr_file_path), curr_file_path);
4311 			break_atr(curr_file_path, '/');
4312 			navigate_to_file(other_view, curr_file_path,
4313 					get_current_file_name(curr_view), 1);
4314 
4315 			other_view->top_line = MAX(0, curr_view->list_pos - shift);
4316 			(void)consider_scroll_offset(other_view);
4317 		}
4318 
4319 		flist_hist_save(other_view);
4320 	}
4321 
4322 	ui_view_schedule_redraw(other_view);
4323 }
4324 
4325 /* Sets local options of the other view to be equal to options of the current
4326  * one. */
4327 static void
sync_local_opts(int defer_slow)4328 sync_local_opts(int defer_slow)
4329 {
4330 	clone_local_options(curr_view, other_view, defer_slow);
4331 	ui_view_schedule_redraw(other_view);
4332 }
4333 
4334 /* Sets filters of the other view to be equal to options of the current one. */
4335 static void
sync_filters(void)4336 sync_filters(void)
4337 {
4338 	other_view->prev_invert = curr_view->prev_invert;
4339 	other_view->invert = curr_view->invert;
4340 
4341 	(void)filter_assign(&other_view->local_filter.filter,
4342 			&curr_view->local_filter.filter);
4343 	matcher_free(other_view->manual_filter);
4344 	other_view->manual_filter = matcher_clone(curr_view->manual_filter);
4345 	(void)filter_assign(&other_view->auto_filter, &curr_view->auto_filter);
4346 	ui_view_schedule_reload(other_view);
4347 }
4348 
4349 /* Closes current tab unless it's the last one. */
4350 static int
tabclose_cmd(const cmd_info_t * cmd_info)4351 tabclose_cmd(const cmd_info_t *cmd_info)
4352 {
4353 	tabs_close();
4354 	return 0;
4355 }
4356 
4357 /* Moves current tab to a different position. */
4358 static int
tabmove_cmd(const cmd_info_t * cmd_info)4359 tabmove_cmd(const cmd_info_t *cmd_info)
4360 {
4361 	int where_to;
4362 
4363 	if(cmd_info->argc == 0 || strcmp(cmd_info->argv[0], "$") == 0)
4364 	{
4365 		where_to = tabs_count(curr_view);
4366 	}
4367 	else if(!read_int(cmd_info->argv[0], &where_to))
4368 	{
4369 		return CMDS_ERR_INVALID_ARG;
4370 	}
4371 
4372 	tabs_move(curr_view, where_to);
4373 	ui_views_update_titles();
4374 	return 0;
4375 }
4376 
4377 /* Sets, changes or resets name of current tab. */
4378 static int
tabname_cmd(const cmd_info_t * cmd_info)4379 tabname_cmd(const cmd_info_t *cmd_info)
4380 {
4381 	tabs_rename(curr_view, cmd_info->argc == 0 ? NULL : cmd_info->argv[0]);
4382 	ui_views_update_titles();
4383 	return 0;
4384 }
4385 
4386 /* Creates a new tab.  Takes optional path for the new tab. */
4387 static int
tabnew_cmd(const cmd_info_t * cmd_info)4388 tabnew_cmd(const cmd_info_t *cmd_info)
4389 {
4390 	if(cfg.pane_tabs && curr_view->custom.type == CV_DIFF)
4391 	{
4392 		ui_sb_err("Switching tab of single pane would drop comparison");
4393 		return 1;
4394 	}
4395 
4396 	const char *path = NULL;
4397 	char canonic_dir[PATH_MAX + 1];
4398 
4399 	if(cmd_info->argc > 0)
4400 	{
4401 		char dir[PATH_MAX + 1];
4402 
4403 		int updir;
4404 		flist_pick_cd_path(curr_view, flist_get_dir(curr_view), cmd_info->argv[0],
4405 				&updir, dir, sizeof(dir));
4406 		if(updir)
4407 		{
4408 			copy_str(dir, sizeof(dir), "..");
4409 		}
4410 
4411 		to_canonic_path(dir, flist_get_dir(curr_view), canonic_dir,
4412 				sizeof(canonic_dir));
4413 
4414 		if(!cd_is_possible(canonic_dir))
4415 		{
4416 			return 0;
4417 		}
4418 
4419 		path = canonic_dir;
4420 	}
4421 
4422 	if(tabs_new(NULL, path) != 0)
4423 	{
4424 		ui_sb_err("Failed to open a new tab");
4425 		return 1;
4426 	}
4427 	return 0;
4428 }
4429 
4430 /* Switches either to the next tab or to tab specified by its number in the only
4431  * optional parameter. */
4432 static int
tabnext_cmd(const cmd_info_t * cmd_info)4433 tabnext_cmd(const cmd_info_t *cmd_info)
4434 {
4435 	if(cmd_info->argc == 0)
4436 	{
4437 		tabs_next(1);
4438 		return 0;
4439 	}
4440 
4441 	int n;
4442 	if(!read_int(cmd_info->argv[0], &n) || n <= 0 || n > tabs_count(curr_view))
4443 	{
4444 		return CMDS_ERR_INVALID_ARG;
4445 	}
4446 
4447 	tabs_goto(n - 1);
4448 	return 0;
4449 }
4450 
4451 /* Closes all tabs but the current one. */
4452 static int
tabonly_cmd(const cmd_info_t * cmd_info)4453 tabonly_cmd(const cmd_info_t *cmd_info)
4454 {
4455 	tabs_only(curr_view);
4456 	stats_redraw_later();
4457 	return 0;
4458 }
4459 
4460 /* Switches either to the previous tab or to n-th previous tab, where n is
4461  * specified by the only optional parameter. */
4462 static int
tabprevious_cmd(const cmd_info_t * cmd_info)4463 tabprevious_cmd(const cmd_info_t *cmd_info)
4464 {
4465 	if(cmd_info->argc == 0)
4466 	{
4467 		tabs_previous(1);
4468 		return 0;
4469 	}
4470 
4471 	int n;
4472 	if(!read_int(cmd_info->argv[0], &n) || n <= 0)
4473 	{
4474 		return CMDS_ERR_INVALID_ARG;
4475 	}
4476 
4477 	tabs_previous(n);
4478 	return 0;
4479 }
4480 
4481 /* Creates files. */
4482 static int
touch_cmd(const cmd_info_t * cmd_info)4483 touch_cmd(const cmd_info_t *cmd_info)
4484 {
4485 	const int at = get_at(curr_view, cmd_info);
4486 	return fops_mkfiles(curr_view, at, cmd_info->argv, cmd_info->argc) != 0;
4487 }
4488 
4489 /* Gets destination position based range.  Returns the position. */
4490 static int
get_at(const view_t * view,const cmd_info_t * cmd_info)4491 get_at(const view_t *view, const cmd_info_t *cmd_info)
4492 {
4493 	return (cmd_info->end == NOT_DEF) ? view->list_pos : cmd_info->end;
4494 }
4495 
4496 /* Replaces letters in names of files according to character mapping. */
4497 static int
tr_cmd(const cmd_info_t * cmd_info)4498 tr_cmd(const cmd_info_t *cmd_info)
4499 {
4500 	char buf[strlen(cmd_info->argv[0]) + 1];
4501 	size_t pl, sl;
4502 
4503 	if(cmd_info->argv[0][0] == '\0' || cmd_info->argv[1][0] == '\0')
4504 	{
4505 		ui_sb_err("Empty argument");
4506 		return 1;
4507 	}
4508 
4509 	pl = strlen(cmd_info->argv[0]);
4510 	sl = strlen(cmd_info->argv[1]);
4511 	strcpy(buf, cmd_info->argv[1]);
4512 	if(pl < sl)
4513 	{
4514 		ui_sb_err("Second argument cannot be longer");
4515 		return 1;
4516 	}
4517 	else if(pl > sl)
4518 	{
4519 		while(sl < pl)
4520 		{
4521 			buf[sl] = buf[sl - 1];
4522 			++sl;
4523 		}
4524 		buf[sl] = '\0';
4525 	}
4526 
4527 	flist_set_marking(curr_view, 0);
4528 	return fops_tr(curr_view, cmd_info->argv[0], buf) != 0;
4529 }
4530 
4531 /* Lists all valid non-empty trash directories in a menu with optional size of
4532  * each one. */
4533 static int
trashes_cmd(const cmd_info_t * cmd_info)4534 trashes_cmd(const cmd_info_t *cmd_info)
4535 {
4536 	return show_trashes_menu(curr_view, cmd_info->qmark) != 0;
4537 }
4538 
4539 /* Convert view into a tree with root at current location. */
4540 static int
tree_cmd(const cmd_info_t * cmd_info)4541 tree_cmd(const cmd_info_t *cmd_info)
4542 {
4543 	int in_tree = flist_custom_active(curr_view)
4544 	           && cv_tree(curr_view->custom.type);
4545 
4546 	if(cmd_info->emark && in_tree)
4547 	{
4548 		rn_leave(curr_view, 1);
4549 	}
4550 	else
4551 	{
4552 		(void)flist_load_tree(curr_view, flist_get_dir(curr_view));
4553 	}
4554 	return 0;
4555 }
4556 
4557 static int
undolist_cmd(const cmd_info_t * cmd_info)4558 undolist_cmd(const cmd_info_t *cmd_info)
4559 {
4560 	return show_undolist_menu(curr_view, cmd_info->emark) != 0;
4561 }
4562 
4563 static int
unlet_cmd(const cmd_info_t * cmd_info)4564 unlet_cmd(const cmd_info_t *cmd_info)
4565 {
4566 	vle_tb_clear(vle_err);
4567 	if(unlet_variables(cmd_info->args) != 0 && !cmd_info->emark)
4568 	{
4569 		ui_sb_err(vle_tb_get_data(vle_err));
4570 		return 1;
4571 	}
4572 	return 0;
4573 }
4574 
4575 /* Unmaps keys in normal and visual or in command-line mode. */
4576 static int
unmap_cmd(const cmd_info_t * cmd_info)4577 unmap_cmd(const cmd_info_t *cmd_info)
4578 {
4579 	wchar_t *subst = substitute_specs(cmd_info->argv[0]);
4580 	if(subst == NULL)
4581 	{
4582 		show_error_msgf("Unmapping Error", "Failed to convert to wide string: %s",
4583 				cmd_info->argv[0]);
4584 		return 0;
4585 	}
4586 
4587 	int result;
4588 	if(cmd_info->emark)
4589 	{
4590 		result = (vle_keys_user_remove(subst, CMDLINE_MODE) != 0);
4591 	}
4592 	else if(!vle_keys_user_exists(subst, NORMAL_MODE))
4593 	{
4594 		ui_sb_err("No such mapping in normal mode");
4595 		result = -1;
4596 	}
4597 	else if(!vle_keys_user_exists(subst, VISUAL_MODE))
4598 	{
4599 		ui_sb_err("No such mapping in visual mode");
4600 		result = -2;
4601 	}
4602 	else
4603 	{
4604 		result = (vle_keys_user_remove(subst, NORMAL_MODE) != 0);
4605 		result += (vle_keys_user_remove(subst, VISUAL_MODE) != 0);
4606 	}
4607 	free(subst);
4608 
4609 	if(result > 0)
4610 	{
4611 		ui_sb_err("Error while unmapping keys");
4612 	}
4613 	return result != 0;
4614 }
4615 
4616 /* Unselects files that match passed in expression or range. */
4617 static int
unselect_cmd(const cmd_info_t * cmd_info)4618 unselect_cmd(const cmd_info_t *cmd_info)
4619 {
4620 	int error;
4621 	cmds_preserve_selection();
4622 
4623 	/* If no arguments are passed, unselect the range. */
4624 	if(cmd_info->argc == 0)
4625 	{
4626 		flist_sel_by_range(curr_view, cmd_info->begin, cmd_info->end, 0);
4627 		return 0;
4628 	}
4629 
4630 	if(cmd_info->begin != NOT_DEF)
4631 	{
4632 		ui_sb_err("Either range or argument should be supplied.");
4633 		error = 1;
4634 	}
4635 	else if(cmd_info->args[0] == '!' && !char_is_one_of("/{", cmd_info->args[1]))
4636 	{
4637 		error = flist_sel_by_filter(curr_view, cmd_info->args + 1, cmd_info->emark,
4638 				0);
4639 	}
4640 	else
4641 	{
4642 		error = flist_sel_by_pattern(curr_view, cmd_info->args, 0, 0);
4643 	}
4644 
4645 	return (error ? CMDS_ERR_CUSTOM : 0);
4646 }
4647 
4648 static int
view_cmd(const cmd_info_t * cmd_info)4649 view_cmd(const cmd_info_t *cmd_info)
4650 {
4651 	if((!curr_stats.preview.on || cmd_info->emark) && !qv_can_show())
4652 	{
4653 		return 1;
4654 	}
4655 	if(curr_stats.preview.on && cmd_info->emark)
4656 	{
4657 		return 0;
4658 	}
4659 	qv_toggle();
4660 	return 0;
4661 }
4662 
4663 static int
vifm_cmd(const cmd_info_t * cmd_info)4664 vifm_cmd(const cmd_info_t *cmd_info)
4665 {
4666 	return show_vifm_menu(curr_view) != 0;
4667 }
4668 
4669 static int
vmap_cmd(const cmd_info_t * cmd_info)4670 vmap_cmd(const cmd_info_t *cmd_info)
4671 {
4672 	return do_map(cmd_info, "Visual", VISUAL_MODE, 0) != 0;
4673 }
4674 
4675 static int
vnoremap_cmd(const cmd_info_t * cmd_info)4676 vnoremap_cmd(const cmd_info_t *cmd_info)
4677 {
4678 	return do_map(cmd_info, "Visual", VISUAL_MODE, 1) != 0;
4679 }
4680 
4681 /* Maps keys in the specified mode. */
4682 static int
do_map(const cmd_info_t * cmd_info,const char map_type[],int mode,int no_remap)4683 do_map(const cmd_info_t *cmd_info, const char map_type[], int mode,
4684 		int no_remap)
4685 {
4686 	wchar_t *keys, *mapping;
4687 	char *raw_rhs, *rhs;
4688 	char t;
4689 
4690 	if(cmd_info->argc <= 1)
4691 	{
4692 		keys = substitute_specs(cmd_info->args);
4693 		if(keys != NULL)
4694 		{
4695 			int save_msg = show_map_menu(curr_view, map_type, mode, keys);
4696 			free(keys);
4697 			return save_msg != 0;
4698 		}
4699 		show_error_msgf("Mapping Error", "Failed to convert to wide string: %s",
4700 				cmd_info->args);
4701 		return 0;
4702 	}
4703 
4704 	const char *args = cmd_info->args;
4705 	const int flags = (no_remap ? KEYS_FLAG_NOREMAP : KEYS_FLAG_NONE)
4706 	                | parse_map_args(&args);
4707 
4708 	raw_rhs = vle_cmds_past_arg(args);
4709 	t = *raw_rhs;
4710 	*raw_rhs = '\0';
4711 
4712 	int error = 0;
4713 	rhs = vle_cmds_at_arg(raw_rhs + 1);
4714 	keys = substitute_specs(args);
4715 	mapping = substitute_specs(rhs);
4716 	if(keys != NULL && mapping != NULL)
4717 	{
4718 		error = vle_keys_user_add(keys, mapping, mode, flags);
4719 	}
4720 	else
4721 	{
4722 		show_error_msgf("Mapping Error", "Failed to convert to wide string: %s",
4723 				cmd_info->args);
4724 	}
4725 	free(mapping);
4726 	free(keys);
4727 
4728 	*raw_rhs = t;
4729 
4730 	if(error)
4731 		show_error_msg("Mapping Error", "Unable to allocate enough memory");
4732 
4733 	return 0;
4734 }
4735 
4736 /* Parses <*> :*map arguments removing them from the line.  Returns flags
4737  * collected. */
4738 static int
parse_map_args(const char ** args)4739 parse_map_args(const char **args)
4740 {
4741 	int flags = 0;
4742 	do
4743 	{
4744 		if(skip_prefix(args, "<silent>"))
4745 		{
4746 			flags |= KEYS_FLAG_SILENT;
4747 		}
4748 		else if(skip_prefix(args, "<wait>"))
4749 		{
4750 			flags |= KEYS_FLAG_WAIT;
4751 		}
4752 		else
4753 		{
4754 			break;
4755 		}
4756 		*args = skip_whitespace(*args);
4757 	}
4758 	while(1);
4759 	return flags;
4760 }
4761 
4762 #ifdef _WIN32
4763 static int
volumes_cmd(const cmd_info_t * cmd_info)4764 volumes_cmd(const cmd_info_t *cmd_info)
4765 {
4766 	return show_volumes_menu(curr_view) != 0;
4767 }
4768 #endif
4769 
4770 static int
vsplit_cmd(const cmd_info_t * cmd_info)4771 vsplit_cmd(const cmd_info_t *cmd_info)
4772 {
4773 	return do_split(cmd_info, VSPLIT);
4774 }
4775 
4776 static int
do_split(const cmd_info_t * cmd_info,SPLIT orientation)4777 do_split(const cmd_info_t *cmd_info, SPLIT orientation)
4778 {
4779 	if(cmd_info->emark && cmd_info->argc != 0)
4780 	{
4781 		ui_sb_err("No arguments are allowed if you use \"!\"");
4782 		return 1;
4783 	}
4784 
4785 	if(cmd_info->emark)
4786 	{
4787 		if(curr_stats.number_of_windows == 1)
4788 			split_view(orientation);
4789 		else
4790 			only();
4791 	}
4792 	else
4793 	{
4794 		if(cmd_info->argc == 1)
4795 			cd(other_view, flist_get_dir(curr_view), cmd_info->argv[0]);
4796 		split_view(orientation);
4797 	}
4798 	return 0;
4799 }
4800 
4801 static int
vunmap_cmd(const cmd_info_t * cmd_info)4802 vunmap_cmd(const cmd_info_t *cmd_info)
4803 {
4804 	return do_unmap(cmd_info->argv[0], VISUAL_MODE);
4805 }
4806 
4807 /* Unmaps keys for the specified mode.  Returns zero on success, otherwise
4808  * non-zero is returned and message is printed on the statusbar. */
4809 static int
do_unmap(const char keys[],int mode)4810 do_unmap(const char keys[], int mode)
4811 {
4812 	wchar_t *subst = substitute_specs(keys);
4813 	if(subst == NULL)
4814 	{
4815 		show_error_msgf("Unmapping Error", "Failed to convert to wide string: %s",
4816 				keys);
4817 		return 0;
4818 	}
4819 
4820 	int result = vle_keys_user_remove(subst, mode);
4821 	free(subst);
4822 
4823 	if(result != 0)
4824 	{
4825 		ui_sb_err("No such mapping");
4826 		return 1;
4827 	}
4828 	return 0;
4829 }
4830 
4831 /* Executes Ctrl-W [count] {arg} normal mode command. */
4832 static int
wincmd_cmd(const cmd_info_t * cmd_info)4833 wincmd_cmd(const cmd_info_t *cmd_info)
4834 {
4835 	int count;
4836 	char *cmd;
4837 	wchar_t *wcmd;
4838 
4839 	if(cmd_info->count != NOT_DEF && cmd_info->count < 0)
4840 	{
4841 		return CMDS_ERR_INVALID_RANGE;
4842 	}
4843 	if(cmd_info->args[0] == '\0' || cmd_info->args[1] != '\0')
4844 	{
4845 		return CMDS_ERR_INVALID_ARG;
4846 	}
4847 
4848 	count = (cmd_info->count <= 1) ? 1 : cmd_info->count;
4849 	cmd = format_str("%c%d%s", NC_C_w, count, cmd_info->args);
4850 
4851 	wcmd = to_wide(cmd);
4852 	if(wcmd == NULL)
4853 	{
4854 		show_error_msgf("Command Error", "Failed to convert to wide string: %s",
4855 				cmd);
4856 		free(cmd);
4857 		return 0;
4858 	}
4859 	free(cmd);
4860 
4861 	(void)vle_keys_exec_timed_out_no_remap(wcmd);
4862 	free(wcmd);
4863 	return 0;
4864 }
4865 
4866 /* Prefix that execute same command-line command for both panes. */
4867 static int
windo_cmd(const cmd_info_t * cmd_info)4868 windo_cmd(const cmd_info_t *cmd_info)
4869 {
4870 	int result = 0;
4871 
4872 	if(cmd_info->argc == 0)
4873 		return 0;
4874 
4875 	result += winrun(&lwin, cmd_info->args) != 0;
4876 	result += winrun(&rwin, cmd_info->args) != 0;
4877 
4878 	update_screen(UT_FULL);
4879 
4880 	return result;
4881 }
4882 
4883 static int
winrun_cmd(const cmd_info_t * cmd_info)4884 winrun_cmd(const cmd_info_t *cmd_info)
4885 {
4886 	int result = 0;
4887 	const char *cmd;
4888 
4889 	if(cmd_info->argc == 0)
4890 		return 0;
4891 
4892 	if(cmd_info->argv[0][1] != '\0' ||
4893 			!char_is_one_of("^$%.,", cmd_info->argv[0][0]))
4894 		return CMDS_ERR_INVALID_ARG;
4895 
4896 	if(cmd_info->argc == 1)
4897 		return 0;
4898 
4899 	cmd = cmd_info->args + 2;
4900 	switch(cmd_info->argv[0][0])
4901 	{
4902 		case '^':
4903 			result += winrun(&lwin, cmd) != 0;
4904 			break;
4905 		case '$':
4906 			result += winrun(&rwin, cmd) != 0;
4907 			break;
4908 		case '%':
4909 			result += winrun(&lwin, cmd) != 0;
4910 			result += winrun(&rwin, cmd) != 0;
4911 			break;
4912 		case '.':
4913 			result += winrun(curr_view, cmd) != 0;
4914 			break;
4915 		case ',':
4916 			result += winrun(other_view, cmd) != 0;
4917 			break;
4918 	}
4919 
4920 	stats_refresh_later();
4921 
4922 	return result;
4923 }
4924 
4925 /* Executes cmd command-line command for a specific view. */
4926 static int
winrun(view_t * view,const char cmd[])4927 winrun(view_t *view, const char cmd[])
4928 {
4929 	const int prev_global_local_settings = curr_stats.global_local_settings;
4930 	int result;
4931 	view_t *tmp_curr, *tmp_other;
4932 
4933 	ui_view_pick(view, &tmp_curr, &tmp_other);
4934 
4935 	/* :winrun and :windo should be able to set settings separately for each
4936 	 * window. */
4937 	curr_stats.global_local_settings = 0;
4938 	result = exec_commands(cmd, curr_view, CIT_COMMAND);
4939 	curr_stats.global_local_settings = prev_global_local_settings;
4940 
4941 	ui_view_unpick(view, tmp_curr, tmp_other);
4942 
4943 	return result;
4944 }
4945 
4946 static int
write_cmd(const cmd_info_t * cmd_info)4947 write_cmd(const cmd_info_t *cmd_info)
4948 {
4949 	state_store();
4950 	return 0;
4951 }
4952 
4953 /* Possibly exits vifm normally with or without saving state to vifminfo
4954  * file. */
4955 static int
qall_cmd(const cmd_info_t * cmd_info)4956 qall_cmd(const cmd_info_t *cmd_info)
4957 {
4958 	vifm_try_leave(!cmd_info->emark, 0, cmd_info->emark);
4959 	return 0;
4960 }
4961 
4962 /* Possibly exits vifm normally with or without saving state to vifminfo file or
4963  * closes a tab. */
4964 static int
quit_cmd(const cmd_info_t * cmd_info)4965 quit_cmd(const cmd_info_t *cmd_info)
4966 {
4967 	ui_quit(!cmd_info->emark, cmd_info->emark);
4968 	return 0;
4969 }
4970 
4971 /* Possibly exits the application saving vifminfo file or closes a tab. */
4972 static int
wq_cmd(const cmd_info_t * cmd_info)4973 wq_cmd(const cmd_info_t *cmd_info)
4974 {
4975 	ui_quit(1, cmd_info->emark);
4976 	return 0;
4977 }
4978 
4979 /* Possibly exits the application saving vifminfo file. */
4980 static int
wqall_cmd(const cmd_info_t * cmd_info)4981 wqall_cmd(const cmd_info_t *cmd_info)
4982 {
4983 	vifm_try_leave(1, 0, cmd_info->emark);
4984 	return 0;
4985 }
4986 
4987 /* Processes :[range]yank command followed by "{reg} [{count}]" or
4988  * "{reg}|{count}". */
4989 static int
yank_cmd(const cmd_info_t * cmd_info)4990 yank_cmd(const cmd_info_t *cmd_info)
4991 {
4992 	int reg = DEFAULT_REG_NAME;
4993 	int result;
4994 
4995 	result = get_reg_and_count(cmd_info, &reg);
4996 	if(result == 0)
4997 	{
4998 		flist_set_marking(curr_view, 0);
4999 		result = fops_yank(curr_view, reg) != 0;
5000 	}
5001 
5002 	return result;
5003 }
5004 
5005 /* Processes arguments of form "{reg} [{count}]" or "{reg}|{count}".  On success
5006  * *reg is set (so it should be initialized before the call) and zero is
5007  * returned, otherwise cmds unit error code is returned. */
5008 static int
get_reg_and_count(const cmd_info_t * cmd_info,int * reg)5009 get_reg_and_count(const cmd_info_t *cmd_info, int *reg)
5010 {
5011 	if(cmd_info->argc == 2)
5012 	{
5013 		int count;
5014 		int error;
5015 
5016 		error = get_reg(cmd_info->argv[0], reg);
5017 		if(error != 0)
5018 		{
5019 			return error;
5020 		}
5021 
5022 		if(!isdigit(cmd_info->argv[1][0]))
5023 			return CMDS_ERR_TRAILING_CHARS;
5024 
5025 		count = atoi(cmd_info->argv[1]);
5026 		if(count == 0)
5027 		{
5028 			ui_sb_err("Count argument can't be zero");
5029 			return CMDS_ERR_CUSTOM;
5030 		}
5031 		flist_sel_count(curr_view, cmd_info->end, count);
5032 	}
5033 	else if(cmd_info->argc == 1)
5034 	{
5035 		if(isdigit(cmd_info->argv[0][0]))
5036 		{
5037 			int count = atoi(cmd_info->argv[0]);
5038 			if(count == 0)
5039 			{
5040 				ui_sb_err("Count argument can't be zero");
5041 				return CMDS_ERR_CUSTOM;
5042 			}
5043 			flist_sel_count(curr_view, cmd_info->end, count);
5044 		}
5045 		else
5046 		{
5047 			return get_reg(cmd_info->argv[0], reg);
5048 		}
5049 	}
5050 	return 0;
5051 }
5052 
5053 /* Processes argument as register name.  On success *reg is set (so it should be
5054  * initialized before the call) and zero is returned, otherwise cmds unit error
5055  * code is returned. */
5056 static int
get_reg(const char arg[],int * reg)5057 get_reg(const char arg[], int *reg)
5058 {
5059 	if(arg[1] != '\0')
5060 	{
5061 		return CMDS_ERR_TRAILING_CHARS;
5062 	}
5063 	if(!regs_exists(arg[0]))
5064 	{
5065 		return CMDS_ERR_TRAILING_CHARS;
5066 	}
5067 
5068 	*reg = arg[0];
5069 	return 0;
5070 }
5071 
5072 /* Special handler for user defined commands, which are defined using
5073  * :command. */
5074 static int
usercmd_cmd(const cmd_info_t * cmd_info)5075 usercmd_cmd(const cmd_info_t *cmd_info)
5076 {
5077 	char *expanded_com;
5078 	MacroFlags flags;
5079 	int external = 1;
5080 	int bg;
5081 	int save_msg = 0;
5082 
5083 	int lpending_marking = lwin.pending_marking;
5084 	int rpending_marking = rwin.pending_marking;
5085 
5086 	/* Expand macros in a bound command. */
5087 	expanded_com = ma_expand(cmd_info->user_action, cmd_info->args, &flags,
5088 			vle_cmds_identify(cmd_info->user_action) == COM_EXECUTE);
5089 
5090 	if(expanded_com[0] == ':')
5091 	{
5092 		/* ma_expand() could have reset the flags, restore them to restore range
5093 		 * selection. */
5094 		lwin.pending_marking = lpending_marking;
5095 		rwin.pending_marking = rpending_marking;
5096 
5097 		commands_scope_start();
5098 
5099 		int sm = exec_commands(expanded_com, curr_view, CIT_COMMAND);
5100 		free(expanded_com);
5101 
5102 		if(commands_scope_finish() != 0)
5103 		{
5104 			ui_sb_err("Unmatched if-else-endif");
5105 			return 1;
5106 		}
5107 
5108 		return sm != 0;
5109 	}
5110 
5111 	bg = parse_bg_mark(expanded_com);
5112 
5113 	flist_sel_stash(curr_view);
5114 
5115 	char *title = format_str(":%s%s%s", cmd_info->user_cmd,
5116 			(cmd_info->raw_args[0] == '\0' ? "" : " "), cmd_info->raw_args);
5117 	int handled = rn_ext(expanded_com, title, flags, bg, &save_msg);
5118 	free(title);
5119 
5120 	if(handled > 0)
5121 	{
5122 		/* Do nothing. */
5123 	}
5124 	else if(handled < 0)
5125 	{
5126 		/* XXX: is it intentional to skip adding such commands to undo list? */
5127 		free(expanded_com);
5128 		return save_msg;
5129 	}
5130 	else if(starts_with_lit(expanded_com, "filter") &&
5131 			char_is_one_of(" !/", expanded_com[6]))
5132 	{
5133 		save_msg = exec_command(expanded_com, curr_view, CIT_COMMAND);
5134 		external = 0;
5135 	}
5136 	else if(expanded_com[0] == '!')
5137 	{
5138 		char *com_beginning = expanded_com;
5139 		int pause = 0;
5140 		com_beginning++;
5141 		if(*com_beginning == '!')
5142 		{
5143 			pause = 1;
5144 			com_beginning++;
5145 		}
5146 		com_beginning = skip_whitespace(com_beginning);
5147 
5148 		if(*com_beginning != '\0' && bg)
5149 		{
5150 			bg_run_external(com_beginning, 0, SHELL_BY_USER);
5151 		}
5152 		else if(strlen(com_beginning) > 0)
5153 		{
5154 			rn_shell(com_beginning, pause ? PAUSE_ALWAYS : PAUSE_ON_ERROR,
5155 					flags != MF_NO_TERM_MUX, SHELL_BY_USER);
5156 		}
5157 	}
5158 	else if(expanded_com[0] == '/')
5159 	{
5160 		exec_command(expanded_com + 1, curr_view, CIT_FSEARCH_PATTERN);
5161 		cmds_preserve_selection();
5162 		external = 0;
5163 	}
5164 	else if(expanded_com[0] == '=')
5165 	{
5166 		exec_command(expanded_com + 1, curr_view, CIT_FILTER_PATTERN);
5167 		ui_view_schedule_reload(curr_view);
5168 		cmds_preserve_selection();
5169 		external = 0;
5170 	}
5171 	else if(bg)
5172 	{
5173 		bg_run_external(expanded_com, 0, SHELL_BY_USER);
5174 	}
5175 	else
5176 	{
5177 		rn_shell(expanded_com, PAUSE_ON_ERROR, flags != MF_NO_TERM_MUX,
5178 				SHELL_BY_USER);
5179 	}
5180 
5181 	if(external)
5182 	{
5183 		un_group_reopen_last();
5184 		un_group_add_op(OP_USR, strdup(expanded_com), NULL, "", "");
5185 		un_group_close();
5186 	}
5187 
5188 	free(expanded_com);
5189 
5190 	return save_msg;
5191 }
5192 
5193 /* Checks for background mark and trims it from the command.  Returns non-zero
5194  * if mark is found, and zero otherwise. */
5195 static int
parse_bg_mark(char cmd[])5196 parse_bg_mark(char cmd[])
5197 {
5198 	/* Mark is: space, ampersand, any number of trailing separators. */
5199 
5200 	char *const amp = strrchr(cmd, '&');
5201 	if(amp == NULL || amp - 1 < cmd || *vle_cmds_at_arg(amp + 1) != '\0')
5202 	{
5203 		return 0;
5204 	}
5205 
5206 	amp[-1] = '\0';
5207 	return 1;
5208 }
5209 
5210 TSTATIC void
cmds_drop_state(void)5211 cmds_drop_state(void)
5212 {
5213 	update_string(&cmds_state.find.last_args, NULL);
5214 	cmds_state.find.includes_path = 0;
5215 }
5216 
5217 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
5218 /* vim: set cinoptions+=t0 filetype=c : */
5219