/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define CMDLINE_IMPL
#include "cmdline.h"

#include <curses.h>

#include <limits.h>

#include <assert.h> /* assert() */
#include <stddef.h> /* NULL size_t wchar_t */
#include <stdlib.h> /* free() */
#include <string.h> /* strdup() */
#include <wchar.h> /* wcslen() wcsncpy() */
#include <wctype.h> /* iswprint() */

#include "../cfg/config.h"
#include "../compat/curses.h"
#include "../compat/fs_limits.h"
#include "../compat/reallocarray.h"
#include "../engine/abbrevs.h"
#include "../engine/cmds.h"
#include "../engine/completion.h"
#include "../engine/keys.h"
#include "../engine/mode.h"
#include "../engine/parsing.h"
#include "../engine/var.h"
#include "../modes/dialogs/msg_dialog.h"
#include "../ui/color_scheme.h"
#include "../ui/colors.h"
#include "../ui/fileview.h"
#include "../ui/statusbar.h"
#include "../ui/statusline.h"
#include "../ui/quickview.h"
#include "../ui/ui.h"
#include "../utils/hist.h"
#include "../utils/macros.h"
#include "../utils/matcher.h"
#include "../utils/path.h"
#include "../utils/str.h"
#include "../utils/test_helpers.h"
#include "../utils/utf8.h"
#include "../utils/utils.h"
#include "../cmd_completion.h"
#include "../cmd_core.h"
#include "../filelist.h"
#include "../filtering.h"
#include "../flist_pos.h"
#include "../flist_sel.h"
#include "../marks.h"
#include "../running.h"
#include "../search.h"
#include "../status.h"
#include "dialogs/attr_dialog.h"
#include "dialogs/sort_dialog.h"
#include "menu.h"
#include "modes.h"
#include "normal.h"
#include "visual.h"
#include "wk.h"

/* Prompt prefix when navigation is enabled. */
#define NAV_PREFIX L"(nav)"

/* Operation on wild menu performed by wild_menu_op(). */
typedef enum
{
	WMO_RESET,  /* Make the first completion item the current one in the state. */
	WMO_DRAW,   /* Draw the menu for the first time. */
	WMO_REDRAW, /* Redraw the menu. */
}
WildMenuOp;

/* Stashed store of the state to support limited recursion. */
static line_stats_t prev_input_stat;
/* State of the command-line mode. */
static line_stats_t input_stat;

/* Width of the status bar. */
static int line_width = 1;

static int def_handler(wchar_t key);
static void update_cmdline_text(line_stats_t *stat);
static void draw_cmdline_text(line_stats_t *stat);
static void input_line_changed(void);
static void wild_inc_completion(line_stats_t *stat);
static int wild_inc_applies(const char cmd_line[]);
static int detect_input_change(line_stats_t *stat);
static void handle_empty_input(void);
static void handle_nonempty_input(void);
static void update_state(int result, int nmatches);
static void set_local_filter(const char value[]);
static wchar_t * wcsins(wchar_t src[], const wchar_t ins[], int pos);
static int enter_submode(CmdLineSubmode sub_mode, const char initial[],
		int reenter);
static void prepare_cmdline_mode(const wchar_t prompt[], const wchar_t cmd[],
		complete_cmd_func complete, CmdLineSubmode sub_mode, int allow_ee);
static void init_line_stats(line_stats_t *stat, const wchar_t prompt[],
		const wchar_t initial[], complete_cmd_func complete,
		CmdLineSubmode sub_mode, int allow_ee, int prev_mode);
static void save_view_port(line_stats_t *stat, view_t *view);
static void set_view_port(void);
static int is_line_edited(void);
static void leave_cmdline_mode(int cancelled);
static void free_line_stats(line_stats_t *stat);
static void cmd_ctrl_a(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info);
static CmdInputType cls_to_editable_cit(CmdLineSubmode sub_mode);
static void extedit_prompt(const char input[], int cursor_col, int is_expr_reg,
		prompt_cb cb, void *cb_arg);
