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