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 = ©_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 = ®isters_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 = ®isters_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 = ®ular_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, ®);
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, <, &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], ®);
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, ®);
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