static void cmd_ctrl_rb(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_h(key_info_t key_info, keys_info_t *keys_info);
static int should_quit_on_backspace(void);
static int no_initial_line(void);
static void cmd_ctrl_i(key_info_t key_info, keys_info_t *keys_info);
static void cmd_shift_tab(key_info_t key_info, keys_info_t *keys_info);
static void do_completion(void);
static void maybe_grab_statusline(void);
static void go_to_search_match(line_stats_t *stat, view_t *view,
		int in_reverse);
static void wild_menu_op(WildMenuOp op);
static int draw_wild_bar(int *last_pos, int *pos, int *len);
static int draw_wild_popup(int *last_pos, int *pos);
static int compute_wild_menu_height(void);
static void cmd_ctrl_j(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_k(key_info_t key_info, keys_info_t *keys_info);
static void cmd_return(key_info_t key_info, keys_info_t *keys_info);
static void nav_open(void);
static int is_input_line_empty(void);
static int expand_abbrev(void);
TSTATIC const wchar_t * extract_abbrev(line_stats_t *stat, int *pos,
		int *no_remap);
static void exec_abbrev(const wchar_t abbrev_rhs[], int no_remap, int pos);
static void save_input_to_history(const keys_info_t *keys_info,
		const char input[]);
static void save_prompt_to_history(const char input[], int is_expr_reg);
static void finish_prompt_submode(const char input[], prompt_cb cb,
		void *cb_arg);
static CmdInputType search_cls_to_cit(CmdLineSubmode sub_mode);
static int is_forward_search(CmdLineSubmode sub_mode);
static int is_backward_search(CmdLineSubmode sub_mode);
static int replace_wstring(wchar_t **str, const wchar_t with[]);
static void cmd_ctrl_n(key_info_t key_info, keys_info_t *keys_info);
static void hist_next(line_stats_t *stat, const hist_t *hist, size_t len);
static void hist_search_reset_input(void);
static void cmd_ctrl_requals(key_info_t key_info, keys_info_t *keys_info);
static void expr_reg_prompt_cb(const char expr[], void *arg);
static int expr_reg_prompt_completion(const char cmd[], void *arg);
static void cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_w(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xslash(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xa(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xc(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xxc(key_info_t key_info, keys_info_t *keys_info);
static void paste_short_path(view_t *view);
static void cmd_ctrl_xd(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xxd(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xe(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xxe(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xm(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xr(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xxr(key_info_t key_info, keys_info_t *keys_info);
static void paste_short_path_root(view_t *view);
static void cmd_ctrl_xt(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xxt(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_xequals(key_info_t key_info, keys_info_t *keys_info);
static void paste_name_part(const char name[], int root);
static void paste_str(const char str[], int allow_escaping);
static char * escape_cmd_for_pasting(const char str[]);
static void cmd_ctrl_underscore(key_info_t key_info, keys_info_t *keys_info);
static void cmd_meta_b(key_info_t key_info, keys_info_t *keys_info);
static void find_prev_word(void);
static void cmd_meta_d(key_info_t key_info, keys_info_t *keys_info);
static void cmd_meta_f(key_info_t key_info, keys_info_t *keys_info);
static void cmd_meta_dot(key_info_t key_info, keys_info_t *keys_info);
static void remove_previous_dot_completion(void);
static wchar_t * next_dot_completion(void);
static int insert_dot_completion(const wchar_t completion[]);
static int insert_str(const wchar_t str[]);
static void find_next_word(void);
static void cmd_delete(key_info_t key_info, keys_info_t *keys_info);
static void update_cursor(void);
TSTATIC void hist_prev(line_stats_t *stat, const hist_t *hist, size_t len);
static int replace_input_line(line_stats_t *stat, const char new[]);
static const hist_t * pick_hist(void);
static int is_cmdmode(int mode);
static void update_cmdline(line_stats_t *stat);
static int get_required_height(void);
static void cmd_ctrl_o(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_p(key_info_t key_info, keys_info_t *keys_info);
static void move_view_cursor(line_stats_t *stat, view_t *view, int new_pos);
static void update_view_cursor(line_stats_t *stat, view_t *view);
static void cmd_ctrl_t(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info);
static void nav_start(line_stats_t *stat);
static void nav_stop(line_stats_t *stat);
#ifdef ENABLE_EXTENDED_KEYS
static void cmd_down(key_info_t key_info, keys_info_t *keys_info);
static void cmd_up(key_info_t key_info, keys_info_t *keys_info);
static void cmd_left(key_info_t key_info, keys_info_t *keys_info);
static void cmd_right(key_info_t key_info, keys_info_t *keys_info);
static void cmd_home(key_info_t key_info, keys_info_t *keys_info);
static void cmd_end(key_info_t key_info, keys_info_t *keys_info);
static void cmd_page_up(key_info_t key_info, keys_info_t *keys_info);
static void cmd_page_down(key_info_t key_info, keys_info_t *keys_info);
#endif /* ENABLE_EXTENDED_KEYS */
static int hist_search_setup(HIST kind);
static void update_cmdline_size(void);
TSTATIC int line_completion(line_stats_t *stat);
static int start_completion(line_stats_t *stat, int inc_completion);
static char * escaped_arg_hook(const char match[]);
static char * squoted_arg_hook(const char match[]);
static char * dquoted_arg_hook(const char match[]);
static void update_line_stat(line_stats_t *stat, int new_len);
static int esc_wcswidth(const wchar_t str[], size_t n);
static int esc_wcwidth(wchar_t wc);
static wchar_t * wcsdel(wchar_t *src, int pos, int len);
static void switch_to_editing(void);
static void stop_completion(void);
static void stop_dot_completion(void);
static void stop_regular_completion(void);
TSTATIC line_stats_t * get_line_stats(void);
static void handle_mouse_event(key_info_t key_info, keys_info_t *keys_info);

static keys_add_info_t builtin_cmds[] = {
	{WK_C_c,             {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}},
	{WK_C_g,             {{&cmd_ctrl_g}, .descr = "edit cmdline in editor"}},
	{WK_C_h,             {{&cmd_ctrl_h}, .descr = "remove char to the left"}},
	{WK_C_i,             {{&cmd_ctrl_i}, .descr = "start/continue completion"}},
	{WK_C_j,             {{&cmd_ctrl_j}, .descr = "nav: leave without moving the cursor"}},
	{WK_C_k,             {{&cmd_ctrl_k}, .descr = "remove line part to the right"}},
	{WK_CR,              {{&cmd_return}, .descr = "execute/accept input"}},
	{WK_C_n,             {{&cmd_ctrl_n}, .descr = "recall next history item"}},
	{WK_C_o,             {{&cmd_ctrl_o}, .descr = "nav: go to parent directory"}},
	{WK_C_p,             {{&cmd_ctrl_p}, .descr = "recall previous history item"}},
	{WK_C_t,             {{&cmd_ctrl_t}, .descr = "swap adjacent characters"}},
	{WK_ESC,             {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}},
	{WK_ESC WK_ESC,      {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}},
	{WK_C_RB,            {{&cmd_ctrl_rb}, .descr = "expand abbreviation"}},
	{WK_C_USCORE,        {{&cmd_ctrl_underscore}, .descr = "reset completion"}},
	{WK_DELETE,          {{&cmd_ctrl_h}, .descr = "remove char to the left"}},
	{WK_ESC L"[Z",       {{&cmd_shift_tab}, .descr = "complete in reverse order"}},
	{WK_C_b,             {{&cmd_ctrl_b}, .descr = "move cursor to the left"}},
	{WK_C_f,             {{&cmd_ctrl_f}, .descr = "move cursor to the right"}},
	{WK_C_a,             {{&cmd_ctrl_a}, .descr = "move cursor to the beginning"}},
	{WK_C_e,             {{&cmd_ctrl_e}, .descr = "move cursor to the end"}},
	{WK_C_d,             {{&cmd_delete}, .descr = "delete current character"}},
	{WK_C_r WK_EQUALS,   {{&cmd_ctrl_requals}, .descr = "invoke expression register prompt"}},
	{WK_C_u,             {{&cmd_ctrl_u}, .descr = "remove line part to the left"}},
	{WK_C_w,             {{&cmd_ctrl_w}, .descr = "remove word to the left"}},
	{WK_C_y,             {{&cmd_ctrl_y}, .descr = "toggle navigation"}},
	{WK_C_x WK_SLASH,    {{&cmd_ctrl_xslash}, .descr = "insert last search pattern"}},
	{WK_C_x WK_a,        {{&cmd_ctrl_xa}, .descr = "insert implicit permanent filter value"}},
	{WK_C_x WK_c,        {{&cmd_ctrl_xc}, .descr = "insert current file name"}},
	{WK_C_x WK_d,        {{&cmd_ctrl_xd}, .descr = "insert current directory path"}},
	{WK_C_x WK_e,        {{&cmd_ctrl_xe}, .descr = "insert current file extension"}},
	{WK_C_x WK_m,        {{&cmd_ctrl_xm}, .descr = "insert explicit permanent filter value"}},
	{WK_C_x WK_r,        {{&cmd_ctrl_xr}, .descr = "insert root of current file name"}},
	{WK_C_x WK_t,        {{&cmd_ctrl_xt}, .descr = "insert name of current directory"}},
	{WK_C_x WK_C_x WK_c, {{&cmd_ctrl_xxc}, .descr = "insert other file name"}},
	{WK_C_x WK_C_x WK_d, {{&cmd_ctrl_xxd}, .descr = "insert other directory path"}},
	{WK_C_x WK_C_x WK_e, {{&cmd_ctrl_xxe}, .descr = "insert other file extension"}},
	{WK_C_x WK_C_x WK_r, {{&cmd_ctrl_xxr}, .descr = "insert root of other file name"}},
	{WK_C_x WK_C_x WK_t, {{&cmd_ctrl_xxt}, .descr = "insert name of other directory"}},
	{WK_C_x WK_EQUALS,   {{&cmd_ctrl_xequals}, .descr = "insert local filter value"}},
#ifndef __PDCURSES__
	{WK_ESC WK_b,     {{&cmd_meta_b}, .descr = "move cursor to previous word"}},
	{WK_ESC WK_d,     {{&cmd_meta_d}, .descr = "remove next word"}},
	{WK_ESC WK_f,     {{&cmd_meta_f}, .descr = "move cursor to next word"}},
	{WK_ESC WK_DOT,   {{&cmd_meta_dot}, .descr = "start/continue last arg completion"}},
#else
	{{K(ALT_B)},      {{&cmd_meta_b}, .descr = "move cursor to previous word"}},
	{{K(ALT_D)},      {{&cmd_meta_d}, .descr = "remove next word"}},
	{{K(ALT_F)},      {{&cmd_meta_f}, .descr = "move cursor to next word"}},
	{{K(ALT_PERIOD)}, {{&cmd_meta_dot}, .descr = "start/continue last arg completion"}},
#endif
#ifdef ENABLE_EXTENDED_KEYS
	{{K(KEY_BACKSPACE)}, {{&cmd_ctrl_h}, .descr = "remove char to the left"}},
	{{K(KEY_DOWN)},      {{&cmd_down},   .descr = "prefix-complete next history item/next item"}},
	{{K(KEY_UP)},        {{&cmd_up},     .descr = "prefix-complete previous history item/prev item"}},
	{{K(KEY_LEFT)},      {{&cmd_left},   .descr = "move cursor to the left/parent dir"}},
	{{K(KEY_RIGHT)},     {{&cmd_right},  .descr = "move cursor to the right/enter entry"}},
	{{K(KEY_HOME)},      {{&cmd_home},   .descr = "move cursor to the beginning/first item"}},
	{{K(KEY_END)},       {{&cmd_end},    .descr = "move cursor to the end/last item"}},
	{{K(KEY_DC)},        {{&cmd_delete}, .descr = "delete current character"}},
	{{K(KEY_BTAB)},      {{&cmd_shift_tab}, .descr = "complete in reverse order"}},
	{{K(KEY_PPAGE)},     {{&cmd_page_up},   .descr = "nav: scroll page up"}},
	{{K(KEY_NPAGE)},     {{&cmd_page_down}, .descr = "nav: scroll page down"}},
#endif /* ENABLE_EXTENDED_KEYS */
	{{K(KEY_MOUSE)},     {{&handle_mouse_event}, FOLLOWED_BY_NONE}},
};

void
modcline_init(void)
{
	int ret_code;

	vle_keys_set_def_handler(CMDLINE_MODE, &def_handler);

	ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), CMDLINE_MODE);
	assert(ret_code == 0);

	(void)ret_code;
}

void
modnav_init(void)
{
	vle_keys_set_def_handler(NAV_MODE, &def_handler);

	int ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), NAV_MODE);
	assert(ret_code == 0);
	(void)ret_code;
}

/* Handles all keys uncaught by shortcuts.  Returns zero on success and non-zero
 * on error. */
static int
def_handler(wchar_t key)
{
	void *p;
	wchar_t buf[2] = {key, L'\0'};

	input_stat.hist_search = HIST_NONE;

	if(input_stat.complete_continue && !input_stat.inc_completion
			&& input_stat.line[input_stat.index - 1] == L'/' && key == L'/')
	{
		stop_completion();
		return 0;
	}

	/* There is no need for resetting completion because of terminal dimensions
	 * change (note also that iswprint(KEY_RESIZE) might return false). */
	if(key == K(KEY_RESIZE))
	{
		return 0;
	}

	stop_completion();

	if(key != L'\r' && (key == WC_C_SPACE || !iswprint(key)))
	{
		return 0;
	}

	if(!cfg_is_word_wchar(key))
	{
		if(expand_abbrev() != 0)
		{
			return 0;
		}
	}

	p = reallocarray(input_stat.line, input_stat.len + 2, sizeof(wchar_t));
	if(p == NULL)
	{
		leave_cmdline_mode(/*cancelled=*/1);
		return 0;
	}

	input_stat.line = p;

	input_stat.index++;
	wcsins(input_stat.line, buf, input_stat.index);
	input_stat.len++;

	input_stat.curs_pos += esc_wcwidth(key);

	update_cmdline_size();
	update_cmdline_text(&input_stat);

	return 0;
}

/* Processes input change and redraw of resulting command-line. */
static void
update_cmdline_text(line_stats_t *stat)
{
	if(input_stat.sub_mode == CLS_COMMAND)
	{
		wild_inc_completion(stat);
	}
	else
	{
		input_line_changed();
	}

	draw_cmdline_text(stat);
}

/* Updates text displayed on the command line and cursor within it. */
static void
draw_cmdline_text(line_stats_t *stat)
{
	if(stats_silenced_ui())
	{
		/* XXX: erasing status_bar causes blinking if <silent> mapping that uses
		 *      command-line is pressed and held down.  Ideally wouldn't need this
		 *      return. */
		return;
	}

	int group = -1;
	col_attr_t prompt_col = {};

	werase(status_bar);

	switch(stat->state)
	{
		case PS_NORMAL:        group = CMD_LINE_COLOR; break;
		case PS_WRONG_PATTERN: group = ERROR_MSG_COLOR; break;
		case PS_NO_MATCH:      group = CMD_LINE_COLOR;
		                       prompt_col.attr = A_REVERSE;
		                       prompt_col.gui_attr = A_REVERSE;
		                       break;
	}
	prompt_col.attr ^= cfg.cs.color[group].attr;
	prompt_col.gui_attr ^= cfg.cs.color[group].gui_attr;

	wchar_t *escaped;

	escaped = escape_unreadablew(stat->prompt);
	ui_set_attr(status_bar, &prompt_col, cfg.cs.pair[group]);
	compat_mvwaddwstr(status_bar, 0, 0, escaped);
	free(escaped);

	escaped = escape_unreadablew(stat->line);
	ui_set_attr(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
			cfg.cs.pair[CMD_LINE_COLOR]);
	compat_mvwaddwstr(status_bar, stat->prompt_wid/line_width,
			stat->prompt_wid%line_width, escaped);
	free(escaped);

	update_cursor();
	ui_refresh_win(status_bar);
}

/* Callback-like function, which is called every time input line is changed. */
static void
input_line_changed(void)
{
	if(!cfg.inc_search ||
			(!input_stat.search_mode && input_stat.sub_mode != CLS_FILTER))
	{
		return;
	}

	/* Hide cursor during view update, otherwise user might notice it blinking in
	 * wrong places. */
	ui_set_cursor(/*visibility=*/0);

	/* set_view_port() should not be called if none of the conditions are true. */

	input_stat.state = PS_NORMAL;
	if(is_input_line_empty())
	{
		free(input_stat.last_line);
		input_stat.last_line = NULL;

		set_view_port();
		handle_empty_input();
	}
	else if(detect_input_change(&input_stat))
	{
		set_view_port();
		handle_nonempty_input();
	}

	if(input_stat.prev_mode != MENU_MODE && input_stat.prev_mode != VISUAL_MODE)
	{
		fpos_set_pos(curr_view, curr_view->list_pos);
		qv_ui_updated();
		redraw_current_view();
	}
	else if(input_stat.prev_mode == MENU_MODE)
	{
		modmenu_partial_redraw();
	}

	/* Hardware cursor is moved on the screen only on refresh, so refresh status
	 * bar to force cursor moving there before it becomes visible again. */
	ui_refresh_win(status_bar);
	ui_set_cursor(/*visibility=*/1);
}

/* Automatically display completion entries if current command-line qualifies
 * for that (depends on 'wildinc' option). */
static void
wild_inc_completion(line_stats_t *stat)
{
	if(stat->manual_completion)
	{
		/* Do not interfere with user-initiated completion. */
		return;
	}

	if(!detect_input_change(stat))
	{
		return;
	}

	/* Only complete the part before the cursor so just copy that part to
	 * line_mb. */
	wchar_t t = stat->line[stat->index];
	stat->line[stat->index] = L'\0';

	char *line_mb = to_multibyte(stat->line);
	stat->line[stat->index] = t;
	if(line_mb == NULL)
	{
		/* Not enough memory?  Just don't do the completion, no big deal. */
		return;
	}

	if(wild_inc_applies(line_mb))
	{
		stop_completion();
		wild_menu_op(WMO_RESET);

		if(start_completion(stat, /*inc_completion=*/1) == 0)
		{
			stat->complete_continue = 1;
			maybe_grab_statusline();
			wild_menu_op(WMO_DRAW);
		}
	}

	free(line_mb);
}

/* Checks whether completion should be performed automatically for the current
 * :command.  Returns non-zero if so. */
static int
wild_inc_applies(const char cmd_line[])
{
	cmd_info_t info;
	const cmd_t *cmd = vle_cmds_parse(cmds_find_last(cmd_line), &info);
	if(cmd == NULL || is_null_or_empty(info.post_name))
	{
		return 0;
	}

	char *name = format_str(":%s", cmd->name);
	if(name == NULL)
	{
		return 0;
	}

	/* NULL check is for tests. */
	int applies = (cfg.wild_inc != NULL && matcher_matches(cfg.wild_inc, name));
	free(name);
	return applies;
}

/* Checks whether input line has changed since this function was called last
 * time.  Returns non-zero if so. */
static int
detect_input_change(line_stats_t *stat)
{
	/* last_line is NULL initially and line can be NULL in tests. */
	if(stat->last_line == NULL && stat->line == NULL)
	{
		return 0;
	}

	int changed = (stat->last_line == NULL)
	           || (wcscmp(stat->last_line, stat->line) != 0);
	if(changed)
	{
		/* Falsely reporting that an input has changed shouldn't have any negative
		 * consequences. */
		(void)replace_wstring(&stat->last_line, stat->line);
	}
	return changed;
}

/* Provides reaction for empty input during interactive search/filtering. */
static void
handle_empty_input(void)
{
	/* Clear selection/highlight. */
	if(input_stat.prev_mode == MENU_MODE)
	{
		(void)menus_search("", input_stat.menu, 0);
	}
	else if(cfg.hl_search && input_stat.prev_mode != VISUAL_MODE)
	{
		flist_sel_stash(curr_view);
	}

	if(input_stat.prev_mode != MENU_MODE)
	{
		ui_view_reset_search_highlight(curr_view);
	}

	if(input_stat.prev_mode == VISUAL_MODE)
	{
		modvis_update();
	}

	if(input_stat.sub_mode == CLS_FILTER)
	{
		set_local_filter("");
	}
}

/* Provides reaction for non-empty input during interactive search/filtering. */
static void
handle_nonempty_input(void)
{
	char *const mbinput = to_multibyte(input_stat.line);
	int backward = 0;

	switch(input_stat.sub_mode)
	{
		int result;

		case CLS_BSEARCH: backward = 1; /* Fall through. */
		case CLS_FSEARCH:
			result = modnorm_find(curr_view, mbinput, backward, /*print_msg=*/0,
					&input_stat.search_match_found);
			update_state(result, curr_view->matches);
			break;
		case CLS_VBSEARCH: backward = 1; /* Fall through. */
		case CLS_VFSEARCH:
			result = modvis_find_interactive(curr_view, mbinput, backward,
					&input_stat.search_match_found);
			update_state(result, curr_view->matches);
			break;
		case CLS_MENU_FSEARCH:
		case CLS_MENU_BSEARCH:
			result = menus_search(mbinput, input_stat.menu, /*print_errors=*/0);
			update_state(result, menus_search_matched(input_stat.menu));
			break;
		case CLS_FILTER:
			set_local_filter(mbinput);
			break;

		default:
			assert("Unexpected command-line submode.");
			break;
	}

	free(mbinput);
}

/* Computes and sets new prompt state from result of a search and number of
 * matched items.  It is assumed that current state is PS_NORMAL. */
static void
update_state(int result, int nmatches)
{
	if(result < 0)
	{
		input_stat.state = PS_WRONG_PATTERN;
	}
	else if(nmatches == 0)
	{
		input_stat.state = PS_NO_MATCH;
	}
}

/* Updates value of the local filter of the current view. */
static void
set_local_filter(const char value[])
{
	const int rel_pos = input_stat.old_pos - input_stat.old_top;
	const int result = local_filter_set(curr_view, value);
	if(result != 0)
	{
		input_stat.state = (result < 0) ? PS_WRONG_PATTERN : PS_NO_MATCH;
	}
	local_filter_update_view(curr_view, rel_pos);
}

/* Insert a string into another string
 * For example, wcsins(L"foor", L"ba", 4) returns L"foobar"
 * If pos is larger then wcslen(src), the character will
 * be added at the end of the src */
static wchar_t *
wcsins(wchar_t src[], const wchar_t ins[], int pos)
{
	int i;
	wchar_t *p;

	pos--;

	for (p = src + pos; *p; p++)
		;

	for (i = 0; ins[i]; i++)
		;

	for (; p >= src + pos; p--)
		*(p + i) = *p;
	p++;

	for (; *ins; ins++, p++)
		*p = *ins;

	return src;
}

void
modcline_enter(CmdLineSubmode sub_mode, const char initial[])
{
	assert(sub_mode != CLS_MENU_COMMAND && sub_mode != CLS_MENU_FSEARCH &&
			sub_mode != CLS_MENU_BSEARCH &&
			"Use modcline_in_menu() for CLS_MENU_* submodes.");
	assert(sub_mode != CLS_PROMPT &&
			"Use modcline_prompt() for CLS_PROMPT submode.");
	if(enter_submode(sub_mode, initial, /*reenter=*/0) == 0 &&
			!is_null_or_empty(initial))
	{
		/* Trigger handling of input if its initial value isn't empty. */
		update_cmdline_text(&input_stat);
	}
}

void
modcline_in_menu(CmdLineSubmode sub_mode, const char initial[],
		struct menu_data_t *m)
{
	assert((sub_mode == CLS_MENU_COMMAND || sub_mode == CLS_MENU_FSEARCH ||
			sub_mode == CLS_MENU_BSEARCH) &&
			"modcline_in_menu() is only for CLS_MENU_* submodes.");

	if(enter_submode(sub_mode, initial, /*reenter=*/0) == 0)
	{
		input_stat.menu = m;
	}
}

/* Enters command-line editing submode.  Returns zero on success. */
static int
enter_submode(CmdLineSubmode sub_mode, const char initial[], int reenter)
{
	wchar_t *winitial;
	const wchar_t *wprompt;
	complete_cmd_func complete_func = NULL;

	if(sub_mode == CLS_FILTER && curr_view->custom.type == CV_DIFF)
	{
		show_error_msg("Filtering", "No local filter for diff views");
		return 1;
	}

	winitial = to_wide_force(initial);
	if(winitial == NULL)
	{
		show_error_msg("Error", "Not enough memory");
		return 1;
	}

	if(sub_mode == CLS_COMMAND || sub_mode == CLS_MENU_COMMAND)
	{
		wprompt = L":";
		complete_func = &vle_cmds_complete;
	}
	else if(sub_mode == CLS_FILTER)
	{
		wprompt = L"=";
	}
	else if(is_forward_search(sub_mode))
	{
		wprompt = L"/";
	}
	else if(is_backward_search(sub_mode))
	{
		wprompt = L"?";
	}
	else
	{
		assert(0 && "Unknown command line submode.");
		wprompt = L"E";
	}

	if(reenter)
	{
		int prev_mode = input_stat.prev_mode;
		free_line_stats(&input_stat);
		init_line_stats(&input_stat, wprompt, winitial, complete_func, sub_mode,
				/*allow_ee=*/0, prev_mode);

		/* Just in case. */
		curr_stats.save_msg = 1;

		stats_redraw_later();
	}
	else
	{
		prepare_cmdline_mode(wprompt, winitial, complete_func, sub_mode,
				/*allow_ee=*/0);
	}

	free(winitial);
	return 0;
}

void
modcline_prompt(const char prompt[], const char initial[], prompt_cb cb,
		void *cb_arg, complete_cmd_func complete)
{
	wchar_t *wprompt = to_wide_force(prompt);
	wchar_t *winitial = to_wide_force(initial);

	if(wprompt == NULL || winitial == NULL)
	{
		show_error_msg("Error", "Not enough memory");
	}
	else
	{
		prepare_cmdline_mode(wprompt, winitial, complete, CLS_PROMPT,
				/*allow_ee=*/1);
		input_stat.prompt_callback = cb;
		input_stat.prompt_callback_arg = cb_arg;
	}

	free(wprompt);
	free(winitial);
}

void
modcline_redraw(void)
{
	/* Hide cursor during redraw, otherwise user might notice it blinking in wrong
	 * places. */
	ui_set_cursor(/*visibility=*/0);

	if(input_stat.prev_mode == MENU_MODE)
	{
		modmenu_full_redraw();
	}
	else
	{
		ui_redraw_as_background();
		if(input_stat.prev_mode == SORT_MODE)
		{
			redraw_sort_dialog();
		}
		else if(input_stat.prev_mode == ATTR_MODE)
		{
			redraw_attr_dialog();
		}
	}

	line_width = getmaxx(stdscr);
	update_cmdline_size();
	draw_cmdline_text(&input_stat);

	if(input_stat.complete_continue)
	{
		wild_menu_op(WMO_REDRAW);
	}

	if(input_stat.prev_mode != MENU_MODE)
	{
		update_all_windows();
	}

	/* Make cursor visible after all redraws. */
	ui_set_cursor(/*visibility=*/1);
}

/* Performs all necessary preparations for command-line mode to start
 * operating. */
static void
prepare_cmdline_mode(const wchar_t prompt[], const wchar_t initial[],
		complete_cmd_func complete, CmdLineSubmode sub_mode, int allow_ee)
{
	if(is_cmdmode(vle_mode_get()))
	{
		/* We're recursing into command-line mode. */
		ui_sb_unlock();
		prev_input_stat = input_stat;
	}

	line_width = getmaxx(stdscr);
	ui_sb_lock();

	init_line_stats(&input_stat, prompt, initial, complete, sub_mode, allow_ee,
			vle_mode_get());

	vle_mode_set(CMDLINE_MODE, VMT_SECONDARY);

	update_cmdline_size();
	draw_cmdline_text(&input_stat);

	curr_stats.save_msg = 1;

	if(input_stat.prev_mode == NORMAL_MODE)
		cmds_init();

	/* Make cursor visible only after all initial draws. */
	ui_set_cursor(/*visibility=*/1);
}

/* Initializes command-line status. */
static void
init_line_stats(line_stats_t *stat, const wchar_t prompt[],
		const wchar_t initial[], complete_cmd_func complete,
		CmdLineSubmode sub_mode, int allow_ee, int prev_mode)
{
	stat->sub_mode = sub_mode;
	stat->navigating = 0;
	stat->sub_mode_allows_ee = allow_ee;
	stat->menu = NULL;
	stat->prompt_callback = NULL;
	stat->prompt_callback_arg = NULL;

	stat->prev_mode = prev_mode;

	stat->line = vifm_wcsdup(initial);
	stat->last_line = NULL;
	stat->initial_line = vifm_wcsdup(stat->line);
	stat->index = wcslen(initial);
	stat->curs_pos = esc_wcswidth(stat->line, (size_t)-1);
	stat->len = stat->index;
	stat->cmd_pos = -1;
	stat->complete_continue = 0;
	stat->hist_search = HIST_NONE;
	stat->hist_search_stash = NULL;
	stat->reverse_completion = 0;
	stat->manual_completion = 0;
	stat->inc_completion = 0;
	stat->complete = complete;
	stat->search_mode = 0;
	stat->search_match_found = 0;
	stat->dot_pos = -1;
	stat->line_edited = 0;
	stat->enter_mapping_state = vle_keys_mapping_state();
	stat->expanding_abbrev = 0;
	stat->state = PS_NORMAL;

	if((is_forward_search(sub_mode) || is_backward_search(sub_mode)) &&
			sub_mode != CLS_VWFSEARCH && sub_mode != CLS_VWBSEARCH)
	{
		stat->search_mode = 1;
	}

	if(stat->search_mode || sub_mode == CLS_FILTER)
	{
		save_view_port(stat, curr_view);
	}

	wcsncpy(stat->prompt, prompt, ARRAY_LEN(stat->prompt));
	stat->prompt_wid = esc_wcswidth(stat->prompt, (size_t)-1);
	stat->curs_pos += stat->prompt_wid;
}

/* Stores view port parameters (top line, current position). */
static void
save_view_port(line_stats_t *stat, view_t *view)
{
	if(stat->prev_mode != MENU_MODE)
	{
		stat->old_top = view->top_line;
		stat->old_pos = view->list_pos;
	}
	else
	{
		modmenu_save_pos();
	}
}

/* Sets view port parameters to appropriate for current submode state. */
static void
set_view_port(void)
{
	if(input_stat.prev_mode == MENU_MODE)
	{
		modmenu_restore_pos();
		return;
	}

	if(input_stat.sub_mode != CLS_FILTER || !is_line_edited())
	{
		curr_view->top_line = input_stat.old_top;
		curr_view->list_pos = input_stat.old_pos;
	}
	else if(input_stat.sub_mode == CLS_FILTER)
	{
		/* Filtering itself doesn't update status line. */
		ui_stat_update(curr_view, 1);
	}
}

/* Checks whether line was edited since entering command-line mode. */
static int
is_line_edited(void)
{
	if(input_stat.line_edited)
	{
		return 1;
	}

	input_stat.line_edited = wcscmp(input_stat.line, input_stat.initial_line);
	return input_stat.line_edited;
}

/* Leaves command-line mode. */
static void
leave_cmdline_mode(int cancelled)
{
	free_line_stats(&input_stat);

	if(is_cmdmode(vle_mode_get()))
	{
		if(is_cmdmode(input_stat.prev_mode))
		{
			/* We're restoring from a recursive command-line mode. */
			input_stat = prev_input_stat;
			/* Update is needed primarily on cancellation as callback isn't called
			 * then. */
			update_cmdline_text(&input_stat);
			return;
		}

		if(cancelled && input_stat.sub_mode == CLS_PROMPT)
		{
			/* Invoke callback with NULL for a result to inform about cancellation. */
			finish_prompt_submode(/*input=*/NULL, input_stat.prompt_callback,
					input_stat.prompt_callback_arg);
		}

		vle_mode_set(input_stat.prev_mode, VMT_PRIMARY);
	}

	/* Hide the cursor first. */
	ui_set_cursor(/*visibility=*/0);

	const int multiline_status_bar = (getmaxy(status_bar) > 1);

	wresize(status_bar, 1, getmaxx(stdscr) - FIELDS_WIDTH());
	if(multiline_status_bar)
	{
		stats_redraw_later();
		mvwin(status_bar, getmaxy(stdscr) - 1, 0);
		if(input_stat.prev_mode == MENU_MODE)
		{
			wresize(menu_win, getmaxy(stdscr) - 1, getmaxx(stdscr));
			modmenu_partial_redraw();
		}
	}

	curr_stats.save_msg = 0;
	ui_sb_unlock();
	ui_sb_clear();

	if(!vle_mode_is(MENU_MODE))
	{
		ui_ruler_update(curr_view, 1);
	}

	if(input_stat.prev_mode != MENU_MODE && input_stat.prev_mode != VIEW_MODE)
	{
		redraw_current_view();
	}
}

/* Frees resources allocated for command-line status. */
static void
free_line_stats(line_stats_t *stat)
{
	free(input_stat.line);
	free(input_stat.last_line);
	free(input_stat.initial_line);
	free(input_stat.hist_search_stash);
	input_stat.line = NULL;
	input_stat.last_line = NULL;
	input_stat.initial_line = NULL;
	input_stat.hist_search_stash = NULL;
}

/* Moves command-line cursor to the beginning of command-line. */
static void
cmd_ctrl_a(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	input_stat.index = 0;
	input_stat.curs_pos = input_stat.prompt_wid;
	update_cursor();
}

/* Moves command-line cursor to the left. */
static void
cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(input_stat.index > 0)
	{
		input_stat.index--;
		input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index]);
		update_cursor();
	}
}

/* Initiates leaving of command-line mode and reverting related changes in other
 * parts of the interface. */
static void
cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info)
{
	char *mbstr;

	stop_completion();
	werase(status_bar);
	wnoutrefresh(status_bar);

	mbstr = to_multibyte(input_stat.line);
	save_input_to_history(keys_info, mbstr);
	free(mbstr);

	if(input_stat.sub_mode != CLS_FILTER)
	{
		input_stat.line[0] = L'\0';
		input_line_changed();
	}

	const line_stats_t old_input_stat = input_stat;
	leave_cmdline_mode(/*cancelled=*/1);

	if(old_input_stat.prev_mode == VISUAL_MODE)
	{
		if(!old_input_stat.search_mode)
		{
			modvis_leave(curr_stats.save_msg, 1, 1);
			fpos_set_pos(curr_view, marks_find_in_view(curr_view, '<'));
		}
	}

	if(old_input_stat.sub_mode == CLS_COMMAND)
	{
		curr_stats.save_msg = cmds_dispatch("", curr_view, CIT_COMMAND);
	}
	else if(old_input_stat.sub_mode == CLS_FILTER)
	{
		local_filter_cancel(curr_view);
		curr_view->top_line = old_input_stat.old_top;
		curr_view->list_pos = old_input_stat.old_pos;
		redraw_current_view();
	}
}

/* Moves command-line cursor to the end of command-line. */
static void
cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(input_stat.index == input_stat.len)
		return;

	input_stat.index = input_stat.len;
	input_stat.curs_pos = input_stat.prompt_wid +
			esc_wcswidth(input_stat.line, (size_t)-1);
	update_cursor();
}

/* Moves command-line cursor to the right. */
static void
cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(input_stat.index < input_stat.len)
	{
		input_stat.curs_pos += esc_wcwidth(input_stat.line[input_stat.index]);
		input_stat.index++;
		update_cursor();
	}
}

/* Opens the editor with already typed in characters, gets entered line and
 * executes it as if it was typed. */
static void
cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info)
{
	const CmdInputType type = cls_to_editable_cit(input_stat.sub_mode);
	const int prompt_ee = (input_stat.sub_mode == CLS_PROMPT)
	                   && input_stat.sub_mode_allows_ee;
	if(type != (CmdInputType)-1 || prompt_ee)
	{
		char *const mbstr = to_multibyte(input_stat.line);
		const int prev_mode = input_stat.prev_mode;
		const CmdLineSubmode sub_mode = input_stat.sub_mode;
		const prompt_cb prompt_callback = input_stat.prompt_callback;
		void *const prompt_callback_arg = input_stat.prompt_callback_arg;
		const int index = input_stat.index;

		leave_cmdline_mode(/*cancelled=*/0);

		if(sub_mode == CLS_FILTER)
		{
			local_filter_cancel(curr_view);
		}

		if(prompt_ee)
		{
			int is_expr_reg = is_cmdmode(prev_mode);
			extedit_prompt(mbstr, index + 1, is_expr_reg, prompt_callback,
					prompt_callback_arg);
		}
		else
		{
			cmds_run_ext(mbstr, index + 1, type);
		}

		free(mbstr);
	}
}

/* Converts command-line sub-mode to type of command input which supports
 * editing.  Returns (CmdInputType)-1 when there is no appropriate command
 * type. */
static CmdInputType
cls_to_editable_cit(CmdLineSubmode sub_mode)
{
	switch(sub_mode)
	{
		case CLS_COMMAND:  return CIT_COMMAND;
		case CLS_FSEARCH:  return CIT_FSEARCH_PATTERN;
		case CLS_BSEARCH:  return CIT_BSEARCH_PATTERN;
		case CLS_VFSEARCH: return CIT_VFSEARCH_PATTERN;
		case CLS_VBSEARCH: return CIT_VBSEARCH_PATTERN;
		case CLS_FILTER:   return CIT_FILTER_PATTERN;

		default:
			return (CmdInputType)-1;
	}
}

/* Queries prompt input using external editor. */
static void
extedit_prompt(const char input[], int cursor_col, int is_expr_reg,
		prompt_cb cb, void *cb_arg)
{
	CmdInputType type = (is_expr_reg ? CIT_EXPRREG_INPUT : CIT_PROMPT_INPUT);
	char *const ext_cmd = cmds_get_ext(input, cursor_col, type);

	if(ext_cmd != NULL)
	{
		save_prompt_to_history(ext_cmd, is_expr_reg);
	}
	else
	{
		save_prompt_to_history(input, is_expr_reg);

		ui_sb_err("Error querying data from external source.");
		curr_stats.save_msg = 1;
	}

	/* Invoking this will NULL in case of error to report it. */
	finish_prompt_submode(ext_cmd, cb, cb_arg);
	free(ext_cmd);
}

/* Expands abbreviation to the left of current cursor position, if any
 * present. */
static void
cmd_ctrl_rb(key_info_t key_info, keys_info_t *keys_info)
{
	(void)expand_abbrev();
}

/* Handles backspace. */
static void
cmd_ctrl_h(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(should_quit_on_backspace())
	{
		cmd_ctrl_c(key_info, keys_info);
		return;
	}

	if(input_stat.index == 0)
	{
		return;
	}

	input_stat.index--;
	input_stat.len--;

	input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index]);

	if(input_stat.index == input_stat.len)
	{
		input_stat.line[input_stat.index] = L'\0';
	}
	else
	{
		wcsdel(input_stat.line, input_stat.index + 1, 1);
	}

	update_cmdline_text(&input_stat);
}

/* Checks whether backspace key pressed in current state should quit
 * command-line mode.  Returns non-zero if so, otherwise zero is returned. */
static int
should_quit_on_backspace(void)
{
	return input_stat.index == 0
	    && input_stat.len == 0
	    && input_stat.sub_mode != CLS_PROMPT
	    && (input_stat.sub_mode != CLS_FILTER || no_initial_line());
}

/* Checks whether initial line was empty.  Returns non-zero if so, otherwise
 * non-zero is returned. */
static int
no_initial_line(void)
{
	return input_stat.initial_line[0] == L'\0';
}

static void
cmd_ctrl_i(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.search_mode)
	{
		go_to_search_match(&input_stat, curr_view, /*in_reverse=*/0);
		return;
	}

	if(!input_stat.complete_continue)
		wild_menu_op(WMO_RESET);
	input_stat.reverse_completion = 0;

	if(input_stat.complete_continue && vle_compl_get_count() == 2)
		input_stat.complete_continue = 0;

	do_completion();
	wild_menu_op(WMO_DRAW);
}

static void
cmd_shift_tab(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.search_mode)
	{
		go_to_search_match(&input_stat, curr_view, /*in_reverse=*/1);
		return;
	}

	if(!input_stat.complete_continue)
		wild_menu_op(WMO_RESET);
	input_stat.reverse_completion = 1;

	if(input_stat.complete_continue && vle_compl_get_count() == 2)
		input_stat.complete_continue = 0;

	do_completion();
	wild_menu_op(WMO_DRAW);
}

