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 "cmdline.h"
21
22 #include <curses.h>
23
24 #include <limits.h>
25
26 #include <assert.h> /* assert() */
27 #include <stddef.h> /* NULL size_t wchar_t */
28 #include <stdlib.h> /* free() */
29 #include <string.h> /* strdup() */
30 #include <wchar.h> /* wcslen() wcsncpy() */
31 #include <wctype.h> /* iswprint() */
32
33 #include "../cfg/config.h"
34 #include "../compat/curses.h"
35 #include "../compat/fs_limits.h"
36 #include "../compat/reallocarray.h"
37 #include "../engine/abbrevs.h"
38 #include "../engine/cmds.h"
39 #include "../engine/completion.h"
40 #include "../engine/keys.h"
41 #include "../engine/mode.h"
42 #include "../modes/dialogs/msg_dialog.h"
43 #include "../ui/color_scheme.h"
44 #include "../ui/colors.h"
45 #include "../ui/fileview.h"
46 #include "../ui/statusbar.h"
47 #include "../ui/statusline.h"
48 #include "../ui/quickview.h"
49 #include "../ui/ui.h"
50 #include "../utils/hist.h"
51 #include "../utils/macros.h"
52 #include "../utils/matcher.h"
53 #include "../utils/path.h"
54 #include "../utils/str.h"
55 #include "../utils/test_helpers.h"
56 #include "../utils/utf8.h"
57 #include "../utils/utils.h"
58 #include "../cmd_completion.h"
59 #include "../cmd_core.h"
60 #include "../filelist.h"
61 #include "../filtering.h"
62 #include "../flist_pos.h"
63 #include "../flist_sel.h"
64 #include "../marks.h"
65 #include "../search.h"
66 #include "../status.h"
67 #include "dialogs/attr_dialog.h"
68 #include "dialogs/sort_dialog.h"
69 #include "menu.h"
70 #include "modes.h"
71 #include "normal.h"
72 #include "visual.h"
73 #include "wk.h"
74
75 /* History search mode. */
76 typedef enum
77 {
78 HIST_NONE, /* No search in history is active. */
79 HIST_GO, /* Retrieving items from history one by one. */
80 HIST_SEARCH /* Retrieving items that match entered prefix skipping others. */
81 }
82 HIST;
83
84 /* Describes possible states of a prompt used for interactive search. */
85 typedef enum
86 {
87 PS_NORMAL, /* Normal state (empty input or input is OK). */
88 PS_WRONG_PATTERN, /* Pattern contains a mistake. */
89 PS_NO_MATCH, /* Pattern is OK, but no matches found. */
90 }
91 PromptState;
92
93 /* Holds state of the command-line editing mode. */
94 typedef struct
95 {
96 wchar_t *line; /* the line reading */
97 wchar_t *initial_line; /* initial state of the line */
98 int index; /* index of the current character in cmdline */
99 int curs_pos; /* position of the cursor in status bar*/
100 int len; /* length of the string */
101 int cmd_pos; /* position in the history */
102 wchar_t prompt[NAME_MAX + 1]; /* prompt */
103 int prompt_wid; /* width of prompt */
104 int complete_continue; /* if non-zero, continue previous completion */
105 int dot_pos; /* history pos for dot completion, or < 0 */
106 size_t dot_index; /* dot completion line index */
107 size_t dot_len; /* dot completion previous completion len */
108 HIST history_search; /* HIST_* */
109 int hist_search_len; /* length of history search pattern */
110 wchar_t *line_buf; /* content of line before using history */
111 int reverse_completion;
112 complete_cmd_func complete;
113 int search_mode;
114 int old_top; /* for search_mode */
115 int old_pos; /* for search_mode */
116 int line_edited; /* Cache for whether input line changed flag. */
117 int enter_mapping_state; /* The mapping state at entering the mode. */
118 int expanding_abbrev; /* Abbreviation expansion is in progress. */
119 PromptState state; /* Prompt state with regard to current input. */
120 }
121 line_stats_t;
122
123 static int prev_mode;
124 static CmdLineSubmode sub_mode;
125 static line_stats_t input_stat;
126 /* Width of the status bar. */
127 static int line_width = 1;
128 static void *sub_mode_ptr;
129 /* Whether current submode allows external editing. */
130 static int sub_mode_allows_ee;
131
132 static int def_handler(wchar_t key);
133 static void update_cmdline_text(line_stats_t *stat);
134 static void draw_cmdline_text(line_stats_t *stat);
135 static void input_line_changed(void);
136 static void handle_empty_input(void);
137 static void handle_nonempty_input(void);
138 static void update_state(int result, int nmatches);
139 static void set_local_filter(const char value[]);
140 static wchar_t * wcsins(wchar_t src[], const wchar_t ins[], int pos);
141 static void prepare_cmdline_mode(const wchar_t prompt[], const wchar_t cmd[],
142 complete_cmd_func complete);
143 static void save_view_port(void);
144 static void set_view_port(void);
145 static int is_line_edited(void);
146 static void leave_cmdline_mode(void);
147 static void cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info);
148 static void cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info);
149 static CmdInputType cls_to_editable_cit(CmdLineSubmode sub_mode);
150 static void extedit_prompt(const char input[], int cursor_col);
151 static void cmd_ctrl_rb(key_info_t key_info, keys_info_t *keys_info);
152 static void cmd_ctrl_h(key_info_t key_info, keys_info_t *keys_info);
153 static int should_quit_on_backspace(void);
154 static int no_initial_line(void);
155 static void cmd_ctrl_i(key_info_t key_info, keys_info_t *keys_info);
156 static void cmd_shift_tab(key_info_t key_info, keys_info_t *keys_info);
157 static void do_completion(void);
158 static void draw_wild_menu(int op);
159 static int draw_wild_bar(int *last_pos, int *pos, int *len);
160 static int draw_wild_popup(int *last_pos, int *pos, int *len);
161 static void cmd_ctrl_k(key_info_t key_info, keys_info_t *keys_info);
162 static void cmd_return(key_info_t key_info, keys_info_t *keys_info);
163 static int is_input_line_empty(void);
164 static void expand_abbrev(void);
165 TSTATIC const wchar_t * extract_abbrev(line_stats_t *stat, int *pos,
166 int *no_remap);
167 static void exec_abbrev(const wchar_t abbrev_rhs[], int no_remap, int pos);
168 static void save_input_to_history(const keys_info_t *keys_info,
169 const char input[]);
170 static void finish_prompt_submode(const char input[]);
171 static CmdInputType search_cls_to_cit(CmdLineSubmode sub_mode);
172 static int is_forward_search(CmdLineSubmode sub_mode);
173 static int is_backward_search(CmdLineSubmode sub_mode);
174 static int replace_wstring(wchar_t **str, const wchar_t with[]);
175 static void cmd_ctrl_n(key_info_t key_info, keys_info_t *keys_info);
176 #ifdef ENABLE_EXTENDED_KEYS
177 static void cmd_down(key_info_t key_info, keys_info_t *keys_info);
178 #endif /* ENABLE_EXTENDED_KEYS */
179 static void hist_next(line_stats_t *stat, const hist_t *hist, size_t len);
180 static void cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info);
181 static void cmd_ctrl_w(key_info_t key_info, keys_info_t *keys_info);
182 static void cmd_ctrl_xslash(key_info_t key_info, keys_info_t *keys_info);
183 static void cmd_ctrl_xa(key_info_t key_info, keys_info_t *keys_info);
184 static void cmd_ctrl_xc(key_info_t key_info, keys_info_t *keys_info);
185 static void cmd_ctrl_xxc(key_info_t key_info, keys_info_t *keys_info);
186 static void paste_short_path(view_t *view);
187 static void cmd_ctrl_xd(key_info_t key_info, keys_info_t *keys_info);
188 static void cmd_ctrl_xxd(key_info_t key_info, keys_info_t *keys_info);
189 static void cmd_ctrl_xe(key_info_t key_info, keys_info_t *keys_info);
190 static void cmd_ctrl_xxe(key_info_t key_info, keys_info_t *keys_info);
191 static void cmd_ctrl_xm(key_info_t key_info, keys_info_t *keys_info);
192 static void cmd_ctrl_xr(key_info_t key_info, keys_info_t *keys_info);
193 static void cmd_ctrl_xxr(key_info_t key_info, keys_info_t *keys_info);
194 static void paste_short_path_root(view_t *view);
195 static void cmd_ctrl_xt(key_info_t key_info, keys_info_t *keys_info);
196 static void cmd_ctrl_xxt(key_info_t key_info, keys_info_t *keys_info);
197 static void cmd_ctrl_xequals(key_info_t key_info, keys_info_t *keys_info);
198 static void paste_name_part(const char name[], int root);
199 static void paste_str(const char str[], int allow_escaping);
200 static char * escape_cmd_for_pasting(const char str[]);
201 static void cmd_ctrl_underscore(key_info_t key_info, keys_info_t *keys_info);
202 static void cmd_meta_b(key_info_t key_info, keys_info_t *keys_info);
203 static void find_prev_word(void);
204 static void cmd_meta_d(key_info_t key_info, keys_info_t *keys_info);
205 static void cmd_meta_f(key_info_t key_info, keys_info_t *keys_info);
206 static void cmd_meta_dot(key_info_t key_info, keys_info_t *keys_info);
207 static void remove_previous_dot_completion(void);
208 static wchar_t * next_dot_completion(void);
209 static int insert_dot_completion(const wchar_t completion[]);
210 static int insert_str(const wchar_t str[]);
211 static void find_next_word(void);
212 static void cmd_left(key_info_t key_info, keys_info_t *keys_info);
213 static void cmd_right(key_info_t key_info, keys_info_t *keys_info);
214 static void cmd_home(key_info_t key_info, keys_info_t *keys_info);
215 static void cmd_end(key_info_t key_info, keys_info_t *keys_info);
216 static void cmd_delete(key_info_t key_info, keys_info_t *keys_info);
217 static void update_cursor(void);
218 TSTATIC void hist_prev(line_stats_t *stat, const hist_t *hist, size_t len);
219 static int replace_input_line(line_stats_t *stat, const char new[]);
220 static const hist_t * pick_hist(void);
221 static void update_cmdline(line_stats_t *stat);
222 static int get_required_height(void);
223 static void cmd_ctrl_p(key_info_t key_info, keys_info_t *keys_info);
224 static void cmd_ctrl_t(key_info_t key_info, keys_info_t *keys_info);
225 #ifdef ENABLE_EXTENDED_KEYS
226 static void cmd_up(key_info_t key_info, keys_info_t *keys_info);
227 #endif /* ENABLE_EXTENDED_KEYS */
228 static void update_cmdline_size(void);
229 TSTATIC int line_completion(line_stats_t *stat);
230 static char * escaped_arg_hook(const char match[]);
231 static char * squoted_arg_hook(const char match[]);
232 static char * dquoted_arg_hook(const char match[]);
233 static void update_line_stat(line_stats_t *stat, int new_len);
234 static wchar_t * wcsdel(wchar_t *src, int pos, int len);
235 static void stop_completion(void);
236 static void stop_dot_completion(void);
237 static void stop_regular_completion(void);
238
239 static keys_add_info_t builtin_cmds[] = {
240 {WK_C_c, {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}},
241 {WK_C_g, {{&cmd_ctrl_g}, .descr = "edit cmdline in editor"}},
242 {WK_C_h, {{&cmd_ctrl_h}, .descr = "remove char to the left"}},
243 {WK_C_i, {{&cmd_ctrl_i}, .descr = "start/continue completion"}},
244 {WK_C_k, {{&cmd_ctrl_k}, .descr = "remove line part to the right"}},
245 {WK_CR, {{&cmd_return}, .descr = "execute/accept input"}},
246 {WK_C_n, {{&cmd_ctrl_n}, .descr = "recall next history item"}},
247 {WK_C_p, {{&cmd_ctrl_p}, .descr = "recall previous history item"}},
248 {WK_C_t, {{&cmd_ctrl_t}, .descr = "swap adjacent characters"}},
249 {WK_ESC, {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}},
250 {WK_ESC WK_ESC, {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}},
251 {WK_C_RB, {{&cmd_ctrl_rb}, .descr = "expand abbreviation"}},
252 {WK_C_USCORE, {{&cmd_ctrl_underscore}, .descr = "reset completion"}},
253 {WK_DELETE, {{&cmd_ctrl_h}, .descr = "remove char to the left"}},
254 {WK_ESC L"[Z", {{&cmd_shift_tab}, .descr = "complete in reverse order"}},
255 {WK_C_b, {{&cmd_left}, .descr = "move cursor to the left"}},
256 {WK_C_f, {{&cmd_right}, .descr = "move cursor to the right"}},
257 {WK_C_a, {{&cmd_home}, .descr = "move cursor to the beginning"}},
258 {WK_C_e, {{&cmd_end}, .descr = "move cursor to the end"}},
259 {WK_C_d, {{&cmd_delete}, .descr = "delete current character"}},
260 {WK_C_u, {{&cmd_ctrl_u}, .descr = "remove line part to the left"}},
261 {WK_C_w, {{&cmd_ctrl_w}, .descr = "remove word to the left"}},
262 {WK_C_x WK_SLASH, {{&cmd_ctrl_xslash}, .descr = "insert last search pattern"}},
263 {WK_C_x WK_a, {{&cmd_ctrl_xa}, .descr = "insert implicit permanent filter value"}},
264 {WK_C_x WK_c, {{&cmd_ctrl_xc}, .descr = "insert current file name"}},
265 {WK_C_x WK_d, {{&cmd_ctrl_xd}, .descr = "insert current directory path"}},
266 {WK_C_x WK_e, {{&cmd_ctrl_xe}, .descr = "insert current file extension"}},
267 {WK_C_x WK_m, {{&cmd_ctrl_xm}, .descr = "insert explicit permanent filter value"}},
268 {WK_C_x WK_r, {{&cmd_ctrl_xr}, .descr = "insert root of current file name"}},
269 {WK_C_x WK_t, {{&cmd_ctrl_xt}, .descr = "insert name of current directory"}},
270 {WK_C_x WK_C_x WK_c, {{&cmd_ctrl_xxc}, .descr = "insert other file name"}},
271 {WK_C_x WK_C_x WK_d, {{&cmd_ctrl_xxd}, .descr = "insert other directory path"}},
272 {WK_C_x WK_C_x WK_e, {{&cmd_ctrl_xxe}, .descr = "insert other file extension"}},
273 {WK_C_x WK_C_x WK_r, {{&cmd_ctrl_xxr}, .descr = "insert root of other file name"}},
274 {WK_C_x WK_C_x WK_t, {{&cmd_ctrl_xxt}, .descr = "insert name of other directory"}},
275 {WK_C_x WK_EQUALS, {{&cmd_ctrl_xequals}, .descr = "insert local filter value"}},
276 #ifndef __PDCURSES__
277 {WK_ESC WK_b, {{&cmd_meta_b}, .descr = "move cursor to previous word"}},
278 {WK_ESC WK_d, {{&cmd_meta_d}, .descr = "remove next word"}},
279 {WK_ESC WK_f, {{&cmd_meta_f}, .descr = "move cursor to next word"}},
280 {WK_ESC WK_DOT, {{&cmd_meta_dot}, .descr = "start/continue last arg completion"}},
281 #else
282 {{K(ALT_B)}, {{&cmd_meta_b}, .descr = "move cursor to previous word"}},
283 {{K(ALT_D)}, {{&cmd_meta_d}, .descr = "remove next word"}},
284 {{K(ALT_F)}, {{&cmd_meta_f}, .descr = "move cursor to next word"}},
285 {{K(ALT_PERIOD)}, {{&cmd_meta_dot}, .descr = "start/continue last arg completion"}},
286 #endif
287 #ifdef ENABLE_EXTENDED_KEYS
288 {{K(KEY_BACKSPACE)}, {{&cmd_ctrl_h}, .descr = "remove char to the left"}},
289 {{K(KEY_DOWN)}, {{&cmd_down}, .descr = "prefix-complete next history item"}},
290 {{K(KEY_UP)}, {{&cmd_up}, .descr = "prefix-complete previous history item"}},
291 {{K(KEY_LEFT)}, {{&cmd_left}, .descr = "move cursor to the left"}},
292 {{K(KEY_RIGHT)}, {{&cmd_right}, .descr = "move cursor to the right"}},
293 {{K(KEY_HOME)}, {{&cmd_home}, .descr = "move cursor to the beginning"}},
294 {{K(KEY_END)}, {{&cmd_end}, .descr = "move cursor to the end"}},
295 {{K(KEY_DC)}, {{&cmd_delete}, .descr = "delete current character"}},
296 {{K(KEY_BTAB)}, {{&cmd_shift_tab}, .descr = "complete in reverse order"}},
297 #endif /* ENABLE_EXTENDED_KEYS */
298 };
299
300 void
modcline_init(void)301 modcline_init(void)
302 {
303 int ret_code;
304
305 vle_keys_set_def_handler(CMDLINE_MODE, &def_handler);
306
307 ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), CMDLINE_MODE);
308 assert(ret_code == 0);
309
310 (void)ret_code;
311 }
312
313 /* Handles all keys uncaught by shortcuts. Returns zero on success and non-zero
314 * on error. */
315 static int
def_handler(wchar_t key)316 def_handler(wchar_t key)
317 {
318 void *p;
319 wchar_t buf[2] = {key, L'\0'};
320
321 input_stat.history_search = HIST_NONE;
322
323 if(input_stat.complete_continue
324 && input_stat.line[input_stat.index - 1] == L'/' && key == L'/')
325 {
326 stop_completion();
327 return 0;
328 }
329
330 /* There is no need for resetting completion because of terminal dimensions
331 * change (note also that iswprint(KEY_RESIZE) might return false). */
332 if(key == K(KEY_RESIZE))
333 {
334 return 0;
335 }
336
337 stop_completion();
338
339 if(key != L'\r' && !iswprint(key))
340 {
341 return 0;
342 }
343
344 if(!cfg_is_word_wchar(key))
345 {
346 expand_abbrev();
347 }
348
349 p = reallocarray(input_stat.line, input_stat.len + 2, sizeof(wchar_t));
350 if(p == NULL)
351 {
352 leave_cmdline_mode();
353 return 0;
354 }
355
356 input_stat.line = p;
357
358 input_stat.index++;
359 wcsins(input_stat.line, buf, input_stat.index);
360 input_stat.len++;
361
362 input_stat.curs_pos += vifm_wcwidth(key);
363
364 update_cmdline_size();
365 update_cmdline_text(&input_stat);
366
367 return 0;
368 }
369
370 /* Processes input change and redraw of resulting command-line. */
371 static void
update_cmdline_text(line_stats_t * stat)372 update_cmdline_text(line_stats_t *stat)
373 {
374 input_line_changed();
375 draw_cmdline_text(stat);
376 }
377
378 /* Updates text displayed on the command line and cursor within it. */
379 static void
draw_cmdline_text(line_stats_t * stat)380 draw_cmdline_text(line_stats_t *stat)
381 {
382 if(stats_silenced_ui())
383 {
384 /* XXX: erasing status_bar causes blinking if <silent> mapping that uses
385 * command-line is pressed and held down. Ideally wouldn't need this
386 * return. */
387 return;
388 }
389
390 int pair = -1;
391 col_attr_t prompt_col = {};
392
393 werase(status_bar);
394
395 switch(input_stat.state)
396 {
397 case PS_NORMAL: pair = CMD_LINE_COLOR; break;
398 case PS_WRONG_PATTERN: pair = ERROR_MSG_COLOR; break;
399 case PS_NO_MATCH: pair = CMD_LINE_COLOR;
400 prompt_col.attr = A_REVERSE;
401 break;
402 }
403 prompt_col.attr ^= cfg.cs.color[pair].attr;
404
405 ui_set_attr(status_bar, &prompt_col, cfg.cs.pair[pair]);
406 compat_mvwaddwstr(status_bar, 0, 0, stat->prompt);
407
408 ui_set_attr(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
409 cfg.cs.pair[CMD_LINE_COLOR]);
410 compat_mvwaddwstr(status_bar, stat->prompt_wid/line_width,
411 stat->prompt_wid%line_width, stat->line);
412
413 update_cursor();
414 ui_refresh_win(status_bar);
415 }
416
417 /* Callback-like function, which is called every time input line is changed. */
418 static void
input_line_changed(void)419 input_line_changed(void)
420 {
421 static wchar_t *previous;
422
423 if(!cfg.inc_search || (!input_stat.search_mode && sub_mode != CLS_FILTER))
424 {
425 return;
426 }
427
428 /* Hide cursor during view update, otherwise user might notice it blinking in
429 * wrong place. */
430 curs_set(0);
431
432 /* set_view_port() should not be called if none of the conditions are true. */
433
434 input_stat.state = PS_NORMAL;
435 if(is_input_line_empty())
436 {
437 free(previous);
438 previous = NULL;
439
440 set_view_port();
441 handle_empty_input();
442 }
443 else if(previous == NULL || wcscmp(previous, input_stat.line) != 0)
444 {
445 (void)replace_wstring(&previous, input_stat.line);
446
447 set_view_port();
448 handle_nonempty_input();
449 }
450
451 if(prev_mode != MENU_MODE && prev_mode != VISUAL_MODE)
452 {
453 fpos_set_pos(curr_view, curr_view->list_pos);
454 qv_ui_updated();
455 redraw_current_view();
456 }
457 else if(prev_mode == MENU_MODE)
458 {
459 modmenu_full_redraw();
460 }
461
462 /* Hardware cursor is moved on the screen only on refresh, so refresh status
463 * bar to force cursor moving there before it becomes visible again. */
464 ui_refresh_win(status_bar);
465 curs_set(1);
466 }
467
468 /* Provides reaction for empty input during interactive search/filtering. */
469 static void
handle_empty_input(void)470 handle_empty_input(void)
471 {
472 /* Clear selection/highlight. */
473 if(prev_mode == MENU_MODE)
474 {
475 (void)menus_search("", sub_mode_ptr, 0);
476 }
477 else if(cfg.hl_search)
478 {
479 flist_sel_stash(curr_view);
480 }
481
482 if(prev_mode != MENU_MODE)
483 {
484 ui_view_reset_search_highlight(curr_view);
485 }
486
487 if(sub_mode == CLS_FILTER)
488 {
489 set_local_filter("");
490 }
491 }
492
493 /* Provides reaction for non-empty input during interactive search/filtering. */
494 static void
handle_nonempty_input(void)495 handle_nonempty_input(void)
496 {
497 char *const mbinput = to_multibyte(input_stat.line);
498 int backward = 0;
499
500 switch(sub_mode)
501 {
502 int result;
503
504 case CLS_BSEARCH: backward = 1; /* Fall through. */
505 case CLS_FSEARCH:
506 result = modnorm_find(curr_view, mbinput, backward, 0);
507 update_state(result, curr_view->matches);
508 break;
509 case CLS_VBSEARCH: backward = 1; /* Fall through. */
510 case CLS_VFSEARCH:
511 result = modvis_find(curr_view, mbinput, backward, 0);
512 update_state(result, curr_view->matches);
513 break;
514 case CLS_MENU_FSEARCH:
515 case CLS_MENU_BSEARCH:
516 result = menus_search(mbinput, sub_mode_ptr, 0);
517 update_state(result, menus_search_matched(sub_mode_ptr));
518 break;
519 case CLS_FILTER:
520 set_local_filter(mbinput);
521 break;
522
523 default:
524 assert("Unexpected command-line submode.");
525 break;
526 }
527
528 free(mbinput);
529 }
530
531 /* Computes and sets new prompt state from result of a search and number of
532 * matched items. It is assumed that current state is PS_NORMAL. */
533 static void
update_state(int result,int nmatches)534 update_state(int result, int nmatches)
535 {
536 if(result < 0)
537 {
538 input_stat.state = PS_WRONG_PATTERN;
539 }
540 else if(nmatches == 0)
541 {
542 input_stat.state = PS_NO_MATCH;
543 }
544 }
545
546 /* Updates value of the local filter of the current view. */
547 static void
set_local_filter(const char value[])548 set_local_filter(const char value[])
549 {
550 const int rel_pos = input_stat.old_pos - input_stat.old_top;
551 const int result = local_filter_set(curr_view, value);
552 if(result != 0)
553 {
554 input_stat.state = (result < 0) ? PS_WRONG_PATTERN : PS_NO_MATCH;
555 }
556 local_filter_update_view(curr_view, rel_pos);
557 }
558
559 /* Insert a string into another string
560 * For example, wcsins(L"foor", L"ba", 4) returns L"foobar"
561 * If pos is larger then wcslen(src), the character will
562 * be added at the end of the src */
563 static wchar_t *
wcsins(wchar_t src[],const wchar_t ins[],int pos)564 wcsins(wchar_t src[], const wchar_t ins[], int pos)
565 {
566 int i;
567 wchar_t *p;
568
569 pos--;
570
571 for (p = src + pos; *p; p++)
572 ;
573
574 for (i = 0; ins[i]; i++)
575 ;
576
577 for (; p >= src + pos; p--)
578 *(p + i) = *p;
579 p++;
580
581 for (; *ins; ins++, p++)
582 *p = *ins;
583
584 return src;
585 }
586
587 void
modcline_enter(CmdLineSubmode cl_sub_mode,const char cmd[],void * ptr)588 modcline_enter(CmdLineSubmode cl_sub_mode, const char cmd[], void *ptr)
589 {
590 wchar_t *wcmd;
591 const wchar_t *wprompt;
592 complete_cmd_func complete_func = NULL;
593
594 if(cl_sub_mode == CLS_FILTER && curr_view->custom.type == CV_DIFF)
595 {
596 show_error_msg("Filtering", "No local filter for diff views");
597 return;
598 }
599
600 wcmd = to_wide_force(cmd);
601 if(wcmd == NULL)
602 {
603 show_error_msg("Error", "Not enough memory");
604 return;
605 }
606
607 sub_mode_ptr = ptr;
608 sub_mode = cl_sub_mode;
609 sub_mode_allows_ee = 0;
610
611 if(sub_mode == CLS_COMMAND || sub_mode == CLS_MENU_COMMAND)
612 {
613 wprompt = L":";
614 complete_func = &vle_cmds_complete;
615 }
616 else if(sub_mode == CLS_FILTER)
617 {
618 wprompt = L"=";
619 }
620 else if(is_forward_search(sub_mode))
621 {
622 wprompt = L"/";
623 }
624 else if(is_backward_search(sub_mode))
625 {
626 wprompt = L"?";
627 }
628 else
629 {
630 assert(0 && "Unknown command line submode.");
631 wprompt = L"E";
632 }
633
634 prepare_cmdline_mode(wprompt, wcmd, complete_func);
635 free(wcmd);
636 }
637
638 void
modcline_prompt(const char prompt[],const char cmd[],prompt_cb cb,complete_cmd_func complete,int allow_ee)639 modcline_prompt(const char prompt[], const char cmd[], prompt_cb cb,
640 complete_cmd_func complete, int allow_ee)
641 {
642 wchar_t *wprompt;
643 wchar_t *wcmd;
644
645 sub_mode_ptr = cb;
646 sub_mode = CLS_PROMPT;
647 sub_mode_allows_ee = allow_ee;
648
649 wprompt = to_wide_force(prompt);
650 wcmd = to_wide_force(cmd);
651 if(wprompt == NULL || wcmd == NULL)
652 {
653 show_error_msg("Error", "Not enough memory");
654 }
655 else
656 {
657 prepare_cmdline_mode(wprompt, wcmd, complete);
658 }
659
660 free(wprompt);
661 free(wcmd);
662 }
663
664 void
modcline_redraw(void)665 modcline_redraw(void)
666 {
667 if(prev_mode == MENU_MODE)
668 {
669 modmenu_full_redraw();
670 }
671 else
672 {
673 update_screen(UT_REDRAW);
674 if(prev_mode == SORT_MODE)
675 {
676 redraw_sort_dialog();
677 }
678 else if(prev_mode == ATTR_MODE)
679 {
680 redraw_attr_dialog();
681 }
682 }
683
684 line_width = getmaxx(stdscr);
685 update_cmdline_size();
686 draw_cmdline_text(&input_stat);
687 curs_set(1);
688
689 if(input_stat.complete_continue && cfg.wild_menu)
690 {
691 draw_wild_menu(-1);
692 }
693 }
694
695 /* Performs all necessary preparations for command-line mode to start
696 * operating. */
697 static void
prepare_cmdline_mode(const wchar_t prompt[],const wchar_t cmd[],complete_cmd_func complete)698 prepare_cmdline_mode(const wchar_t prompt[], const wchar_t cmd[],
699 complete_cmd_func complete)
700 {
701 line_width = getmaxx(stdscr);
702 prev_mode = vle_mode_get();
703 vle_mode_set(CMDLINE_MODE, VMT_SECONDARY);
704
705 ui_sb_lock();
706
707 input_stat.line = vifm_wcsdup(cmd);
708 input_stat.initial_line = vifm_wcsdup(input_stat.line);
709 input_stat.index = wcslen(cmd);
710 input_stat.curs_pos = vifm_wcswidth(input_stat.line, (size_t)-1);
711 input_stat.len = input_stat.index;
712 input_stat.cmd_pos = -1;
713 input_stat.complete_continue = 0;
714 input_stat.history_search = HIST_NONE;
715 input_stat.line_buf = NULL;
716 input_stat.reverse_completion = 0;
717 input_stat.complete = complete;
718 input_stat.search_mode = 0;
719 input_stat.dot_pos = -1;
720 input_stat.line_edited = 0;
721 input_stat.enter_mapping_state = vle_keys_mapping_state();
722 input_stat.state = PS_NORMAL;
723
724 if((is_forward_search(sub_mode) || is_backward_search(sub_mode)) &&
725 sub_mode != CLS_VWFSEARCH && sub_mode != CLS_VWBSEARCH)
726 {
727 input_stat.search_mode = 1;
728 }
729
730 if(input_stat.search_mode || sub_mode == CLS_FILTER)
731 {
732 save_view_port();
733 }
734
735 wcsncpy(input_stat.prompt, prompt, ARRAY_LEN(input_stat.prompt));
736 input_stat.prompt_wid = vifm_wcswidth(input_stat.prompt, (size_t)-1);
737 input_stat.curs_pos += input_stat.prompt_wid;
738
739 update_cmdline_size();
740 update_cmdline_text(&input_stat);
741 if(curr_stats.load_stage > 0)
742 {
743 curs_set(1);
744 }
745
746 curr_stats.save_msg = 1;
747
748 if(prev_mode == NORMAL_MODE)
749 init_commands();
750 }
751
752 /* Stores view port parameters (top line, current position). */
753 static void
save_view_port(void)754 save_view_port(void)
755 {
756 if(prev_mode != MENU_MODE)
757 {
758 input_stat.old_top = curr_view->top_line;
759 input_stat.old_pos = curr_view->list_pos;
760 }
761 else
762 {
763 modmenu_save_pos();
764 }
765 }
766
767 /* Sets view port parameters to appropriate for current submode state. */
768 static void
set_view_port(void)769 set_view_port(void)
770 {
771 if(prev_mode == MENU_MODE)
772 {
773 modmenu_restore_pos();
774 return;
775 }
776
777 if(sub_mode != CLS_FILTER || !is_line_edited())
778 {
779 curr_view->top_line = input_stat.old_top;
780 curr_view->list_pos = input_stat.old_pos;
781 }
782 else if(sub_mode == CLS_FILTER)
783 {
784 /* Filtering itself doesn't update status line. */
785 ui_stat_update(curr_view, 1);
786 }
787
788 if(prev_mode == VISUAL_MODE)
789 {
790 modvis_update();
791 }
792 }
793
794 /* Checks whether line was edited since entering command-line mode. */
795 static int
is_line_edited(void)796 is_line_edited(void)
797 {
798 if(input_stat.line_edited)
799 {
800 return 1;
801 }
802
803 input_stat.line_edited = wcscmp(input_stat.line, input_stat.initial_line);
804 return input_stat.line_edited;
805 }
806
807 /* Leaves command-line mode. */
808 static void
leave_cmdline_mode(void)809 leave_cmdline_mode(void)
810 {
811 const int multiline_status_bar = (getmaxy(status_bar) > 1);
812
813 wresize(status_bar, 1, getmaxx(stdscr) - FIELDS_WIDTH());
814 if(multiline_status_bar)
815 {
816 stats_redraw_later();
817 mvwin(status_bar, getmaxy(stdscr) - 1, 0);
818 if(prev_mode == MENU_MODE)
819 {
820 wresize(menu_win, getmaxy(stdscr) - 1, getmaxx(stdscr));
821 modmenu_partial_redraw();
822 }
823 }
824
825 free(input_stat.line);
826 free(input_stat.initial_line);
827 free(input_stat.line_buf);
828 input_stat.line = NULL;
829 input_stat.initial_line = NULL;
830 input_stat.line_buf = NULL;
831
832 if(curr_stats.load_stage > 0)
833 {
834 curs_set(0);
835 }
836 curr_stats.save_msg = 0;
837 ui_sb_unlock();
838 ui_sb_clear();
839
840 if(vle_mode_is(CMDLINE_MODE))
841 {
842 vle_mode_set(prev_mode, VMT_PRIMARY);
843 }
844
845 if(!vle_mode_is(MENU_MODE))
846 {
847 ui_ruler_update(curr_view, 1);
848 }
849
850 if(prev_mode != MENU_MODE && prev_mode != VIEW_MODE)
851 {
852 redraw_current_view();
853 }
854 }
855
856 /* Initiates leaving of command-line mode and reverting related changes in other
857 * parts of the interface. */
858 static void
cmd_ctrl_c(key_info_t key_info,keys_info_t * keys_info)859 cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info)
860 {
861 char *mbstr;
862
863 stop_completion();
864 werase(status_bar);
865 wnoutrefresh(status_bar);
866
867 mbstr = to_multibyte(input_stat.line);
868 save_input_to_history(keys_info, mbstr);
869 free(mbstr);
870
871 if(sub_mode != CLS_FILTER)
872 {
873 input_stat.line[0] = L'\0';
874 input_line_changed();
875 }
876
877 leave_cmdline_mode();
878
879 if(prev_mode == VISUAL_MODE)
880 {
881 if(!input_stat.search_mode)
882 {
883 modvis_leave(curr_stats.save_msg, 1, 1);
884 fpos_set_pos(curr_view, marks_find_in_view(curr_view, '<'));
885 }
886 }
887 if(sub_mode == CLS_COMMAND)
888 {
889 curr_stats.save_msg = exec_commands("", curr_view, CIT_COMMAND);
890 }
891 else if(sub_mode == CLS_FILTER)
892 {
893 local_filter_cancel(curr_view);
894 curr_view->top_line = input_stat.old_top;
895 curr_view->list_pos = input_stat.old_pos;
896 redraw_current_view();
897 }
898 }
899
900 /* Opens the editor with already typed in characters, gets entered line and
901 * executes it as if it was typed. */
902 static void
cmd_ctrl_g(key_info_t key_info,keys_info_t * keys_info)903 cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info)
904 {
905 const CmdInputType type = cls_to_editable_cit(sub_mode);
906 const int prompt_ee = sub_mode == CLS_PROMPT && sub_mode_allows_ee;
907 if(type != (CmdInputType)-1 || prompt_ee)
908 {
909 char *const mbstr = to_multibyte(input_stat.line);
910 leave_cmdline_mode();
911
912 if(sub_mode == CLS_FILTER)
913 {
914 local_filter_cancel(curr_view);
915 }
916
917 if(prompt_ee)
918 {
919 extedit_prompt(mbstr, input_stat.index + 1);
920 }
921 else
922 {
923 get_and_execute_command(mbstr, input_stat.index + 1, type);
924 }
925
926 free(mbstr);
927 }
928 }
929
930 /* Converts command-line sub-mode to type of command input which supports
931 * editing. Returns (CmdInputType)-1 when there is no appropriate command
932 * type. */
933 static CmdInputType
cls_to_editable_cit(CmdLineSubmode sub_mode)934 cls_to_editable_cit(CmdLineSubmode sub_mode)
935 {
936 switch(sub_mode)
937 {
938 case CLS_COMMAND: return CIT_COMMAND;
939 case CLS_FSEARCH: return CIT_FSEARCH_PATTERN;
940 case CLS_BSEARCH: return CIT_BSEARCH_PATTERN;
941 case CLS_VFSEARCH: return CIT_VFSEARCH_PATTERN;
942 case CLS_VBSEARCH: return CIT_VBSEARCH_PATTERN;
943 case CLS_FILTER: return CIT_FILTER_PATTERN;
944
945 default:
946 return (CmdInputType)-1;
947 }
948 }
949
950 /* Queries prompt input using external editor. */
951 static void
extedit_prompt(const char input[],int cursor_col)952 extedit_prompt(const char input[], int cursor_col)
953 {
954 char *const ext_cmd = get_ext_command(input, cursor_col, CIT_PROMPT_INPUT);
955
956 if(ext_cmd != NULL)
957 {
958 finish_prompt_submode(ext_cmd);
959 }
960 else
961 {
962 hists_prompt_save(input);
963
964 ui_sb_err("Error querying data from external source.");
965 curr_stats.save_msg = 1;
966 }
967
968 free(ext_cmd);
969 }
970
971 /* Expands abbreviation to the left of current cursor position, if any
972 * present. */
973 static void
cmd_ctrl_rb(key_info_t key_info,keys_info_t * keys_info)974 cmd_ctrl_rb(key_info_t key_info, keys_info_t *keys_info)
975 {
976 expand_abbrev();
977 }
978
979 /* Handles backspace. */
980 static void
cmd_ctrl_h(key_info_t key_info,keys_info_t * keys_info)981 cmd_ctrl_h(key_info_t key_info, keys_info_t *keys_info)
982 {
983 input_stat.history_search = HIST_NONE;
984 stop_completion();
985
986 if(should_quit_on_backspace())
987 {
988 cmd_ctrl_c(key_info, keys_info);
989 return;
990 }
991
992 if(input_stat.index == 0)
993 {
994 return;
995 }
996
997 input_stat.index--;
998 input_stat.len--;
999
1000 input_stat.curs_pos -= vifm_wcwidth(input_stat.line[input_stat.index]);
1001
1002 if(input_stat.index == input_stat.len)
1003 {
1004 input_stat.line[input_stat.index] = L'\0';
1005 }
1006 else
1007 {
1008 wcsdel(input_stat.line, input_stat.index + 1, 1);
1009 }
1010
1011 update_cmdline_text(&input_stat);
1012 }
1013
1014 /* Checks whether backspace key pressed in current state should quit
1015 * command-line mode. Returns non-zero if so, otherwise zero is returned. */
1016 static int
should_quit_on_backspace(void)1017 should_quit_on_backspace(void)
1018 {
1019 return input_stat.index == 0
1020 && input_stat.len == 0
1021 && sub_mode != CLS_PROMPT
1022 && (sub_mode != CLS_FILTER || no_initial_line());
1023 }
1024
1025 /* Checks whether initial line was empty. Returns non-zero if so, otherwise
1026 * non-zero is returned. */
1027 static int
no_initial_line(void)1028 no_initial_line(void)
1029 {
1030 return input_stat.initial_line[0] == L'\0';
1031 }
1032
1033 static void
cmd_ctrl_i(key_info_t key_info,keys_info_t * keys_info)1034 cmd_ctrl_i(key_info_t key_info, keys_info_t *keys_info)
1035 {
1036 if(!input_stat.complete_continue)
1037 draw_wild_menu(1);
1038 input_stat.reverse_completion = 0;
1039
1040 if(input_stat.complete_continue && vle_compl_get_count() == 2)
1041 input_stat.complete_continue = 0;
1042
1043 do_completion();
1044 if(cfg.wild_menu)
1045 draw_wild_menu(0);
1046 }
1047
1048 static void
cmd_shift_tab(key_info_t key_info,keys_info_t * keys_info)1049 cmd_shift_tab(key_info_t key_info, keys_info_t *keys_info)
1050 {
1051 if(!input_stat.complete_continue)
1052 draw_wild_menu(1);
1053 input_stat.reverse_completion = 1;
1054
1055 if(input_stat.complete_continue && vle_compl_get_count() == 2)
1056 input_stat.complete_continue = 0;
1057
1058 do_completion();
1059 if(cfg.wild_menu)
1060 draw_wild_menu(0);
1061 }
1062
1063 static void
do_completion(void)1064 do_completion(void)
1065 {
1066 if(input_stat.complete == NULL)
1067 {
1068 return;
1069 }
1070
1071 line_completion(&input_stat);
1072
1073 update_cmdline_size();
1074 update_cmdline_text(&input_stat);
1075 }
1076
1077 /*
1078 * op == 0 - draw
1079 * op < 0 - redraw
1080 * op > 0 - reset
1081 */
1082 static void
draw_wild_menu(int op)1083 draw_wild_menu(int op)
1084 {
1085 static int last_pos;
1086
1087 int pos = vle_compl_get_pos();
1088 const int count = vle_compl_get_count() - 1;
1089 int i;
1090 int len = getmaxx(stdscr);
1091
1092 /* This check should go first to ensure that resetting of wild menu is
1093 * processed and no returns will break the expected behaviour. */
1094 if(op > 0)
1095 {
1096 last_pos = 0;
1097 return;
1098 }
1099
1100 if(input_stat.complete == NULL || count < 2)
1101 {
1102 return;
1103 }
1104
1105 if(pos == 0 || pos == count)
1106 last_pos = 0;
1107 if(last_pos == 0 && pos == count - 1)
1108 last_pos = count;
1109
1110 i = cfg.wild_popup
1111 ? draw_wild_popup(&last_pos, &pos, &len)
1112 : draw_wild_bar(&last_pos, &pos, &len);
1113
1114 if(pos > 0 && pos != count)
1115 {
1116 last_pos = pos;
1117 draw_wild_menu(op);
1118 return;
1119 }
1120 if(op == 0 && len < 2 && i - 1 == pos)
1121 last_pos = i;
1122 ui_refresh_win(stat_win);
1123
1124 update_cursor();
1125 }
1126
1127 /* Draws wild menu as a bar. Returns index of the last displayed item. */
1128 static int
draw_wild_bar(int * last_pos,int * pos,int * len)1129 draw_wild_bar(int *last_pos, int *pos, int *len)
1130 {
1131 int i;
1132
1133 const vle_compl_t *const items = vle_compl_get_items();
1134 const int count = vle_compl_get_count() - 1;
1135
1136 checked_wmove(stat_win, 0, 0);
1137 werase(stat_win);
1138
1139 if(*pos < *last_pos)
1140 {
1141 int l = *len;
1142 while(*last_pos > 0 && l > 2)
1143 {
1144 --*last_pos;
1145 l -= strlen(items[*last_pos].text);
1146 if(*last_pos != 0)
1147 l -= 2;
1148 }
1149 if(l < 2)
1150 ++*last_pos;
1151 }
1152
1153 for(i = *last_pos; i < count && *len > 0; ++i)
1154 {
1155 *len -= strlen(items[i].text);
1156 if(i != 0)
1157 *len -= 2;
1158
1159 if(i == *last_pos && *last_pos > 0)
1160 {
1161 wprintw(stat_win, "< ");
1162 }
1163 else if(i > *last_pos)
1164 {
1165 if(*len < 2)
1166 {
1167 wprintw(stat_win, " >");
1168 break;
1169 }
1170 wprintw(stat_win, " ");
1171 }
1172
1173 if(i == *pos)
1174 {
1175 col_attr_t col = cfg.cs.color[STATUS_LINE_COLOR];
1176 cs_mix_colors(&col, &cfg.cs.color[WILD_MENU_COLOR]);
1177 ui_set_attr(stat_win, &col, -1);
1178 }
1179 wprint(stat_win, items[i].text);
1180 if(i == *pos)
1181 {
1182 ui_set_attr(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
1183 cfg.cs.pair[STATUS_LINE_COLOR]);
1184 *pos = -*pos;
1185 }
1186 }
1187
1188 return i;
1189 }
1190
1191 /* Draws wild menu as a popup. Returns index of the last displayed item. */
1192 static int
draw_wild_popup(int * last_pos,int * pos,int * len)1193 draw_wild_popup(int *last_pos, int *pos, int *len)
1194 {
1195 const vle_compl_t *const items = vle_compl_get_items();
1196 const int count = vle_compl_get_count() - 1;
1197 const int max_height = getmaxy(stdscr) - getmaxy(status_bar)
1198 - ui_stat_job_bar_height() - 1;
1199 const int height = MIN(count, MIN(10, max_height));
1200 size_t max_title_width;
1201 int i, j;
1202
1203 if(*pos < *last_pos)
1204 {
1205 *last_pos = MAX(0, *last_pos - height);
1206 }
1207
1208 wresize(stat_win, height, getmaxx(stdscr));
1209 werase(stat_win);
1210 ui_stat_reposition(getmaxy(status_bar), 1);
1211
1212 max_title_width = 0U;
1213 for(i = *last_pos, j = 0; i < count && j < height; ++i, ++j)
1214 {
1215 const size_t width = utf8_strsw(items[i].text);
1216 if(width > max_title_width)
1217 {
1218 max_title_width = width;
1219 }
1220 }
1221
1222 for(i = *last_pos, j = 0; i < count && j < height; ++i, ++j)
1223 {
1224 if(i == *pos)
1225 {
1226 col_attr_t col = cfg.cs.color[STATUS_LINE_COLOR];
1227 cs_mix_colors(&col, &cfg.cs.color[WILD_MENU_COLOR]);
1228 ui_set_attr(stat_win, &col, -1);
1229 }
1230
1231 checked_wmove(stat_win, j, 0);
1232 ui_stat_draw_popup_line(stat_win, items[i].text, items[i].descr,
1233 max_title_width);
1234
1235 if(i == *pos)
1236 {
1237 ui_set_attr(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
1238 cfg.cs.pair[STATUS_LINE_COLOR]);
1239 *pos = -*pos;
1240 }
1241 }
1242
1243 return i;
1244 }
1245
1246 static void
cmd_ctrl_k(key_info_t key_info,keys_info_t * keys_info)1247 cmd_ctrl_k(key_info_t key_info, keys_info_t *keys_info)
1248 {
1249 input_stat.history_search = HIST_NONE;
1250 stop_completion();
1251
1252 if(input_stat.index == input_stat.len)
1253 return;
1254
1255 wcsdel(input_stat.line, input_stat.index + 1,
1256 input_stat.len - input_stat.index);
1257 input_stat.len = input_stat.index;
1258
1259 update_cmdline_text(&input_stat);
1260 }
1261
1262 static void
cmd_return(key_info_t key_info,keys_info_t * keys_info)1263 cmd_return(key_info_t key_info, keys_info_t *keys_info)
1264 {
1265 /* TODO: refactor this cmd_return() function. */
1266 char *input;
1267
1268 stop_completion();
1269 werase(status_bar);
1270 wnoutrefresh(status_bar);
1271
1272 if(is_input_line_empty() && sub_mode == CLS_MENU_COMMAND)
1273 {
1274 leave_cmdline_mode();
1275 return;
1276 }
1277
1278 expand_abbrev();
1279
1280 input = to_multibyte(input_stat.line);
1281
1282 leave_cmdline_mode();
1283
1284 if(prev_mode == VISUAL_MODE && sub_mode != CLS_VFSEARCH &&
1285 sub_mode != CLS_VBSEARCH)
1286 {
1287 modvis_leave(curr_stats.save_msg, 1, 0);
1288 }
1289
1290 save_input_to_history(keys_info, input);
1291
1292 if(sub_mode == CLS_COMMAND || sub_mode == CLS_MENU_COMMAND)
1293 {
1294 const CmdInputType cmd_type = (sub_mode == CLS_COMMAND)
1295 ? CIT_COMMAND
1296 : CIT_MENU_COMMAND;
1297
1298 const char *real_start = input;
1299 while(*real_start == ' ' || *real_start == ':')
1300 {
1301 real_start++;
1302 }
1303
1304 if(sub_mode == CLS_COMMAND)
1305 {
1306 commands_scope_start();
1307 }
1308 curr_stats.save_msg = exec_commands(real_start, curr_view, cmd_type);
1309 if(sub_mode == CLS_COMMAND)
1310 {
1311 if(commands_scope_finish() != 0)
1312 {
1313 curr_stats.save_msg = 1;
1314 }
1315 }
1316 }
1317 else if(sub_mode == CLS_PROMPT)
1318 {
1319 finish_prompt_submode(input);
1320 }
1321 else if(sub_mode == CLS_FILTER)
1322 {
1323 if(cfg.inc_search)
1324 {
1325 local_filter_accept(curr_view);
1326 }
1327 else
1328 {
1329 local_filter_apply(curr_view, input);
1330 ui_view_schedule_reload(curr_view);
1331 }
1332 }
1333 else if(!cfg.inc_search || prev_mode == VIEW_MODE || input[0] == '\0')
1334 {
1335 const char *const pattern = (input[0] == '\0') ? hists_search_last()
1336 : input;
1337
1338 switch(sub_mode)
1339 {
1340 case CLS_FSEARCH:
1341 case CLS_BSEARCH:
1342 case CLS_VFSEARCH:
1343 case CLS_VBSEARCH:
1344 case CLS_VWFSEARCH:
1345 case CLS_VWBSEARCH:
1346 {
1347 const CmdInputType cit = search_cls_to_cit(sub_mode);
1348 curr_stats.save_msg = exec_command(pattern, curr_view, cit);
1349 break;
1350 }
1351 case CLS_MENU_FSEARCH:
1352 case CLS_MENU_BSEARCH:
1353 stats_refresh_later();
1354 curr_stats.save_msg = menus_search(pattern, sub_mode_ptr, 1);
1355 break;
1356
1357 default:
1358 assert(0 && "Unknown command line submode.");
1359 break;
1360 }
1361 }
1362 else if(cfg.inc_search && input_stat.search_mode)
1363 {
1364 if(prev_mode == MENU_MODE)
1365 {
1366 menus_search_print_msg(sub_mode_ptr);
1367 curr_stats.save_msg = 1;
1368 }
1369 else
1370 {
1371 /* In case of successful search and 'hlsearch' option set, a message like
1372 * "n files selected" is printed automatically. */
1373 if(curr_view->matches == 0 || !cfg.hl_search)
1374 {
1375 print_search_msg(curr_view, is_backward_search(sub_mode));
1376 curr_stats.save_msg = 1;
1377 }
1378 }
1379 }
1380
1381 free(input);
1382 }
1383
1384 /* Checks whether input line is empty. Returns non-zero if so, otherwise
1385 * non-zero is returned. */
1386 static int
is_input_line_empty(void)1387 is_input_line_empty(void)
1388 {
1389 return input_stat.line[0] == L'\0';
1390 }
1391
1392 /* Expands abbreviation to the left of current cursor position, if any
1393 * present, otherwise does nothing. */
1394 static void
expand_abbrev(void)1395 expand_abbrev(void)
1396 {
1397 int pos;
1398 const wchar_t *abbrev_rhs;
1399 int no_remap;
1400
1401 /* No recursion on expanding abbreviations. */
1402 if(input_stat.expanding_abbrev)
1403 {
1404 return;
1405 }
1406
1407 abbrev_rhs = extract_abbrev(&input_stat, &pos, &no_remap);
1408 if(abbrev_rhs != NULL)
1409 {
1410 input_stat.expanding_abbrev = 1;
1411 exec_abbrev(abbrev_rhs, no_remap, pos);
1412 input_stat.expanding_abbrev = 0;
1413
1414 update_cmdline_size();
1415 update_cmdline_text(&input_stat);
1416 }
1417 }
1418
1419 /* Lookups for an abbreviation to the left of cursor position. Returns RHS for
1420 * the abbreviation and fills pointer arguments if found, otherwise NULL is
1421 * returned. */
1422 TSTATIC const wchar_t *
extract_abbrev(line_stats_t * stat,int * pos,int * no_remap)1423 extract_abbrev(line_stats_t *stat, int *pos, int *no_remap)
1424 {
1425 const int i = stat->index;
1426 wchar_t *const line = stat->line;
1427
1428 int l;
1429 wchar_t c;
1430 const wchar_t *abbrev_rhs;
1431
1432 l = i;
1433 while(l > 0 && cfg_is_word_wchar(line[l - 1]))
1434 {
1435 --l;
1436 }
1437
1438 if(l == i)
1439 {
1440 return NULL;
1441 }
1442
1443 c = line[i];
1444 line[i] = L'\0';
1445 abbrev_rhs = vle_abbr_expand(&line[l], no_remap);
1446 line[i] = c;
1447
1448 *pos = l;
1449
1450 return abbrev_rhs;
1451 }
1452
1453 /* Runs RHS of an abbreviation after removing LHS that starts at the pos. */
1454 static void
exec_abbrev(const wchar_t abbrev_rhs[],int no_remap,int pos)1455 exec_abbrev(const wchar_t abbrev_rhs[], int no_remap, int pos)
1456 {
1457 const size_t lhs_len = input_stat.index - pos;
1458
1459 input_stat.len -= lhs_len;
1460 input_stat.index -= lhs_len;
1461 input_stat.curs_pos -= vifm_wcswidth(&input_stat.line[pos], lhs_len);
1462 (void)wcsdel(input_stat.line, pos + 1, lhs_len);
1463
1464 if(no_remap)
1465 {
1466 (void)vle_keys_exec_timed_out_no_remap(abbrev_rhs);
1467 }
1468 else
1469 {
1470 (void)vle_keys_exec_timed_out(abbrev_rhs);
1471 }
1472 }
1473
1474 /* Saves command-line input into appropriate history. input can be NULL, in
1475 * which case it's ignored. */
1476 static void
save_input_to_history(const keys_info_t * keys_info,const char input[])1477 save_input_to_history(const keys_info_t *keys_info, const char input[])
1478 {
1479 if(input_stat.search_mode)
1480 {
1481 hists_search_save(input);
1482 }
1483 else if(sub_mode == CLS_COMMAND)
1484 {
1485 const int mapped_input = input_stat.enter_mapping_state != 0 &&
1486 vle_keys_mapping_state() == input_stat.enter_mapping_state;
1487 const int ignore_input = mapped_input || keys_info->recursive;
1488 if(!ignore_input)
1489 {
1490 hists_commands_save(input);
1491 }
1492 }
1493 else if(sub_mode == CLS_PROMPT)
1494 {
1495 hists_prompt_save(input);
1496 }
1497 }
1498
1499 /* Performs final actions on successful querying of prompt input. */
1500 static void
finish_prompt_submode(const char input[])1501 finish_prompt_submode(const char input[])
1502 {
1503 const prompt_cb cb = (prompt_cb)sub_mode_ptr;
1504
1505 modes_post();
1506 modes_pre();
1507
1508 cb(input);
1509 }
1510
1511 /* Converts search command-line sub-mode to type of command input. Returns
1512 * (CmdInputType)-1 if there is no appropriate command type. */
1513 static CmdInputType
search_cls_to_cit(CmdLineSubmode sub_mode)1514 search_cls_to_cit(CmdLineSubmode sub_mode)
1515 {
1516 switch(sub_mode)
1517 {
1518 case CLS_FSEARCH: return CIT_FSEARCH_PATTERN;
1519 case CLS_BSEARCH: return CIT_BSEARCH_PATTERN;
1520 case CLS_VFSEARCH: return CIT_VFSEARCH_PATTERN;
1521 case CLS_VBSEARCH: return CIT_VBSEARCH_PATTERN;
1522 case CLS_VWFSEARCH: return CIT_VWFSEARCH_PATTERN;
1523 case CLS_VWBSEARCH: return CIT_VWBSEARCH_PATTERN;
1524
1525 default:
1526 assert(0 && "Unknown search command-line submode.");
1527 return (CmdInputType)-1;
1528 }
1529 }
1530
1531 /* Checks whether specified mode is one of forward searching modes. Returns
1532 * non-zero if it is, otherwise zero is returned. */
1533 static int
is_forward_search(CmdLineSubmode sub_mode)1534 is_forward_search(CmdLineSubmode sub_mode)
1535 {
1536 return sub_mode == CLS_FSEARCH
1537 || sub_mode == CLS_VFSEARCH
1538 || sub_mode == CLS_MENU_FSEARCH
1539 || sub_mode == CLS_VWFSEARCH;
1540 }
1541
1542 /* Checks whether specified mode is one of backward searching modes. Returns
1543 * non-zero if it is, otherwise zero is returned. */
1544 static int
is_backward_search(CmdLineSubmode sub_mode)1545 is_backward_search(CmdLineSubmode sub_mode)
1546 {
1547 return sub_mode == CLS_BSEARCH
1548 || sub_mode == CLS_VBSEARCH
1549 || sub_mode == CLS_MENU_BSEARCH
1550 || sub_mode == CLS_VWBSEARCH;
1551 }
1552
1553 static void
save_users_input(void)1554 save_users_input(void)
1555 {
1556 (void)replace_wstring(&input_stat.line_buf, input_stat.line);
1557 }
1558
1559 static void
restore_user_input(void)1560 restore_user_input(void)
1561 {
1562 input_stat.cmd_pos = -1;
1563 (void)replace_wstring(&input_stat.line, input_stat.line_buf);
1564 input_stat.len = wcslen(input_stat.line);
1565 update_cmdline(&input_stat);
1566 }
1567
1568 /* Replaces *str with a copy of the with string. *str can be NULL or equal to
1569 * the with (then function does nothing). Returns non-zero if memory allocation
1570 * failed. */
1571 static int
replace_wstring(wchar_t ** str,const wchar_t with[])1572 replace_wstring(wchar_t **str, const wchar_t with[])
1573 {
1574 if(*str != with)
1575 {
1576 wchar_t *const new = vifm_wcsdup(with);
1577 if(new == NULL)
1578 {
1579 return 1;
1580 }
1581 free(*str);
1582 *str = new;
1583 }
1584 return 0;
1585 }
1586
1587 static void
cmd_ctrl_n(key_info_t key_info,keys_info_t * keys_info)1588 cmd_ctrl_n(key_info_t key_info, keys_info_t *keys_info)
1589 {
1590 stop_completion();
1591
1592 if(input_stat.history_search == HIST_NONE)
1593 save_users_input();
1594
1595 input_stat.history_search = HIST_GO;
1596
1597 hist_next(&input_stat, pick_hist(), cfg.history_len);
1598 }
1599
1600 #ifdef ENABLE_EXTENDED_KEYS
1601 static void
cmd_down(key_info_t key_info,keys_info_t * keys_info)1602 cmd_down(key_info_t key_info, keys_info_t *keys_info)
1603 {
1604 stop_completion();
1605
1606 if(input_stat.history_search == HIST_NONE)
1607 save_users_input();
1608
1609 if(input_stat.history_search != HIST_SEARCH)
1610 {
1611 input_stat.history_search = HIST_SEARCH;
1612 input_stat.hist_search_len = input_stat.len;
1613 }
1614
1615 hist_next(&input_stat, pick_hist(), cfg.history_len);
1616 }
1617 #endif /* ENABLE_EXTENDED_KEYS */
1618
1619 /* Puts next element in the history or restores user input if end of history has
1620 * been reached. hist can be NULL, in which case nothing happens. */
1621 static void
hist_next(line_stats_t * stat,const hist_t * hist,size_t len)1622 hist_next(line_stats_t *stat, const hist_t *hist, size_t len)
1623 {
1624 if(hist == NULL || hist_is_empty(hist))
1625 {
1626 return;
1627 }
1628
1629 if(stat->history_search != HIST_SEARCH)
1630 {
1631 if(stat->cmd_pos <= 0)
1632 {
1633 restore_user_input();
1634 return;
1635 }
1636 --stat->cmd_pos;
1637 }
1638 else
1639 {
1640 int pos = stat->cmd_pos;
1641 int len = stat->hist_search_len;
1642 while(--pos >= 0)
1643 {
1644 wchar_t *const buf = to_wide(hist->items[pos].text);
1645 if(wcsncmp(stat->line, buf, len) == 0)
1646 {
1647 free(buf);
1648 break;
1649 }
1650 free(buf);
1651 }
1652 if(pos < 0)
1653 {
1654 restore_user_input();
1655 return;
1656 }
1657 stat->cmd_pos = pos;
1658 }
1659
1660 (void)replace_input_line(stat, hist->items[stat->cmd_pos].text);
1661
1662 update_cmdline(stat);
1663
1664 if(stat->cmd_pos > (int)len - 1)
1665 {
1666 stat->cmd_pos = len - 1;
1667 }
1668 }
1669
1670 static void
cmd_ctrl_u(key_info_t key_info,keys_info_t * keys_info)1671 cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info)
1672 {
1673 input_stat.history_search = HIST_NONE;
1674 stop_completion();
1675
1676 if(input_stat.index == 0)
1677 return;
1678
1679 input_stat.len -= input_stat.index;
1680
1681 input_stat.curs_pos = input_stat.prompt_wid;
1682 wcsdel(input_stat.line, 1, input_stat.index);
1683
1684 input_stat.index = 0;
1685
1686 werase(status_bar);
1687 compat_mvwaddwstr(status_bar, 0, 0, input_stat.prompt);
1688 compat_mvwaddwstr(status_bar, 0, input_stat.prompt_wid, input_stat.line);
1689
1690 update_cmdline_text(&input_stat);
1691 }
1692
1693 /* Handler of Ctrl-W shortcut, which remove all to the left until beginning of
1694 * the word is found (i.e. until first delimiter character). */
1695 static void
cmd_ctrl_w(key_info_t key_info,keys_info_t * keys_info)1696 cmd_ctrl_w(key_info_t key_info, keys_info_t *keys_info)
1697 {
1698 int old;
1699
1700 input_stat.history_search = HIST_NONE;
1701 stop_completion();
1702
1703 old = input_stat.index;
1704
1705 while(input_stat.index > 0 && iswspace(input_stat.line[input_stat.index - 1]))
1706 {
1707 input_stat.curs_pos -= vifm_wcwidth(input_stat.line[input_stat.index - 1]);
1708 input_stat.index--;
1709 }
1710 if(iswalnum(input_stat.line[input_stat.index - 1]))
1711 {
1712 while(input_stat.index > 0 &&
1713 iswalnum(input_stat.line[input_stat.index - 1]))
1714 {
1715 const wchar_t curr_wchar = input_stat.line[input_stat.index - 1];
1716 input_stat.curs_pos -= vifm_wcwidth(curr_wchar);
1717 input_stat.index--;
1718 }
1719 }
1720 else
1721 {
1722 while(input_stat.index > 0 &&
1723 !iswalnum(input_stat.line[input_stat.index - 1]) &&
1724 !iswspace(input_stat.line[input_stat.index - 1]))
1725 {
1726 const wchar_t curr_wchar = input_stat.line[input_stat.index - 1];
1727 input_stat.curs_pos -= vifm_wcwidth(curr_wchar);
1728 input_stat.index--;
1729 }
1730 }
1731
1732 if(input_stat.index != old)
1733 {
1734 wcsdel(input_stat.line, input_stat.index + 1, old - input_stat.index);
1735 input_stat.len -= old - input_stat.index;
1736 }
1737
1738 update_cmdline_text(&input_stat);
1739 }
1740
1741 /* Inserts last pattern from search history into current cursor position. */
1742 static void
cmd_ctrl_xslash(key_info_t key_info,keys_info_t * keys_info)1743 cmd_ctrl_xslash(key_info_t key_info, keys_info_t *keys_info)
1744 {
1745 paste_str(hists_search_last(), 0);
1746 }
1747
1748 /* Inserts value of automatic filter of active pane into current cursor
1749 * position. */
1750 static void
cmd_ctrl_xa(key_info_t key_info,keys_info_t * keys_info)1751 cmd_ctrl_xa(key_info_t key_info, keys_info_t *keys_info)
1752 {
1753 paste_str(curr_view->auto_filter.raw, 0);
1754 }
1755
1756 /* Inserts name of the current file of active pane into current cursor
1757 * position. */
1758 static void
cmd_ctrl_xc(key_info_t key_info,keys_info_t * keys_info)1759 cmd_ctrl_xc(key_info_t key_info, keys_info_t *keys_info)
1760 {
1761 paste_short_path(curr_view);
1762 }
1763
1764 /* Inserts name of the current file of inactive pane into current cursor
1765 * position. */
1766 static void
cmd_ctrl_xxc(key_info_t key_info,keys_info_t * keys_info)1767 cmd_ctrl_xxc(key_info_t key_info, keys_info_t *keys_info)
1768 {
1769 paste_short_path(other_view);
1770 }
1771
1772 /* Pastes short path of the current entry of the view into current cursor
1773 * position. */
1774 static void
paste_short_path(view_t * view)1775 paste_short_path(view_t *view)
1776 {
1777 if(flist_custom_active(view))
1778 {
1779 char short_path[PATH_MAX + 1];
1780 get_short_path_of(view, get_current_entry(view), NF_NONE, 0,
1781 sizeof(short_path), short_path);
1782 paste_str(short_path, 1);
1783 return;
1784 }
1785
1786 paste_str(get_current_file_name(view), 1);
1787 }
1788
1789 /* Inserts path to the current directory of active pane into current cursor
1790 * position. */
1791 static void
cmd_ctrl_xd(key_info_t key_info,keys_info_t * keys_info)1792 cmd_ctrl_xd(key_info_t key_info, keys_info_t *keys_info)
1793 {
1794 paste_str(flist_get_dir(curr_view), 1);
1795 }
1796
1797 /* Inserts path to the current directory of inactive pane into current cursor
1798 * position. */
1799 static void
cmd_ctrl_xxd(key_info_t key_info,keys_info_t * keys_info)1800 cmd_ctrl_xxd(key_info_t key_info, keys_info_t *keys_info)
1801 {
1802 paste_str(flist_get_dir(other_view), 1);
1803 }
1804
1805 /* Inserts extension of the current file of active pane into current cursor
1806 * position. */
1807 static void
cmd_ctrl_xe(key_info_t key_info,keys_info_t * keys_info)1808 cmd_ctrl_xe(key_info_t key_info, keys_info_t *keys_info)
1809 {
1810 paste_name_part(get_current_file_name(curr_view), 0);
1811 }
1812
1813 /* Inserts extension of the current file of inactive pane into current cursor
1814 * position. */
1815 static void
cmd_ctrl_xxe(key_info_t key_info,keys_info_t * keys_info)1816 cmd_ctrl_xxe(key_info_t key_info, keys_info_t *keys_info)
1817 {
1818 paste_name_part(get_current_file_name(other_view), 0);
1819 }
1820
1821 /* Inserts value of manual filter of active pane into current cursor
1822 * position. */
1823 static void
cmd_ctrl_xm(key_info_t key_info,keys_info_t * keys_info)1824 cmd_ctrl_xm(key_info_t key_info, keys_info_t *keys_info)
1825 {
1826 paste_str(matcher_get_undec(curr_view->manual_filter), 0);
1827 }
1828
1829 /* Inserts name root of current file of active pane into current cursor
1830 * position. */
1831 static void
cmd_ctrl_xr(key_info_t key_info,keys_info_t * keys_info)1832 cmd_ctrl_xr(key_info_t key_info, keys_info_t *keys_info)
1833 {
1834 paste_short_path_root(curr_view);
1835 }
1836
1837 /* Inserts name root of current file of inactive pane into current cursor
1838 * position. */
1839 static void
cmd_ctrl_xxr(key_info_t key_info,keys_info_t * keys_info)1840 cmd_ctrl_xxr(key_info_t key_info, keys_info_t *keys_info)
1841 {
1842 paste_short_path_root(other_view);
1843 }
1844
1845 /* Pastes short root of the current entry of the view into current cursor
1846 * position. */
1847 static void
paste_short_path_root(view_t * view)1848 paste_short_path_root(view_t *view)
1849 {
1850 char short_path[PATH_MAX + 1];
1851 get_short_path_of(view, get_current_entry(view), NF_NONE, 0,
1852 sizeof(short_path), short_path);
1853 paste_name_part(short_path, 1);
1854 }
1855
1856 /* Inserts last component of path to the current directory of active pane into
1857 * current cursor position. */
1858 static void
cmd_ctrl_xt(key_info_t key_info,keys_info_t * keys_info)1859 cmd_ctrl_xt(key_info_t key_info, keys_info_t *keys_info)
1860 {
1861 paste_str(get_last_path_component(flist_get_dir(curr_view)), 1);
1862 }
1863
1864 /* Inserts last component of path to the current directory of inactive pane into
1865 * current cursor position. */
1866 static void
cmd_ctrl_xxt(key_info_t key_info,keys_info_t * keys_info)1867 cmd_ctrl_xxt(key_info_t key_info, keys_info_t *keys_info)
1868 {
1869 paste_str(get_last_path_component(flist_get_dir(other_view)), 1);
1870 }
1871
1872 /* Inserts value of local filter of active pane into current cursor position. */
1873 static void
cmd_ctrl_xequals(key_info_t key_info,keys_info_t * keys_info)1874 cmd_ctrl_xequals(key_info_t key_info, keys_info_t *keys_info)
1875 {
1876 paste_str(curr_view->local_filter.filter.raw, 0);
1877 }
1878
1879 /* Inserts root/extension of the name file into current cursor position. */
1880 static void
paste_name_part(const char name[],int root)1881 paste_name_part(const char name[], int root)
1882 {
1883 int root_len;
1884 const char *ext;
1885 char *const name_copy = strdup(name);
1886
1887 split_ext(name_copy, &root_len, &ext);
1888 paste_str(root ? name_copy : ext, 1);
1889
1890 free(name_copy);
1891 }
1892
1893 /* Inserts string into current cursor position and updates command-line on the
1894 * screen. */
1895 static void
paste_str(const char str[],int allow_escaping)1896 paste_str(const char str[], int allow_escaping)
1897 {
1898 char *escaped;
1899 wchar_t *wide;
1900
1901 if(prev_mode == MENU_MODE)
1902 {
1903 return;
1904 }
1905
1906 stop_completion();
1907
1908 escaped = (allow_escaping && sub_mode == CLS_COMMAND)
1909 ? escape_cmd_for_pasting(str)
1910 : NULL;
1911 if(escaped != NULL)
1912 {
1913 str = escaped;
1914 }
1915
1916 wide = to_wide_force(str);
1917
1918 if(insert_str((str[0] != '\0' && wide[0] == L'\0') ? L"?" : wide) == 0)
1919 {
1920 update_cmdline_text(&input_stat);
1921 }
1922
1923 free(wide);
1924 free(escaped);
1925 }
1926
1927 /* Escapes str for inserting into current position of command-line command when
1928 * needed. Returns escaped string or NULL when no escaping is needed. */
1929 static char *
escape_cmd_for_pasting(const char str[])1930 escape_cmd_for_pasting(const char str[])
1931 {
1932 wchar_t *const wide_input = vifm_wcsdup(input_stat.line);
1933 char *mb_input;
1934 char *escaped;
1935
1936 wide_input[input_stat.index] = L'\0';
1937 mb_input = to_multibyte(wide_input);
1938
1939 escaped = commands_escape_for_insertion(mb_input, strlen(mb_input), str);
1940
1941 free(mb_input);
1942 free(wide_input);
1943
1944 return escaped;
1945 }
1946
1947 static void
cmd_ctrl_underscore(key_info_t key_info,keys_info_t * keys_info)1948 cmd_ctrl_underscore(key_info_t key_info, keys_info_t *keys_info)
1949 {
1950 if(!input_stat.complete_continue)
1951 {
1952 return;
1953 }
1954
1955 /* Restore initial user input (before completion). */
1956 vle_compl_rewind();
1957 input_stat.reverse_completion = 0;
1958 if(input_stat.complete != NULL)
1959 {
1960 line_completion(&input_stat);
1961 }
1962
1963 stop_completion();
1964 }
1965
1966 static void
cmd_meta_b(key_info_t key_info,keys_info_t * keys_info)1967 cmd_meta_b(key_info_t key_info, keys_info_t *keys_info)
1968 {
1969 find_prev_word();
1970 update_cursor();
1971 }
1972
1973 /* Moves cursor to the beginning of the current or previous word on Meta+B. */
1974 static void
find_prev_word(void)1975 find_prev_word(void)
1976 {
1977 while(input_stat.index > 0 &&
1978 !cfg_is_word_wchar(input_stat.line[input_stat.index - 1]))
1979 {
1980 input_stat.curs_pos -= vifm_wcwidth(input_stat.line[input_stat.index - 1]);
1981 input_stat.index--;
1982 }
1983 while(input_stat.index > 0 &&
1984 cfg_is_word_wchar(input_stat.line[input_stat.index - 1]))
1985 {
1986 input_stat.curs_pos -= vifm_wcwidth(input_stat.line[input_stat.index - 1]);
1987 input_stat.index--;
1988 }
1989 }
1990
1991 static void
cmd_meta_d(key_info_t key_info,keys_info_t * keys_info)1992 cmd_meta_d(key_info_t key_info, keys_info_t *keys_info)
1993 {
1994 int old_i, old_c;
1995
1996 old_i = input_stat.index;
1997 old_c = input_stat.curs_pos;
1998 find_next_word();
1999
2000 if(input_stat.index == old_i)
2001 {
2002 return;
2003 }
2004
2005 wcsdel(input_stat.line, old_i + 1, input_stat.index - old_i);
2006 input_stat.len -= input_stat.index - old_i;
2007 input_stat.index = old_i;
2008 input_stat.curs_pos = old_c;
2009
2010 update_cmdline_text(&input_stat);
2011 }
2012
2013 static void
cmd_meta_f(key_info_t key_info,keys_info_t * keys_info)2014 cmd_meta_f(key_info_t key_info, keys_info_t *keys_info)
2015 {
2016 find_next_word();
2017 update_cursor();
2018 }
2019
2020 /* Inserts last part of previous command to current cursor position. Each next
2021 * call will insert last part of older command. */
2022 static void
cmd_meta_dot(key_info_t key_info,keys_info_t * keys_info)2023 cmd_meta_dot(key_info_t key_info, keys_info_t *keys_info)
2024 {
2025 wchar_t *wide;
2026
2027 if(sub_mode != CLS_COMMAND)
2028 {
2029 return;
2030 }
2031
2032 stop_regular_completion();
2033
2034 if(hist_is_empty(&curr_stats.cmd_hist))
2035 {
2036 return;
2037 }
2038
2039 if(input_stat.dot_pos < 0)
2040 {
2041 input_stat.dot_pos = input_stat.cmd_pos + 1;
2042 }
2043 else
2044 {
2045 remove_previous_dot_completion();
2046 }
2047
2048 wide = next_dot_completion();
2049
2050 if(insert_dot_completion(wide) == 0)
2051 {
2052 update_cmdline_text(&input_stat);
2053 }
2054 else
2055 {
2056 stop_dot_completion();
2057 }
2058
2059 free(wide);
2060 }
2061
2062 /* Removes previous dot completion from any part of command line. */
2063 static void
remove_previous_dot_completion(void)2064 remove_previous_dot_completion(void)
2065 {
2066 size_t start = input_stat.dot_index;
2067 size_t end = start + input_stat.dot_len;
2068 memmove(input_stat.line + start, input_stat.line + end,
2069 sizeof(wchar_t)*(input_stat.len - end + 1));
2070 input_stat.len -= input_stat.dot_len;
2071 input_stat.index -= input_stat.dot_len;
2072 input_stat.curs_pos -= input_stat.dot_len;
2073 }
2074
2075 /* Gets next dot completion from history of commands. Returns new string, caller
2076 * should free it. */
2077 static wchar_t *
next_dot_completion(void)2078 next_dot_completion(void)
2079 {
2080 if(input_stat.dot_pos >= curr_stats.cmd_hist.size)
2081 {
2082 return vifm_wcsdup(L"");
2083 }
2084
2085 size_t len;
2086 const char *last_entry = curr_stats.cmd_hist.items[input_stat.dot_pos++].text;
2087 const char *last_arg_pos = vle_cmds_last_arg(last_entry, 1, &len);
2088
2089 char *last_arg = format_str("%.*s", (int)len, last_arg_pos);
2090 wchar_t *wide = to_wide(last_arg);
2091 free(last_arg);
2092
2093 return wide;
2094 }
2095
2096 /* Inserts dot completion into current cursor position in the command line.
2097 * Returns zero on success. */
2098 static int
insert_dot_completion(const wchar_t completion[])2099 insert_dot_completion(const wchar_t completion[])
2100 {
2101 const int dot_index = input_stat.index;
2102 if(insert_str(completion) == 0)
2103 {
2104 input_stat.dot_index = dot_index;
2105 input_stat.dot_len = wcslen(completion);
2106 return 0;
2107 }
2108 return 1;
2109 }
2110
2111 /* Inserts wide string into current cursor position. Returns zero on success,
2112 * otherwise non-zero is returned. */
2113 static int
insert_str(const wchar_t str[])2114 insert_str(const wchar_t str[])
2115 {
2116 const size_t len = wcslen(str);
2117 const size_t new_len = input_stat.len + len + 1;
2118
2119 wchar_t *const ptr = reallocarray(input_stat.line, new_len, sizeof(wchar_t));
2120 if(ptr == NULL)
2121 {
2122 return 1;
2123 }
2124 input_stat.line = ptr;
2125
2126 wcsins(input_stat.line, str, input_stat.index + 1);
2127
2128 input_stat.index += len;
2129 input_stat.curs_pos += vifm_wcswidth(str, (size_t)-1);
2130 input_stat.len += len;
2131
2132 return 0;
2133 }
2134
2135 /* Moves cursor to the end of the current or next word on Meta+F. */
2136 static void
find_next_word(void)2137 find_next_word(void)
2138 {
2139 while(input_stat.index < input_stat.len
2140 && !cfg_is_word_wchar(input_stat.line[input_stat.index]))
2141 {
2142 input_stat.curs_pos += vifm_wcwidth(input_stat.line[input_stat.index]);
2143 input_stat.index++;
2144 }
2145 while(input_stat.index < input_stat.len
2146 && cfg_is_word_wchar(input_stat.line[input_stat.index]))
2147 {
2148 input_stat.curs_pos += vifm_wcwidth(input_stat.line[input_stat.index]);
2149 input_stat.index++;
2150 }
2151 }
2152
2153 static void
cmd_left(key_info_t key_info,keys_info_t * keys_info)2154 cmd_left(key_info_t key_info, keys_info_t *keys_info)
2155 {
2156 input_stat.history_search = HIST_NONE;
2157 stop_completion();
2158
2159 if(input_stat.index > 0)
2160 {
2161 input_stat.index--;
2162 input_stat.curs_pos -= vifm_wcwidth(input_stat.line[input_stat.index]);
2163 update_cursor();
2164 }
2165 }
2166
2167 static void
cmd_right(key_info_t key_info,keys_info_t * keys_info)2168 cmd_right(key_info_t key_info, keys_info_t *keys_info)
2169 {
2170 input_stat.history_search = HIST_NONE;
2171 stop_completion();
2172
2173 if(input_stat.index < input_stat.len)
2174 {
2175 input_stat.curs_pos += vifm_wcwidth(input_stat.line[input_stat.index]);
2176 input_stat.index++;
2177 update_cursor();
2178 }
2179 }
2180
2181 static void
cmd_home(key_info_t key_info,keys_info_t * keys_info)2182 cmd_home(key_info_t key_info, keys_info_t *keys_info)
2183 {
2184 input_stat.index = 0;
2185 input_stat.curs_pos = input_stat.prompt_wid;
2186 update_cursor();
2187 }
2188
2189 /* Moves cursor to the end of command-line on Ctrl+E and End. */
2190 static void
cmd_end(key_info_t key_info,keys_info_t * keys_info)2191 cmd_end(key_info_t key_info, keys_info_t *keys_info)
2192 {
2193 if(input_stat.index == input_stat.len)
2194 return;
2195
2196 input_stat.index = input_stat.len;
2197 input_stat.curs_pos = input_stat.prompt_wid +
2198 vifm_wcswidth(input_stat.line, (size_t)-1);
2199 update_cursor();
2200 }
2201
2202 static void
cmd_delete(key_info_t key_info,keys_info_t * keys_info)2203 cmd_delete(key_info_t key_info, keys_info_t *keys_info)
2204 {
2205 input_stat.history_search = HIST_NONE;
2206 stop_completion();
2207
2208 if(input_stat.index == input_stat.len)
2209 return;
2210
2211 wcsdel(input_stat.line, input_stat.index+1, 1);
2212 input_stat.len--;
2213
2214 update_cmdline_text(&input_stat);
2215 }
2216
2217 static void
update_cursor(void)2218 update_cursor(void)
2219 {
2220 checked_wmove(status_bar, input_stat.curs_pos/line_width,
2221 input_stat.curs_pos%line_width);
2222 }
2223
2224 /* Replaces current input with specified string. Returns zero on success,
2225 * otherwise non-zero is returned. */
2226 static int
replace_input_line(line_stats_t * stat,const char new[])2227 replace_input_line(line_stats_t *stat, const char new[])
2228 {
2229 wchar_t *const wide_new = to_wide(new);
2230 if(wide_new == NULL)
2231 {
2232 return 1;
2233 }
2234
2235 free(stat->line);
2236 stat->line = wide_new;
2237 stat->len = wcslen(wide_new);
2238 return 0;
2239 }
2240
2241 static void
cmd_ctrl_p(key_info_t key_info,keys_info_t * keys_info)2242 cmd_ctrl_p(key_info_t key_info, keys_info_t *keys_info)
2243 {
2244 stop_completion();
2245
2246 if(input_stat.history_search == HIST_NONE)
2247 save_users_input();
2248
2249 input_stat.history_search = HIST_GO;
2250
2251 hist_prev(&input_stat, pick_hist(), cfg.history_len);
2252 }
2253
2254 /* Swaps the order of two characters. */
2255 static void
cmd_ctrl_t(key_info_t key_info,keys_info_t * keys_info)2256 cmd_ctrl_t(key_info_t key_info, keys_info_t *keys_info)
2257 {
2258 wchar_t char_before_last;
2259 int index;
2260
2261 stop_completion();
2262
2263 index = input_stat.index;
2264 if(index == 0 || input_stat.len == 1)
2265 {
2266 return;
2267 }
2268
2269 if(index == input_stat.len)
2270 {
2271 --index;
2272 }
2273 else
2274 {
2275 input_stat.curs_pos += vifm_wcwidth(input_stat.line[input_stat.index]);
2276 ++input_stat.index;
2277 }
2278
2279 char_before_last = input_stat.line[index - 1];
2280 input_stat.line[index - 1] = input_stat.line[index];
2281 input_stat.line[index] = char_before_last;
2282
2283 update_cmdline_text(&input_stat);
2284 }
2285
2286 #ifdef ENABLE_EXTENDED_KEYS
2287 static void
cmd_up(key_info_t key_info,keys_info_t * keys_info)2288 cmd_up(key_info_t key_info, keys_info_t *keys_info)
2289 {
2290 stop_completion();
2291
2292 if(input_stat.history_search == HIST_NONE)
2293 save_users_input();
2294
2295 if(input_stat.history_search != HIST_SEARCH)
2296 {
2297 input_stat.history_search = HIST_SEARCH;
2298 input_stat.hist_search_len = input_stat.len;
2299 }
2300
2301 hist_prev(&input_stat, pick_hist(), cfg.history_len);
2302 }
2303 #endif /* ENABLE_EXTENDED_KEYS */
2304
2305 /* Puts previous element in the history. hist can be NULL, in which case
2306 * nothing happens. */
2307 TSTATIC void
hist_prev(line_stats_t * stat,const hist_t * hist,size_t len)2308 hist_prev(line_stats_t *stat, const hist_t *hist, size_t len)
2309 {
2310 if(hist == NULL || hist_is_empty(hist))
2311 {
2312 return;
2313 }
2314
2315 if(stat->history_search != HIST_SEARCH)
2316 {
2317 if(stat->cmd_pos == hist->size - 1)
2318 {
2319 return;
2320 }
2321
2322 ++stat->cmd_pos;
2323
2324 /* Handle the case when the most recent history item equals current input.
2325 * This can happen if command-line mode was given some initial input
2326 * string. Initially cmd_pos is -1, no need to check anything if history
2327 * contains only one element as even if it's equal input line won't be
2328 * changed. */
2329 if(stat->cmd_pos == 0 && hist->size != 1)
2330 {
2331 wchar_t *const wide_item = to_wide(hist->items[0].text);
2332 if(wcscmp(stat->line, wide_item) == 0)
2333 {
2334 ++stat->cmd_pos;
2335 }
2336 free(wide_item);
2337 }
2338 }
2339 else
2340 {
2341 int pos = stat->cmd_pos;
2342 int len = stat->hist_search_len;
2343 while(++pos < hist->size)
2344 {
2345 wchar_t *const wide_item = to_wide(hist->items[pos].text);
2346 if(wcsncmp(stat->line, wide_item, len) == 0)
2347 {
2348 free(wide_item);
2349 break;
2350 }
2351 free(wide_item);
2352 }
2353 if(pos >= hist->size)
2354 return;
2355 stat->cmd_pos = pos;
2356 }
2357
2358 (void)replace_input_line(stat, hist->items[stat->cmd_pos].text);
2359
2360 update_cmdline(stat);
2361
2362 if(stat->cmd_pos > (int)len - 1)
2363 {
2364 stat->cmd_pos = len - 1;
2365 }
2366 }
2367
2368 /* Picks history depending on sub_mode. Returns picked history or NULL. */
2369 static const hist_t *
pick_hist(void)2370 pick_hist(void)
2371 {
2372 if(sub_mode == CLS_COMMAND)
2373 {
2374 return &curr_stats.cmd_hist;
2375 }
2376 if(input_stat.search_mode)
2377 {
2378 return &curr_stats.search_hist;
2379 }
2380 if(sub_mode == CLS_PROMPT)
2381 {
2382 return &curr_stats.prompt_hist;
2383 }
2384 if(sub_mode == CLS_FILTER)
2385 {
2386 return &curr_stats.filter_hist;
2387 }
2388 return NULL;
2389 }
2390
2391 /* Updates command-line properties and redraws it. */
2392 static void
update_cmdline(line_stats_t * stat)2393 update_cmdline(line_stats_t *stat)
2394 {
2395 int required_height;
2396 stat->curs_pos = stat->prompt_wid + vifm_wcswidth(stat->line, stat->len);
2397 stat->index = stat->len;
2398
2399 required_height = get_required_height();
2400 if(required_height >= getmaxy(status_bar))
2401 {
2402 update_cmdline_size();
2403 }
2404
2405 update_cmdline_text(stat);
2406 }
2407
2408 /* Gets status bar height required to display all its content. Returns the
2409 * height. */
2410 static int
get_required_height(void)2411 get_required_height(void)
2412 {
2413 return DIV_ROUND_UP(input_stat.prompt_wid + input_stat.len + 1, line_width);
2414 }
2415
2416 /* Replaces [ prefix_len : stat->index ) range of stat->line with wide version
2417 * of the completed parameter. Returns zero on success, otherwise non-zero is
2418 * returned. */
2419 static int
line_part_complete(line_stats_t * stat,size_t prefix_len,const char completed[])2420 line_part_complete(line_stats_t *stat, size_t prefix_len,
2421 const char completed[])
2422 {
2423 void *t;
2424 wchar_t *line_ending;
2425 wchar_t *wide_completed;
2426
2427 const size_t new_len = prefix_len + wide_len(completed) +
2428 (stat->len - stat->index) + 1;
2429
2430 line_ending = vifm_wcsdup(stat->line + stat->index);
2431 if(line_ending == NULL)
2432 {
2433 return -1;
2434 }
2435
2436 t = reallocarray(stat->line, new_len, sizeof(wchar_t));
2437 if(t == NULL)
2438 {
2439 free(line_ending);
2440 return -1;
2441 }
2442 stat->line = t;
2443
2444 wide_completed = to_wide(completed);
2445 vifm_swprintf(stat->line + prefix_len, new_len,
2446 L"%" WPRINTF_WSTR L"%" WPRINTF_WSTR, wide_completed, line_ending);
2447 free(wide_completed);
2448 free(line_ending);
2449
2450 update_line_stat(stat, new_len);
2451 update_cmdline_size();
2452 update_cmdline_text(stat);
2453 return 0;
2454 }
2455
2456 /* Make sure status bar size is taken into account in the TUI. */
2457 static void
update_cmdline_size(void)2458 update_cmdline_size(void)
2459 {
2460 const int required_height = MIN(getmaxy(stdscr), get_required_height());
2461 if(required_height < getmaxy(status_bar))
2462 {
2463 /* Do not shrink status bar. */
2464 return;
2465 }
2466
2467 /* This handles case when status bar didn't get larger to cover corner case
2468 * updates (e.g. first redraw). */
2469
2470 mvwin(status_bar, getmaxy(stdscr) - required_height, 0);
2471 wresize(status_bar, required_height, line_width);
2472
2473 if(prev_mode != MENU_MODE)
2474 {
2475 if(ui_stat_reposition(required_height,
2476 cfg.wild_menu && cfg.wild_popup && input_stat.complete_continue))
2477 {
2478 ui_stat_refresh();
2479 }
2480 }
2481 else
2482 {
2483 wresize(menu_win, getmaxy(stdscr) - required_height, getmaxx(stdscr));
2484 modmenu_partial_redraw();
2485 }
2486 }
2487
2488 /* Returns non-zero on error. */
2489 TSTATIC int
line_completion(line_stats_t * stat)2490 line_completion(line_stats_t *stat)
2491 {
2492 static size_t prefix_len;
2493
2494 char *completion;
2495 int result;
2496
2497 if(!stat->complete_continue)
2498 {
2499 wchar_t t;
2500 CompletionPreProcessing compl_func_arg;
2501
2502 /* Only complete the part before the cursor so just copy that part to
2503 * line_mb. */
2504 t = stat->line[stat->index];
2505 stat->line[stat->index] = L'\0';
2506
2507 char *const line_mb = to_multibyte(stat->line);
2508 stat->line[stat->index] = t;
2509 if(line_mb == NULL)
2510 {
2511 return -1;
2512 }
2513
2514 const char *const line_mb_cmd = find_last_command(line_mb);
2515
2516 vle_compl_reset();
2517
2518 compl_func_arg = CPP_NONE;
2519 if(sub_mode == CLS_COMMAND || sub_mode == CLS_MENU_COMMAND)
2520 {
2521 const CmdLineLocation ipt = get_cmdline_location(line_mb,
2522 line_mb + strlen(line_mb));
2523 switch(ipt)
2524 {
2525 case CLL_OUT_OF_ARG:
2526 case CLL_NO_QUOTING:
2527 case CLL_R_QUOTING:
2528 vle_compl_set_add_path_hook(&escaped_arg_hook);
2529 compl_func_arg = CPP_PERCENT_UNESCAPE;
2530 break;
2531
2532 case CLL_S_QUOTING:
2533 vle_compl_set_add_path_hook(&squoted_arg_hook);
2534 compl_func_arg = CPP_SQUOTES_UNESCAPE;
2535 break;
2536
2537 case CLL_D_QUOTING:
2538 vle_compl_set_add_path_hook(&dquoted_arg_hook);
2539 compl_func_arg = CPP_DQUOTES_UNESCAPE;
2540 break;
2541 }
2542 }
2543
2544 const int offset = stat->complete(line_mb_cmd, (void *)compl_func_arg);
2545 line_mb[line_mb_cmd + offset - line_mb] = '\0';
2546 prefix_len = wide_len(line_mb);
2547 free(line_mb);
2548
2549 vle_compl_set_add_path_hook(NULL);
2550 }
2551
2552 vle_compl_set_order(input_stat.reverse_completion);
2553
2554 if(vle_compl_get_count() == 0)
2555 return 0;
2556
2557 completion = vle_compl_next();
2558 result = line_part_complete(stat, prefix_len, completion);
2559 free(completion);
2560
2561 if(vle_compl_get_count() >= 2)
2562 stat->complete_continue = 1;
2563
2564 return result;
2565 }
2566
2567 /* Processes completion match for insertion into command-line as escaped value.
2568 * Returns newly allocated string. */
2569 static char *
escaped_arg_hook(const char match[])2570 escaped_arg_hook(const char match[])
2571 {
2572 #ifndef _WIN32
2573 return shell_like_escape(match, 1);
2574 #else
2575 return strdup(escape_for_cd(match));
2576 #endif
2577 }
2578
2579 /* Processes completion match for insertion into command-line as a value in
2580 * single quotes. Returns newly allocated string. */
2581 static char *
squoted_arg_hook(const char match[])2582 squoted_arg_hook(const char match[])
2583 {
2584 return escape_for_squotes(match, 1);
2585 }
2586
2587 /* Processes completion match for insertion into command-line as a value in
2588 * double quotes. Returns newly allocated string. */
2589 static char *
dquoted_arg_hook(const char match[])2590 dquoted_arg_hook(const char match[])
2591 {
2592 return escape_for_dquotes(match, 1);
2593 }
2594
2595 /* Recalculates some numeric fields of the stat structure that depend on input
2596 * string length. */
2597 static void
update_line_stat(line_stats_t * stat,int new_len)2598 update_line_stat(line_stats_t *stat, int new_len)
2599 {
2600 stat->index += (new_len - 1) - stat->len;
2601 stat->curs_pos = stat->prompt_wid + vifm_wcswidth(stat->line, stat->index);
2602 stat->len = new_len - 1;
2603 }
2604
2605 /* Delete a character in a string
2606 * For example, wcsdelch(L"fooXXbar", 4, 2) returns L"foobar" */
2607 static wchar_t *
wcsdel(wchar_t * src,int pos,int len)2608 wcsdel(wchar_t *src, int pos, int len)
2609 {
2610 int p;
2611
2612 pos--;
2613
2614 for(p = pos; pos - p < len; pos++)
2615 if(! src[pos])
2616 {
2617 src[p] = L'\0';
2618 return src;
2619 }
2620
2621 pos--;
2622
2623 while(src[++pos] != L'\0')
2624 src[pos-len] = src[pos];
2625 src[pos-len] = src[pos];
2626
2627 return src;
2628 }
2629
2630 /* Disables all active completions. */
2631 static void
stop_completion(void)2632 stop_completion(void)
2633 {
2634 stop_dot_completion();
2635 stop_regular_completion();
2636 }
2637
2638 /* Disables dot completion if it's active. */
2639 static void
stop_dot_completion(void)2640 stop_dot_completion(void)
2641 {
2642 input_stat.dot_pos = -1;
2643 }
2644
2645 /* Disables tab completion if it's active. */
2646 static void
stop_regular_completion(void)2647 stop_regular_completion(void)
2648 {
2649 if(!input_stat.complete_continue)
2650 {
2651 return;
2652 }
2653
2654 input_stat.complete_continue = 0;
2655 vle_compl_reset();
2656 if(cfg.wild_menu && input_stat.complete != NULL)
2657 {
2658 if(sub_mode == CLS_MENU_COMMAND)
2659 {
2660 modmenu_full_redraw();
2661 }
2662 else
2663 {
2664 update_screen(UT_REDRAW);
2665 }
2666 update_cmdline_size();
2667 update_cmdline_text(&input_stat);
2668 curs_set(1);
2669 }
2670 }
2671
2672 line_stats_t *
get_line_stats(void)2673 get_line_stats(void)
2674 {
2675 return &input_stat;
2676 }
2677
2678 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
2679 /* vim: set cinoptions+=t0 filetype=c : */
2680