/* Navigates to next/previous search match.  Relies on already available search
 * results and does not perform the search. */
static void
go_to_search_match(line_stats_t *stat, view_t *view, int in_reverse)
{
	if(!cfg.inc_search || stat->sub_mode == CLS_MENU_FSEARCH ||
			stat->sub_mode == CLS_MENU_BSEARCH)
	{
		return;
	}

	int backward = is_backward_search(stat->sub_mode);
	if(in_reverse)
	{
		backward = !backward;
	}

	int match_pos = find_search_match(view, backward, /*count=*/1);
	if(match_pos >= 0)
	{
		move_view_cursor(stat, view, match_pos);
	}
}

static void
do_completion(void)
{
	if(input_stat.complete == NULL)
	{
		return;
	}

	input_stat.manual_completion = 1;

	line_completion(&input_stat);

	update_cmdline_size();
	update_cmdline_text(&input_stat);

	maybe_grab_statusline();

	input_stat.manual_completion = 0;
}

/* Possibly indicates that status line is being reused for wild menu and there
 * is a potential usage conflict due to size differences. */
static void
maybe_grab_statusline(void)
{
	if(cfg.display_statusline && !curr_stats.reusing_statusline &&
			cfg.wild_menu && vle_compl_get_count() > 2 && getmaxy(stat_win) > 1)
	{
		curr_stats.reusing_statusline = 1;
		wresize(stat_win, compute_wild_menu_height(), getmaxx(stdscr));
		modcline_redraw();
	}
}

/* Resets wild menu state or (re)draws it on the screen. */
static void
wild_menu_op(WildMenuOp op)
{
	static int last_pos;

	/* This check should go first to ensure that resetting of wild menu is
	 * processed and no returns will break the expected behaviour. */
	if(op == WMO_RESET)
	{
		last_pos = 0;
		return;
	}

	/* Quit after handling the reset to allow state updates even while wild menu
	 * is off. */
	if(!cfg.wild_menu)
	{
		return;
	}

	const int count = vle_compl_get_count() - 1;

	/* Makes sense to show even single completion item for automatic
	 * completion. */
	const int min_count = (input_stat.inc_completion ? 1 : 2);
	if(input_stat.complete == NULL || count < min_count)
	{
		return;
	}

	int pos = vle_compl_get_pos();

	if(pos == 0 || pos == count)
		last_pos = 0;
	if(last_pos == 0 && pos == count - 1)
		last_pos = count;

	int width_left = getmaxx(stdscr);
	int last_visible = cfg.wild_popup
	                 ? draw_wild_popup(&last_pos, &pos)
	                 : draw_wild_bar(&last_pos, &pos, &width_left);

	if(pos > 0 && pos != count)
	{
		last_pos = pos;
		wild_menu_op(op);
		return;
	}
	if(op == WMO_DRAW && width_left < 2 && last_visible - 1 == pos)
		last_pos = last_visible;
	ui_refresh_win(stat_win);

	update_cursor();
}

/* Draws wild menu as a bar.  Returns index of the last displayed item. */
static int
draw_wild_bar(int *last_pos, int *pos, int *len)
{
	int i;

	const vle_compl_t *const items = vle_compl_get_items();
	const int count = vle_compl_get_count() - 1;

	wresize(stat_win, compute_wild_menu_height(), getmaxx(stdscr));
	checked_wmove(stat_win, 0, 0);
	werase(stat_win);
	ui_stat_reposition(getmaxy(status_bar), 1);

	if(*pos < *last_pos)
	{
		int l = *len;
		while(*last_pos > 0 && l > 2)
		{
			--*last_pos;

			char *escaped = escape_unreadable(items[*last_pos].text);
			l -= strlen(escaped);
			free(escaped);

			if(*last_pos != 0)
				l -= 2;
		}
		if(l < 2)
			++*last_pos;
	}

	col_attr_t wild_box_col = cfg.cs.color[STATUS_LINE_COLOR];
	cs_mix_colors(&wild_box_col, &cfg.cs.color[WILD_BOX_COLOR]);
	int wild_box_pair = cs_load_color(&wild_box_col);
	ui_set_attr(stat_win, &wild_box_col, wild_box_pair);

	/* Clear background where there are no completion items. */
	ui_set_bg(stat_win, &wild_box_col, wild_box_pair);
	werase(stat_win);

	for(i = *last_pos; i < count && *len > 0; ++i)
	{
		char *escaped = escape_unreadable(items[i].text);

		*len -= strlen(escaped);
		if(i != 0)
			*len -= 2;

		if(i == *last_pos && *last_pos > 0)
		{
			wprintw(stat_win, "< ");
		}
		else if(i > *last_pos)
		{
			if(*len < 2)
			{
				free(escaped);
				wprintw(stat_win, " >");
				break;
			}
			wprintw(stat_win, "  ");
		}

		if(i == *pos)
		{
			col_attr_t col = wild_box_col;
			cs_mix_colors(&col, &cfg.cs.color[WILD_MENU_COLOR]);
			ui_set_attr(stat_win, &col, /*pair=*/-1);
		}
		wprint(stat_win, escaped);
		if(i == *pos)
		{
			ui_set_attr(stat_win, &wild_box_col, wild_box_pair);
			*pos = -*pos;
		}

		free(escaped);
	}

	return i;
}

/* Draws wild menu as a popup.  Returns index of the last displayed item. */
static int
draw_wild_popup(int *last_pos, int *pos)
{
	const vle_compl_t *const items = vle_compl_get_items();
	const int count = vle_compl_get_count() - 1;
	const int height = compute_wild_menu_height();
	size_t max_title_width;
	int i, j;

	if(*pos < *last_pos)
	{
		*last_pos = MAX(0, *last_pos - height);
	}

	ui_stat_reposition(getmaxy(status_bar), height);
	wresize(stat_win, height, getmaxx(stdscr));

	col_attr_t wild_box_col = cfg.cs.color[STATUS_LINE_COLOR];
	cs_mix_colors(&wild_box_col, &cfg.cs.color[WILD_BOX_COLOR]);
	ui_set_attr(stat_win, &wild_box_col, /*pair=*/-1);

	werase(stat_win);

	max_title_width = 0U;
	for(i = *last_pos, j = 0; i < count && j < height; ++i, ++j)
	{
		char *escaped = escape_unreadable(items[i].text);
		const size_t width = utf8_strsw(escaped);
		free(escaped);

		if(width > max_title_width)
		{
			max_title_width = width;
		}
	}

	for(i = *last_pos, j = 0; i < count && j < height; ++i, ++j)
	{
		if(i == *pos)
		{
			col_attr_t col = wild_box_col;
			cs_mix_colors(&col, &cfg.cs.color[WILD_MENU_COLOR]);
			ui_set_attr(stat_win, &col, /*pair=*/-1);
		}

		checked_wmove(stat_win, j, 0);
		char *escaped = escape_unreadable(items[i].text);
		ui_stat_draw_popup_line(stat_win, escaped, items[i].descr, max_title_width);
		free(escaped);

		if(i == *pos)
		{
			ui_set_attr(stat_win, &wild_box_col, /*pair=*/-1);
			*pos = -*pos;
		}
	}

	return i;
}

/* Computes height needed for wild menu (bar or popup).  Returns the height. */
static int
compute_wild_menu_height(void)
{
	if(!cfg.wild_popup)
	{
		return 1;
	}

	const int term_height = getmaxy(stdscr);
	const int count = vle_compl_get_count() - 1;

	const int height_limit = MAX(10, term_height*2/5);
	const int max_height = term_height - getmaxy(status_bar)
	                     - ui_stat_job_bar_height() - 1;
	return MIN(count, MIN(height_limit, max_height));
}

/* Leaves navigation mode without undoing cursor position or filter state. */
static void
cmd_ctrl_j(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		if(input_stat.sub_mode == CLS_FILTER)
		{
			local_filter_accept(curr_view, /*update_history=*/0);
		}
		leave_cmdline_mode(/*cancelled=*/0);
	}
}

static void
cmd_ctrl_k(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(input_stat.index == input_stat.len)
		return;

	wcsdel(input_stat.line, input_stat.index + 1,
			input_stat.len - input_stat.index);
	input_stat.len = input_stat.index;

	update_cmdline_text(&input_stat);
}

static void
cmd_return(key_info_t key_info, keys_info_t *keys_info)
{
	/* TODO: refactor this cmd_return() function. */
	if(input_stat.navigating)
	{
		nav_open();
		return;
	}

	stop_completion();
	werase(status_bar);
	wnoutrefresh(status_bar);

	CmdLineSubmode sub_mode = input_stat.sub_mode;

	if(is_input_line_empty() && sub_mode == CLS_MENU_COMMAND)
	{
		leave_cmdline_mode(/*cancelled=*/0);
		return;
	}

	if(expand_abbrev() != 0)
	{
		return;
	}

	char *input = to_multibyte(input_stat.line);
	save_input_to_history(keys_info, input);

	const int prev_mode = input_stat.prev_mode;
	struct menu_data_t *const menu = input_stat.menu;
	const prompt_cb prompt_callback = input_stat.prompt_callback;
	void *const prompt_callback_arg = input_stat.prompt_callback_arg;
	const int search_mode = input_stat.search_mode;
	leave_cmdline_mode(/*cancelled=*/0);

	if(prev_mode == VISUAL_MODE && sub_mode != CLS_VFSEARCH &&
			sub_mode != CLS_VBSEARCH)
	{
		modvis_leave(curr_stats.save_msg, 1, 0);
	}

	if(sub_mode == CLS_COMMAND || sub_mode == CLS_MENU_COMMAND)
	{
		const CmdInputType cmd_type = (sub_mode == CLS_COMMAND)
		                            ? CIT_COMMAND
		                            : CIT_MENU_COMMAND;

		const char *real_start = input;
		while(*real_start == ' ' || *real_start == ':')
		{
			real_start++;
		}

		if(sub_mode == CLS_COMMAND)
		{
			cmds_scope_start();
		}
		curr_stats.save_msg = cmds_dispatch(real_start, curr_view, cmd_type);
		if(sub_mode == CLS_COMMAND)
		{
			if(cmds_scope_finish() != 0)
			{
				curr_stats.save_msg = 1;
			}
		}
	}
	else if(sub_mode == CLS_PROMPT)
	{
		finish_prompt_submode(input, prompt_callback, prompt_callback_arg);
	}
	else if(sub_mode == CLS_FILTER)
	{
		if(cfg.inc_search)
		{
			local_filter_accept(curr_view, /*update_history=*/1);
		}
		else
		{
			local_filter_apply(curr_view, input);
			ui_view_schedule_reload(curr_view);
		}
	}
	else if(!cfg.inc_search || prev_mode == VIEW_MODE || input[0] == '\0')
	{
		const char *const pattern = (input[0] == '\0') ? hists_search_last()
		                                               : input;

		switch(sub_mode)
		{
			case CLS_FSEARCH:
			case CLS_BSEARCH:
			case CLS_VFSEARCH:
			case CLS_VBSEARCH:
			case CLS_VWFSEARCH:
			case CLS_VWBSEARCH:
				{
					const CmdInputType cit = search_cls_to_cit(sub_mode);
					curr_stats.save_msg = cmds_dispatch1(pattern, curr_view, cit);
					break;
				}
			case CLS_MENU_FSEARCH:
			case CLS_MENU_BSEARCH:
				stats_refresh_later();
				curr_stats.save_msg = menus_search(pattern, menu, 1);
				break;

			default:
				assert(0 && "Unknown command line submode.");
				break;
		}
	}
	else if(cfg.inc_search && search_mode)
	{
		if(prev_mode == MENU_MODE)
		{
			modmenu_partial_redraw();
			menus_search_print_msg(menu);
			curr_stats.save_msg = 1;
		}
		else if(get_current_entry(curr_view)->search_match == 0 &&
				input_stat.search_match_found)
		{
			/* The cursor might have been intentionally moved not by the search.
			 * Don't show any message. */
		}
		else
		{
			curr_stats.save_msg = print_search_result(curr_view,
					input_stat.search_match_found, is_backward_search(sub_mode),
					&print_search_msg);
		}
	}

	free(input);
}

/* Opens current entry while navigating. */
static void
nav_open(void)
{
	dir_entry_t *curr = get_current_entry(curr_view);
	if(fentry_is_fake(curr))
	{
		return;
	}

	int is_dir_like = (fentry_is_dir(curr) || is_unc_root(curr_view->curr_dir));

	CmdLineSubmode sub_mode = input_stat.sub_mode;
	if(sub_mode == CLS_FILTER)
	{
		/* Accepting filter changes list of files, but in case of error it might be
		 * better. */
		local_filter_accept(curr_view, /*update_history=*/0);
	}

	if(is_dir_like)
	{
		char *initial = to_multibyte(input_stat.line);
		if(rn_enter_dir(curr_view) == 0)
		{
			replace_string(&initial, "");
		}

		/* Auto-commands on entering a directory can do something that will require
		 * a postponed view update.  Do it here, so that we don't work with file
		 * list that's about to be changed.  In particular re-entering submode saves
		 * top and cursor positions. */
		switch(ui_view_query_scheduled_event(curr_view))
		{
			case UUE_NONE:
				/* Nothing to do. */
				break;
			case UUE_REDRAW:
				ui_view_schedule_redraw(curr_view);
				break;
			case UUE_RELOAD:
				load_saving_pos(curr_view);
				break;
		}

		if(initial != NULL)
		{
			enter_submode(sub_mode, initial, /*reenter=*/1);
			nav_start(&input_stat);

			free(initial);
		}
	}
	else
	{
		leave_cmdline_mode(/*cancelled=*/0);
		if(cfg.nav_open_files)
		{
			rn_open(curr_view, FHE_RUN);
		}
	}
}

/* Checks whether input line is empty.  Returns non-zero if so, otherwise
 * non-zero is returned. */
static int
is_input_line_empty(void)
{
	return input_stat.line[0] == L'\0';
}

/* Expands abbreviation to the left of current cursor position, if any
 * present, otherwise does nothing.  Returns non-zero if an ongoing command-line
 * mode operation should be considered done. */
static int
expand_abbrev(void)
{
	/* Don't expand command-line abbreviations in navigation and avoid recursion
	 * on expanding abbreviations. */
	if(input_stat.navigating || input_stat.expanding_abbrev)
	{
		return 0;
	}

	int pos;
	int no_remap;
	const wchar_t *abbrev_rhs = extract_abbrev(&input_stat, &pos, &no_remap);
	if(abbrev_rhs == NULL)
	{
		return 0;
	}

	input_stat.expanding_abbrev = 1;
	exec_abbrev(abbrev_rhs, no_remap, pos);
	input_stat.expanding_abbrev = 0;
	if(!is_cmdmode(vle_mode_get()))
	{
		/* We must stop processing command-line mode as it has already been left. */
		return 1;
	}

	update_cmdline_size();
	update_cmdline_text(&input_stat);
	return 0;
}

/* Lookups for an abbreviation to the left of cursor position.  Returns RHS for
 * the abbreviation and fills pointer arguments if found, otherwise NULL is
 * returned. */
TSTATIC const wchar_t *
extract_abbrev(line_stats_t *stat, int *pos, int *no_remap)
{
	const int i = stat->index;
	wchar_t *const line = stat->line;

	int l;
	wchar_t c;
	const wchar_t *abbrev_rhs;

	l = i;
	while(l > 0 && cfg_is_word_wchar(line[l - 1]))
	{
		--l;
	}

	if(l == i)
	{
		return NULL;
	}

	c = line[i];
	line[i] = L'\0';
	abbrev_rhs = vle_abbr_expand(&line[l], no_remap);
	line[i] = c;

	*pos = l;

	return abbrev_rhs;
}

/* Runs RHS of an abbreviation after removing LHS that starts at the pos. */
static void
exec_abbrev(const wchar_t abbrev_rhs[], int no_remap, int pos)
{
	const size_t lhs_len = input_stat.index - pos;

	input_stat.len -= lhs_len;
	input_stat.index -= lhs_len;
	input_stat.curs_pos -= esc_wcswidth(&input_stat.line[pos], lhs_len);
	(void)wcsdel(input_stat.line, pos + 1, lhs_len);

	if(no_remap)
	{
		(void)vle_keys_exec_timed_out_no_remap(abbrev_rhs);
	}
	else
	{
		(void)vle_keys_exec_timed_out(abbrev_rhs);
	}
}

/* Saves command-line input into appropriate history.  input can be NULL, in
 * which case it's ignored. */
static void
save_input_to_history(const keys_info_t *keys_info, const char input[])
{
	if(input_stat.search_mode)
	{
		hists_search_save(input);
	}
	else if(ONE_OF(input_stat.sub_mode, CLS_COMMAND, CLS_MENU_COMMAND))
	{
		/* XXX: skipping should probably work for all histories. */
		const int mapped_input = input_stat.enter_mapping_state != 0 &&
			vle_keys_mapping_state() == input_stat.enter_mapping_state;
		const int ignore_input = mapped_input || keys_info->recursive;
		if(!ignore_input)
		{
			if(input_stat.sub_mode == CLS_COMMAND)
			{
				hists_commands_save(input);
			}
			else
			{
				hists_menucmd_save(input);
			}
		}
	}
	else if(input_stat.sub_mode == CLS_PROMPT)
	{
		const int is_expr_reg = is_cmdmode(input_stat.prev_mode);
		save_prompt_to_history(input, is_expr_reg);
	}
}

/* Save prompt input to history. */
static void
save_prompt_to_history(const char input[], int is_expr_reg)
{
	if(is_expr_reg)
	{
		hists_exprreg_save(input);
	}
	else
	{
		hists_prompt_save(input);
	}
}

/* Performs final actions on successful querying of prompt input. */
static void
finish_prompt_submode(const char input[], prompt_cb cb, void *cb_arg)
{
	modes_post();
	modes_pre();

	cb(input, cb_arg);
}

/* Converts search command-line sub-mode to type of command input.  Returns
 * (CmdInputType)-1 if there is no appropriate command type. */
static CmdInputType
search_cls_to_cit(CmdLineSubmode sub_mode)
{
	switch(sub_mode)
	{
		case CLS_FSEARCH:   return CIT_FSEARCH_PATTERN;
		case CLS_BSEARCH:   return CIT_BSEARCH_PATTERN;
		case CLS_VFSEARCH:  return CIT_VFSEARCH_PATTERN;
		case CLS_VBSEARCH:  return CIT_VBSEARCH_PATTERN;
		case CLS_VWFSEARCH: return CIT_VWFSEARCH_PATTERN;
		case CLS_VWBSEARCH: return CIT_VWBSEARCH_PATTERN;

		default:
			assert(0 && "Unknown search command-line submode.");
			return (CmdInputType)-1;
	}
}

/* Checks whether specified mode is one of forward searching modes.  Returns
 * non-zero if it is, otherwise zero is returned. */
static int
is_forward_search(CmdLineSubmode sub_mode)
{
	return sub_mode == CLS_FSEARCH
	    || sub_mode == CLS_VFSEARCH
	    || sub_mode == CLS_MENU_FSEARCH
	    || sub_mode == CLS_VWFSEARCH;
}

/* Checks whether specified mode is one of backward searching modes.  Returns
 * non-zero if it is, otherwise zero is returned. */
static int
is_backward_search(CmdLineSubmode sub_mode)
{
	return sub_mode == CLS_BSEARCH
	    || sub_mode == CLS_VBSEARCH
	    || sub_mode == CLS_MENU_BSEARCH
	    || sub_mode == CLS_VWBSEARCH;
}

/* Replaces *str with a copy of the with string.  *str can be NULL or equal to
 * the with (then function does nothing).  Returns non-zero if memory allocation
 * failed. */
static int
replace_wstring(wchar_t **str, const wchar_t with[])
{
	if(*str != with)
	{
		wchar_t *const new = vifm_wcsdup(with);
		if(new == NULL)
		{
			return 1;
		}
		free(*str);
		*str = new;
	}
	return 0;
}

/* Recalls a newer historical entry or moves view cursor if navigating. */
static void
cmd_ctrl_n(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		if(curr_view->list_pos < curr_view->list_rows - 1)
		{
			move_view_cursor(&input_stat, curr_view, curr_view->list_pos + 1);
		}
		return;
	}

	stop_completion();

	(void)hist_search_setup(HIST_GO);
	hist_next(&input_stat, pick_hist(), cfg.history_len);
}

/* Puts next element in the history or restores user input if end of history has
 * been reached.  hist can be NULL, in which case nothing happens. */
static void
hist_next(line_stats_t *stat, const hist_t *hist, size_t len)
{
	if(len == 0 || hist == NULL || hist_is_empty(hist))
	{
		return;
	}

	if(stat->hist_search != HIST_SEARCH)
	{
		if(stat->cmd_pos <= 0)
		{
			hist_search_reset_input();
			return;
		}
		--stat->cmd_pos;
	}
	else
	{
		int pos = stat->cmd_pos;
		int len = stat->hist_search_len;
		while(--pos >= 0)
		{
			wchar_t *const buf = to_wide(hist->items[pos].text);
			if(wcsncmp(stat->line, buf, len) == 0)
			{
				free(buf);
				break;
			}
			free(buf);
		}
		if(pos < 0)
		{
			hist_search_reset_input();
			return;
		}
		stat->cmd_pos = pos;
	}

	(void)replace_input_line(stat, hist->items[stat->cmd_pos].text);

	update_cmdline(stat);

	if(stat->cmd_pos > (int)len - 1)
	{
		stat->cmd_pos = len - 1;
	}
}

/* Restores original user input saved by hist_search_setup(). */
static void
hist_search_reset_input(void)
{
	input_stat.cmd_pos = -1;
	(void)replace_wstring(&input_stat.line, input_stat.hist_search_stash);
	input_stat.len = wcslen(input_stat.line);
	update_cmdline(&input_stat);
}

/* Invokes expression register prompt. */
static void
cmd_ctrl_requals(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(!is_cmdmode(input_stat.prev_mode))
	{
		modcline_prompt("(=)", "", &expr_reg_prompt_cb, /*cb_arg=*/NULL,
				&expr_reg_prompt_completion);
	}
}

/* Handles result of expression register prompt. */
static void
expr_reg_prompt_cb(const char expr[], void *arg)
{
	/* Try to parse expr and convert the result to string on success. */
	parsing_result_t result = vle_parser_eval(expr, /*interactive=*/0);
	if(result.error != PE_NO_ERROR)
	{
		/* TODO: maybe print error message on status bar. */
		var_free(result.value);
		return;
	}

	char *res_str = var_to_str(result.value);
	var_free(result.value);

	wchar_t *wide = to_wide_force(res_str);
	free(res_str);

	if(insert_str(wide) == 0)
	{
		update_cmdline_text(&input_stat);
	}

	free(wide);
}

/* Completion for expression register prompt.  Returns completion start
 * offset. */
static int
expr_reg_prompt_completion(const char cmd[], void *arg)
{
	const char *start;
	complete_expr(cmd, &start);
	return (start - cmd);
}

static void
cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(input_stat.index == 0)
		return;

	input_stat.len -= input_stat.index;

	input_stat.curs_pos = input_stat.prompt_wid;
	wcsdel(input_stat.line, 1, input_stat.index);

	input_stat.index = 0;

	werase(status_bar);
	compat_mvwaddwstr(status_bar, 0, 0, input_stat.prompt);
	compat_mvwaddwstr(status_bar, 0, input_stat.prompt_wid, input_stat.line);

	update_cmdline_text(&input_stat);
}

/* Handler of Ctrl-W shortcut, which remove all to the left until beginning of
 * the word is found (i.e. until first delimiter character). */
static void
cmd_ctrl_w(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	int old = input_stat.index;

	while(input_stat.index > 0 && iswspace(input_stat.line[input_stat.index - 1]))
	{
		input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index - 1]);
		input_stat.index--;
	}
	if(iswalnum(input_stat.line[input_stat.index - 1]))
	{
		while(input_stat.index > 0 &&
				iswalnum(input_stat.line[input_stat.index - 1]))
		{
			const wchar_t curr_wchar = input_stat.line[input_stat.index - 1];
			input_stat.curs_pos -= esc_wcwidth(curr_wchar);
			input_stat.index--;
		}
	}
	else
	{
		while(input_stat.index > 0 &&
				!iswalnum(input_stat.line[input_stat.index - 1]) &&
				!iswspace(input_stat.line[input_stat.index - 1]))
		{
			const wchar_t curr_wchar = input_stat.line[input_stat.index - 1];
			input_stat.curs_pos -= esc_wcwidth(curr_wchar);
			input_stat.index--;
		}
	}

	if(input_stat.index != old)
	{
		wcsdel(input_stat.line, input_stat.index + 1, old - input_stat.index);
		input_stat.len -= old - input_stat.index;
	}

	update_cmdline_text(&input_stat);
}

/* Inserts last pattern from search history into current cursor position. */
static void
cmd_ctrl_xslash(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(hists_search_last(), 0);
}

/* Inserts value of automatic filter of active pane into current cursor
 * position. */
static void
cmd_ctrl_xa(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(curr_view->auto_filter.raw, 0);
}

/* Inserts name of the current file of active pane into current cursor
 * position. */
static void
cmd_ctrl_xc(key_info_t key_info, keys_info_t *keys_info)
{
	paste_short_path(curr_view);
}

/* Inserts name of the current file of inactive pane into current cursor
 * position. */
static void
cmd_ctrl_xxc(key_info_t key_info, keys_info_t *keys_info)
{
	paste_short_path(other_view);
}

/* Pastes short path of the current entry of the view into current cursor
 * position. */
static void
paste_short_path(view_t *view)
{
	if(flist_custom_active(view))
	{
		char short_path[PATH_MAX + 1];
		get_short_path_of(view, get_current_entry(view), NF_NONE, 0,
				sizeof(short_path), short_path);
		paste_str(short_path, 1);
		return;
	}

	paste_str(get_current_file_name(view), 1);
}

/* Inserts path to the current directory of active pane into current cursor
 * position. */
static void
cmd_ctrl_xd(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(flist_get_dir(curr_view), 1);
}

/* Inserts path to the current directory of inactive pane into current cursor
 * position. */
static void
cmd_ctrl_xxd(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(flist_get_dir(other_view), 1);
}

/* Inserts extension of the current file of active pane into current cursor
 * position. */
static void
cmd_ctrl_xe(key_info_t key_info, keys_info_t *keys_info)
{
	paste_name_part(get_current_file_name(curr_view), 0);
}

/* Inserts extension of the current file of inactive pane into current cursor
 * position. */
static void
cmd_ctrl_xxe(key_info_t key_info, keys_info_t *keys_info)
{
	paste_name_part(get_current_file_name(other_view), 0);
}

/* Inserts value of manual filter of active pane into current cursor
 * position. */
static void
cmd_ctrl_xm(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(matcher_get_undec(curr_view->manual_filter), 0);
}

/* Inserts name root of current file of active pane into current cursor
 * position. */
static void
cmd_ctrl_xr(key_info_t key_info, keys_info_t *keys_info)
{
	paste_short_path_root(curr_view);
}

/* Inserts name root of current file of inactive pane into current cursor
 * position. */
static void
cmd_ctrl_xxr(key_info_t key_info, keys_info_t *keys_info)
{
	paste_short_path_root(other_view);
}

/* Pastes short root of the current entry of the view into current cursor
 * position. */
static void
paste_short_path_root(view_t *view)
{
	char short_path[PATH_MAX + 1];
	get_short_path_of(view, get_current_entry(view), NF_NONE, 0,
			sizeof(short_path), short_path);
	paste_name_part(short_path, 1);
}

/* Inserts last component of path to the current directory of active pane into
 * current cursor position. */
static void
cmd_ctrl_xt(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(get_last_path_component(flist_get_dir(curr_view)), 1);
}

/* Inserts last component of path to the current directory of inactive pane into
 * current cursor position. */
static void
cmd_ctrl_xxt(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(get_last_path_component(flist_get_dir(other_view)), 1);
}

/* Inserts value of local filter of active pane into current cursor position. */
static void
cmd_ctrl_xequals(key_info_t key_info, keys_info_t *keys_info)
{
	paste_str(curr_view->local_filter.filter.raw, 0);
}

/* Inserts root/extension of the name file into current cursor position. */
static void
paste_name_part(const char name[], int root)
{
	int root_len;
	const char *ext;
	char *const name_copy = strdup(name);

	split_ext(name_copy, &root_len, &ext);
	paste_str(root ? name_copy : ext, 1);

	free(name_copy);
}

/* Inserts string into current cursor position and updates command-line on the
 * screen. */
static void
paste_str(const char str[], int allow_escaping)
{
	char *escaped;
	wchar_t *wide;

	if(input_stat.prev_mode == MENU_MODE)
	{
		return;
	}

	stop_completion();

	escaped = (allow_escaping && input_stat.sub_mode == CLS_COMMAND)
	        ? escape_cmd_for_pasting(str)
	        : NULL;
	if(escaped != NULL)
	{
		str = escaped;
	}

	wide = to_wide_force(str);

	if(insert_str((str[0] != '\0' && wide[0] == L'\0') ? L"?" : wide) == 0)
	{
		update_cmdline_text(&input_stat);
	}

	free(wide);
	free(escaped);
}

/* Escapes str for inserting into current position of command-line command when
 * needed.  Returns escaped string or NULL when no escaping is needed. */
static char *
escape_cmd_for_pasting(const char str[])
{
	wchar_t *const wide_input = vifm_wcsdup(input_stat.line);
	if(wide_input == NULL)
	{
		return NULL;
	}

	wide_input[input_stat.index] = L'\0';
	char *mb_input = to_multibyte(wide_input);
	free(wide_input);

	char *escaped = cmds_insertion_escape(mb_input, strlen(mb_input), str);
	free(mb_input);

	return escaped;
}

static void
cmd_ctrl_underscore(key_info_t key_info, keys_info_t *keys_info)
{
	if(!input_stat.complete_continue)
	{
		return;
	}

	/* Restore initial user input (before completion). */
	vle_compl_rewind();
	input_stat.reverse_completion = 0;
	if(input_stat.complete != NULL)
	{
		line_completion(&input_stat);
	}

	stop_completion();
}

static void
cmd_meta_b(key_info_t key_info, keys_info_t *keys_info)
{
	find_prev_word();
	update_cursor();
}

/* Moves cursor to the beginning of the current or previous word on Meta+B. */
static void
find_prev_word(void)
{
	while(input_stat.index > 0 &&
			!cfg_is_word_wchar(input_stat.line[input_stat.index - 1]))
	{
		input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index - 1]);
		input_stat.index--;
	}
	while(input_stat.index > 0 &&
			cfg_is_word_wchar(input_stat.line[input_stat.index - 1]))
	{
		input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index - 1]);
		input_stat.index--;
	}
}

static void
cmd_meta_d(key_info_t key_info, keys_info_t *keys_info)
{
	int old_i, old_c;

	old_i = input_stat.index;
	old_c = input_stat.curs_pos;
	find_next_word();

	if(input_stat.index == old_i)
	{
		return;
	}

	wcsdel(input_stat.line, old_i + 1, input_stat.index - old_i);
	input_stat.len -= input_stat.index - old_i;
	input_stat.index = old_i;
	input_stat.curs_pos = old_c;

	update_cmdline_text(&input_stat);
}

static void
cmd_meta_f(key_info_t key_info, keys_info_t *keys_info)
{
	find_next_word();
	update_cursor();
}

/* Inserts last part of previous command to current cursor position.  Each next
 * call will insert last part of older command. */
static void
cmd_meta_dot(key_info_t key_info, keys_info_t *keys_info)
{
	wchar_t *wide;

	if(input_stat.sub_mode != CLS_COMMAND)
	{
		return;
	}

	stop_regular_completion();

	if(hist_is_empty(&curr_stats.cmd_hist))
	{
		return;
	}

	if(input_stat.dot_pos < 0)
	{
		input_stat.dot_pos = input_stat.cmd_pos + 1;
	}
	else
	{
		remove_previous_dot_completion();
	}

	wide = next_dot_completion();

	if(insert_dot_completion(wide) == 0)
	{
		update_cmdline_text(&input_stat);
	}
	else
	{
		stop_dot_completion();
	}

	free(wide);
}

/* Removes previous dot completion from any part of command line. */
static void
remove_previous_dot_completion(void)
{
	size_t start = input_stat.dot_index;
	size_t end = start + input_stat.dot_len;
	memmove(input_stat.line + start, input_stat.line + end,
			sizeof(wchar_t)*(input_stat.len - end + 1));
	input_stat.len -= input_stat.dot_len;
	input_stat.index -= input_stat.dot_len;
	input_stat.curs_pos -= input_stat.dot_len;
}

/* Gets next dot completion from history of commands. Returns new string, caller
 * should free it. */
static wchar_t *
next_dot_completion(void)
{
	if(input_stat.dot_pos >= curr_stats.cmd_hist.size)
	{
		return vifm_wcsdup(L"");
	}

	size_t len;
	const char *last_entry = curr_stats.cmd_hist.items[input_stat.dot_pos++].text;
	const char *last_arg_pos = vle_cmds_last_arg(last_entry, 1, &len);

	char *last_arg = format_str("%.*s", (int)len, last_arg_pos);
	wchar_t *wide = to_wide(last_arg);
	free(last_arg);

	return wide;
}

/* Inserts dot completion into current cursor position in the command line.
 * Returns zero on success. */
static int
insert_dot_completion(const wchar_t completion[])
{
	const int dot_index = input_stat.index;
	if(insert_str(completion) == 0)
	{
		input_stat.dot_index = dot_index;
		input_stat.dot_len = wcslen(completion);
		return 0;
	}
	return 1;
}

/* Inserts wide string into current cursor position.  Returns zero on success,
 * otherwise non-zero is returned. */
static int
insert_str(const wchar_t str[])
{
	const size_t len = wcslen(str);
	const size_t new_len = input_stat.len + len + 1;

	wchar_t *const ptr = reallocarray(input_stat.line, new_len, sizeof(wchar_t));
	if(ptr == NULL)
	{
		return 1;
	}
	input_stat.line = ptr;

	wcsins(input_stat.line, str, input_stat.index + 1);

	input_stat.index += len;
	input_stat.curs_pos += esc_wcswidth(str, (size_t)-1);
	input_stat.len += len;

	return 0;
}

/* Moves cursor to the end of the current or next word on Meta+F. */
static void
find_next_word(void)
{
	while(input_stat.index < input_stat.len
			&& !cfg_is_word_wchar(input_stat.line[input_stat.index]))
	{
		input_stat.curs_pos += esc_wcwidth(input_stat.line[input_stat.index]);
		input_stat.index++;
	}
	while(input_stat.index < input_stat.len
			&& cfg_is_word_wchar(input_stat.line[input_stat.index]))
	{
		input_stat.curs_pos += esc_wcwidth(input_stat.line[input_stat.index]);
		input_stat.index++;
	}
}

static void
cmd_delete(key_info_t key_info, keys_info_t *keys_info)
{
	switch_to_editing();

	if(input_stat.index == input_stat.len)
		return;

	wcsdel(input_stat.line, input_stat.index+1, 1);
	input_stat.len--;

	update_cmdline_text(&input_stat);
}

static void
update_cursor(void)
{
	checked_wmove(status_bar, input_stat.curs_pos/line_width,
			input_stat.curs_pos%line_width);
}

/* Replaces current input with specified string.  Returns zero on success,
 * otherwise non-zero is returned. */
static int
replace_input_line(line_stats_t *stat, const char new[])
{
	wchar_t *const wide_new = to_wide(new);
	if(wide_new == NULL)
	{
		return 1;
	}

	free(stat->line);
	stat->line = wide_new;
	stat->len = wcslen(wide_new);
	return 0;
}

/* Goes to parent directory when in navigation. */
static void
cmd_ctrl_o(key_info_t key_info, keys_info_t *keys_info)
{
	if(!input_stat.navigating)
	{
		return;
	}

	CmdLineSubmode sub_mode = input_stat.sub_mode;
	if(sub_mode == CLS_FILTER)
	{
		local_filter_accept(curr_view, /*update_history=*/0);
	}

	rn_leave(curr_view, /*levels=*/1);
	enter_submode(sub_mode, /*initial=*/"", /*reenter=*/1);
	nav_start(&input_stat);
}

/* Recalls an older historical entry or moves view cursor if navigating. */
static void
cmd_ctrl_p(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		if(curr_view->list_pos > 0)
		{
			move_view_cursor(&input_stat, curr_view, curr_view->list_pos - 1);
		}
		return;
	}

	stop_completion();

	(void)hist_search_setup(HIST_GO);
	hist_prev(&input_stat, pick_hist(), cfg.history_len);
}

/* Moves cursor in the view and updates information related to its position. */
static void
move_view_cursor(line_stats_t *stat, view_t *view, int new_pos)
{
	fpos_set_pos(view, new_pos);
	update_view_cursor(stat, view);
}

void
modcline_update_curr_view_cursor(void)
{
	update_view_cursor(&input_stat, curr_view);
	if(input_stat.prev_mode == VISUAL_MODE)
	{
		modvis_update();
	}
}

/* Updates information related to cursor position. */
static void
update_view_cursor(line_stats_t *stat, view_t *view)
{
	if(stat->sub_mode == CLS_FILTER)
	{
		local_filter_update_pos(view);
	}
	else
	{
		save_view_port(stat, view);
	}
}

/* Swaps the order of two characters. */
static void
cmd_ctrl_t(key_info_t key_info, keys_info_t *keys_info)
{
	wchar_t char_before_last;
	int index;

	stop_completion();

	index = input_stat.index;
	if(index == 0 || input_stat.len == 1)
	{
		return;
	}

	if(index == input_stat.len)
	{
		--index;
	}
	else
	{
		input_stat.curs_pos += esc_wcwidth(input_stat.line[input_stat.index]);
		++input_stat.index;
	}

	char_before_last = input_stat.line[index - 1];
	input_stat.line[index - 1] = input_stat.line[index];
	input_stat.line[index] = char_before_last;

	update_cmdline_text(&input_stat);
}

/* Toggles navigation for search/filtering. */
static void
cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info)
{
	if(!input_stat.search_mode && input_stat.sub_mode != CLS_FILTER)
	{
		return;
	}
	if(!cfg.inc_search || input_stat.prev_mode != NORMAL_MODE)
	{
		return;
	}

	if(!input_stat.navigating)
	{
		nav_start(&input_stat);
	}
	else
	{
		nav_stop(&input_stat);
	}
	draw_cmdline_text(&input_stat);
}

/* Enables navigation. */
static void
nav_start(line_stats_t *stat)
{
	stat->navigating = 1;

	size_t nav_prefix_len = wcslen(NAV_PREFIX);

	wchar_t new_prompt[NAME_MAX + 1] = NAV_PREFIX;
	wcsncpy(new_prompt + nav_prefix_len, stat->prompt,
			ARRAY_LEN(new_prompt) - nav_prefix_len - 1);

	wcscpy(stat->prompt, new_prompt);
	stat->prompt_wid += nav_prefix_len;
	stat->curs_pos += nav_prefix_len;

	vle_mode_set(NAV_MODE, VMT_SECONDARY);
}

/* Disables navigation. */
static void
nav_stop(line_stats_t *stat)
{
	stat->navigating = 0;

	size_t nav_prefix_len = wcslen(NAV_PREFIX);

	memmove(stat->prompt, &stat->prompt[nav_prefix_len],
			sizeof(stat->prompt) - sizeof(wchar_t)*nav_prefix_len);
	stat->prompt_wid -= nav_prefix_len;
	stat->curs_pos -= nav_prefix_len;

	vle_mode_set(CMDLINE_MODE, VMT_SECONDARY);
}

#ifdef ENABLE_EXTENDED_KEYS

/* Fetches a matching future historical entry or moves view cursor down in
 * navigation. */
static void
cmd_down(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		cmd_ctrl_n(key_info, keys_info);
		return;
	}

	stop_completion();

	if(hist_search_setup(HIST_SEARCH))
	{
		input_stat.hist_search_len = input_stat.len;
	}

	hist_next(&input_stat, pick_hist(), cfg.history_len);
}

/* Fetches a matching past historical entry or moves view cursor up in
 * navigation. */
static void
cmd_up(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		cmd_ctrl_p(key_info, keys_info);
		return;
	}

	stop_completion();

	if(hist_search_setup(HIST_SEARCH))
	{
		input_stat.hist_search_len = input_stat.len;
	}

	hist_prev(&input_stat, pick_hist(), cfg.history_len);
}

/* Moves command-line cursor to the left or goes to parent directory in
 * navigation. */
static void
cmd_left(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		cmd_ctrl_o(key_info, keys_info);
	}
	else
	{
		cmd_ctrl_b(key_info, keys_info);
	}
}

/* Moves command-line cursor to the right or enter active entry in
 * navigation. */
static void
cmd_right(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		cmd_return(key_info, keys_info);
	}
	else
	{
		cmd_ctrl_f(key_info, keys_info);
	}
}

/* Moves command-line cursor to the beginning of command-line or moves
 * view cursor to the first file in navigation. */
static void
cmd_home(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		fpos_set_pos(curr_view, 0);
	}
	else
	{
		cmd_ctrl_a(key_info, keys_info);
	}
}

/* Moves command-line cursor to the end of command-line or moves view cursor to
 * the last file in navigation. */
static void
cmd_end(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		fpos_set_pos(curr_view, curr_view->list_rows - 1);
	}
	else
	{
		cmd_ctrl_e(key_info, keys_info);
	}
}

/* Scrolls view up in navigation. */
static void
cmd_page_up(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		fview_scroll_page_up(curr_view);
	}
}

/* Scrolls view down in navigation. */
static void
cmd_page_down(key_info_t key_info, keys_info_t *keys_info)
{
	if(input_stat.navigating)
	{
		fview_scroll_page_down(curr_view);
	}
}

#endif /* ENABLE_EXTENDED_KEYS */

/* Makes sure that a historical search of the specified kind is active, saving
 * current command-line input if the search wasn't active at all.  Returns zero
 * if the request kind was already active. */
static int
hist_search_setup(HIST kind)
{
	/* The input needs to be stored only when historical search is initiated. */
	if(input_stat.hist_search == HIST_NONE)
	{
		(void)replace_wstring(&input_stat.hist_search_stash, input_stat.line);
	}

	if(input_stat.hist_search == kind)
	{
		return 0;
	}

	input_stat.hist_search = kind;
	return 1;
}

/* Puts previous element in the history.  hist can be NULL, in which case
 * nothing happens. */
TSTATIC void
hist_prev(line_stats_t *stat, const hist_t *hist, size_t len)
{
	if(len == 0 || hist == NULL || hist_is_empty(hist))
	{
		return;
	}

	if(stat->hist_search != HIST_SEARCH)
	{
		if(stat->cmd_pos == hist->size - 1)
		{
			return;
		}

		++stat->cmd_pos;

		/* Handle the case when the most recent history item equals current input.
		 * This can happen if command-line mode was given some initial input
		 * string.  Initially cmd_pos is -1, no need to check anything if history
		 * contains only one element as even if it's equal input line won't be
		 * changed. */
		if(stat->cmd_pos == 0 && hist->size != 1)
		{
			wchar_t *const wide_item = to_wide(hist->items[0].text);
			if(wcscmp(stat->line, wide_item) == 0)
			{
				++stat->cmd_pos;
			}
			free(wide_item);
		}
	}
	else
	{
		int pos = stat->cmd_pos;
		int len = stat->hist_search_len;
		while(++pos < hist->size)
		{
			wchar_t *const wide_item = to_wide(hist->items[pos].text);
			if(wcsncmp(stat->line, wide_item, len) == 0)
			{
				free(wide_item);
				break;
			}
			free(wide_item);
		}
		if(pos >= hist->size)
			return;
		stat->cmd_pos = pos;
	}

	(void)replace_input_line(stat, hist->items[stat->cmd_pos].text);

	update_cmdline(stat);

	if(stat->cmd_pos > (int)len - 1)
	{
		stat->cmd_pos = len - 1;
	}
}

/* Picks history depending on sub_mode.  Returns picked history or NULL. */
static const hist_t *
pick_hist(void)
{
	if(input_stat.sub_mode == CLS_COMMAND)
	{
		return &curr_stats.cmd_hist;
	}
	if(input_stat.sub_mode == CLS_MENU_COMMAND)
	{
		return &curr_stats.menucmd_hist;
	}
	if(input_stat.search_mode)
	{
		return &curr_stats.search_hist;
	}
	if(input_stat.sub_mode == CLS_PROMPT)
	{
		if(is_cmdmode(input_stat.prev_mode))
		{
			return &curr_stats.exprreg_hist;
		}
		return &curr_stats.prompt_hist;
	}
	if(input_stat.sub_mode == CLS_FILTER)
	{
		return &curr_stats.filter_hist;
	}
	return NULL;
}

/* Checks for a command-line-like mode. */
static int
is_cmdmode(int mode)
{
  return (mode == CMDLINE_MODE || mode == NAV_MODE);
}

/* Updates command-line properties and redraws it. */
static void
update_cmdline(line_stats_t *stat)
{
	int required_height;
	stat->curs_pos = stat->prompt_wid + esc_wcswidth(stat->line, stat->len);
	stat->index = stat->len;

	required_height = get_required_height();
	if(required_height >= getmaxy(status_bar))
	{
		update_cmdline_size();
	}

	update_cmdline_text(stat);
}

/* Gets status bar height required to display all its content.  Returns the
 * height. */
static int
get_required_height(void)
{
	return DIV_ROUND_UP(input_stat.prompt_wid + input_stat.len + 1, line_width);
}

/* Replaces [ stat->prefix_len : stat->index ) range of stat->line with wide
 * version of the completed parameter.  Returns zero on success, otherwise
 * non-zero is returned. */
static int
line_part_complete(line_stats_t *stat, const char completed[])
{
	void *t;
	wchar_t *line_ending;
	wchar_t *wide_completed = to_wide_force(completed);

	const size_t new_len = stat->prefix_len + wcslen(wide_completed) +
		(stat->len - stat->index) + 1;

	line_ending = vifm_wcsdup(stat->line + stat->index);
	if(line_ending == NULL)
	{
		free(wide_completed);
		return -1;
	}

	t = reallocarray(stat->line, new_len, sizeof(wchar_t));
	if(t == NULL)
	{
		free(wide_completed);
		free(line_ending);
		return -1;
	}
	stat->line = t;

	vifm_swprintf(stat->line + stat->prefix_len, new_len,
			L"%" WPRINTF_WSTR L"%" WPRINTF_WSTR, wide_completed, line_ending);
	free(wide_completed);
	free(line_ending);

	update_line_stat(stat, new_len);
	update_cmdline_size();
	update_cmdline_text(stat);
	return 0;
}

/* Make sure status bar size is taken into account in the TUI. */
static void
update_cmdline_size(void)
{
	const int required_height = MIN(getmaxy(stdscr), get_required_height());
	if(required_height < getmaxy(status_bar))
	{
		/* Do not shrink status bar. */
		return;
	}

	/* This handles case when status bar didn't get larger to cover corner case
	 * updates (e.g. first redraw). */

	mvwin(status_bar, getmaxy(stdscr) - required_height, 0);
	wresize(status_bar, required_height, line_width);

	if(input_stat.prev_mode != MENU_MODE)
	{
		int stat_height = cfg.wild_menu
		               && cfg.wild_popup
		               && input_stat.complete_continue;
		if(ui_stat_reposition(required_height, stat_height ? getmaxy(stat_win) : 0))
		{
			ui_stat_refresh();
		}
	}
	else
	{
		wresize(menu_win, getmaxy(stdscr) - required_height, getmaxx(stdscr));
		modmenu_partial_redraw();
	}
}

/* Returns non-zero on error. */
TSTATIC int
line_completion(line_stats_t *stat)
{
	if(!stat->complete_continue)
	{
		if(start_completion(stat, /*inc_completion=*/0) != 0)
		{
			return -1;
		}
	}

	vle_compl_set_reversed(stat->reverse_completion);

	if(vle_compl_get_count() == 0)
	{
		return 0;
	}

	char *completion = vle_compl_next();
	int result = line_part_complete(stat, completion);
	free(completion);

	if(vle_compl_get_count() >= 2)
	{
		stat->complete_continue = 1;
	}

	return result;
}

/* Generates completion entries for the current state of the command-line.
 * Returns zero on success. */
static int
start_completion(line_stats_t *stat, int inc_completion)
{
	/* Only complete the part before the cursor so just copy that part to
	 * line_mb. */
	wchar_t t = stat->line[stat->index];
	stat->line[stat->index] = L'\0';

	char *const line_mb = to_multibyte(stat->line);
	stat->line[stat->index] = t;
	if(line_mb == NULL)
	{
		return -1;
	}

	const char *line_mb_cmd = line_mb;

	vle_compl_reset();

	CompletionPreProcessing compl_func_arg = CPP_NONE;
	if(stat->sub_mode == CLS_COMMAND || stat->sub_mode == CLS_MENU_COMMAND)
	{
		line_mb_cmd = cmds_find_last(line_mb);

		const CmdLineLocation ipt = cmds_classify_pos(line_mb,
				line_mb + strlen(line_mb));
		switch(ipt)
		{
			case CLL_OUT_OF_ARG:
			case CLL_NO_QUOTING:
			case CLL_R_QUOTING:
				vle_compl_set_add_path_hook(&escaped_arg_hook);
				compl_func_arg = CPP_PERCENT_UNESCAPE;
				break;

			case CLL_S_QUOTING:
				vle_compl_set_add_path_hook(&squoted_arg_hook);
				compl_func_arg = CPP_SQUOTES_UNESCAPE;
				break;

			case CLL_D_QUOTING:
				vle_compl_set_add_path_hook(&dquoted_arg_hook);
				compl_func_arg = CPP_DQUOTES_UNESCAPE;
				break;
		}
	}

	const int offset = stat->complete(line_mb_cmd, (void *)compl_func_arg);
	if(offset >= 0 && offset < (int)strlen(line_mb_cmd))
	{
		line_mb[line_mb_cmd - line_mb + offset] = '\0';
	}
	stat->prefix_len = wide_len(line_mb);
	free(line_mb);

	stat->inc_completion = inc_completion;

	vle_compl_set_add_path_hook(NULL);
	return 0;
}

/* Processes completion match for insertion into command-line as escaped value.
 * Returns newly allocated string. */
static char *
escaped_arg_hook(const char match[])
{
#ifndef _WIN32
	return posix_like_escape(match, /*type=*/1);
#else
	return strdup(escape_for_cd(match));
#endif
}

/* Processes completion match for insertion into command-line as a value in
 * single quotes.  Returns newly allocated string. */
static char *
squoted_arg_hook(const char match[])
{
	return escape_for_squotes(match, 1);
}

/* Processes completion match for insertion into command-line as a value in
 * double quotes.  Returns newly allocated string. */
static char *
dquoted_arg_hook(const char match[])
{
	return escape_for_dquotes(match, 1);
}

/* Recalculates some numeric fields of the stat structure that depend on input
 * string length. */
static void
update_line_stat(line_stats_t *stat, int new_len)
{
	stat->index += (new_len - 1) - stat->len;
	stat->curs_pos = stat->prompt_wid + esc_wcswidth(stat->line, stat->index);
	stat->len = new_len - 1;
}

/* wcswidth()-like function which also accounts for unreadable characters that
 * were escaped. */
static int
esc_wcswidth(const wchar_t str[], size_t n)
{
	int width = 0;
	while(*str != L'\0' && n--)
	{
		width += esc_wcwidth(*str++);
	}
	return width;
}

/* wcwidth()-like function which also accounts for unreadable characters that
 * were escaped. */
static int
esc_wcwidth(wchar_t wc)
{
	/* Escaped unreadable characters have a form of ^X. */
	return (unichar_isprint(wc) ? vifm_wcwidth(wc) : 2);
}

/* Delete a character in a string
 * For example, wcsdelch(L"fooXXbar", 4, 2) returns L"foobar" */
static wchar_t *
wcsdel(wchar_t *src, int pos, int len)
{
	int p;

	pos--;

	for(p = pos; pos - p < len; pos++)
		if(! src[pos])
		{
			src[p] = L'\0';
			return src;
		}

	pos--;

	while(src[++pos] != L'\0')
		src[pos-len] = src[pos];
	src[pos-len] = src[pos];

	return src;
}

/* Makes sure that current state is suitable for making changes to the
 * command-line by aborting any search or completion activities. */
static void
switch_to_editing(void)
{
	input_stat.hist_search = HIST_NONE;
	stop_completion();
}

/* Disables all active completions. */
static void
stop_completion(void)
{
	stop_dot_completion();
	stop_regular_completion();
}

/* Disables dot completion if it's active. */
static void
stop_dot_completion(void)
{
	input_stat.dot_pos = -1;
}

/* Disables tab completion if it's active. */
static void
stop_regular_completion(void)
{
	if(!input_stat.complete_continue)
	{
		return;
	}

	if(curr_stats.reusing_statusline)
	{
		curr_stats.reusing_statusline = 0;
	}

	input_stat.complete_continue = 0;
	vle_compl_reset();
	if(cfg.wild_menu && input_stat.complete != NULL)
	{
		modcline_redraw();
	}
}

int
modcline_complete_dirs(const char str[], void *arg)
{
	int name_offset = after_last(str, '/') - str;
	return name_offset
	     + filename_completion(str, CT_DIRONLY, /*skip_canonicalization=*/0);
}

int
modcline_complete_files(const char str[], void *arg)
{
	int name_offset = after_last(str, '/') - str;
	return name_offset
	     + filename_completion(str, CT_ALL, /*skip_canonicalization=*/0);
}

TSTATIC line_stats_t *
get_line_stats(void)
{
	return &input_stat;
}

/* Processes events from the mouse. */
static void
handle_mouse_event(key_info_t key_info, keys_info_t *keys_info)
{
	MEVENT e;
	if(ui_get_mouse(&e) != OK)
	{
		return;
	}

	if(!wenclose(status_bar, e.y, e.x))
	{
		return;
	}

	if(e.bstate & BUTTON1_PRESSED)
	{
		wmouse_trafo(status_bar, &e.y, &e.x, FALSE);

		int idx = e.y*getmaxx(status_bar) + e.x - input_stat.prompt_wid;
		if(idx < 0)
		{
			idx = 0;
		}
		else if(idx > input_stat.len)
		{
			idx = input_stat.len;
		}

		input_stat.index = idx;
		input_stat.curs_pos = input_stat.index + input_stat.prompt_wid;
		update_cmdline_text(&input_stat);
	}
	else if(e.bstate & BUTTON4_PRESSED)
	{
		cmd_ctrl_p(key_info, keys_info);
	}
	else if(e.bstate & (BUTTON2_PRESSED | BUTTON5_PRESSED))
	{
		cmd_ctrl_n(key_info, keys_info);
	}
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
