/* 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
 */

#include "visual.h"

#include <curses.h>

#include <assert.h> /* assert() */
#include <stddef.h> /* NULL size_t */
#include <string.h>

#include "../cfg/config.h"
#include "../compat/curses.h"
#include "../engine/keys.h"
#include "../engine/mode.h"
#include "../menus/filetypes_menu.h"
#include "../menus/menus.h"
#include "../modes/dialogs/msg_dialog.h"
#include "../ui/fileview.h"
#include "../ui/statusbar.h"
#include "../ui/ui.h"
#include "../utils/hist.h"
#include "../utils/macros.h"
#include "../utils/path.h"
#include "../utils/str.h"
#include "../utils/utils.h"
#include "../cmd_core.h"
#include "../filelist.h"
#include "../filtering.h"
#include "../flist_pos.h"
#include "../flist_sel.h"
#include "../fops_misc.h"
#include "../fops_rename.h"
#include "../marks.h"
#include "../registers.h"
#include "../running.h"
#include "../search.h"
#include "../status.h"
#include "dialogs/attr_dialog.h"
#include "cmdline.h"
#include "modes.h"
#include "normal.h"
#include "wk.h"

/* Types of selection amending. */
typedef enum
{
	AT_NONE,   /* Selection amending is not active, almost same as AT_APPEND. */
	AT_APPEND, /* Amend selection by selecting elements in region. */
	AT_REMOVE, /* Amend selection by deselecting elements in region. */
	AT_INVERT, /* Amend selection by inverting selection of elements in region. */
	AT_COUNT   /* Number of selection amending types. */
}
AmendType;

static void backup_selection_flags(view_t *view);
static void restore_selection_flags(view_t *view);
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_d(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 page_scroll(int base, int direction);
static void cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_l(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 cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ctrl_x(key_info_t key_info, keys_info_t *keys_info);
static void call_incdec(int count);
static void cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info);
static void cmd_quote(key_info_t key_info, keys_info_t *keys_info);
static void sug_quote(vle_keys_list_cb cb);
static void cmd_dollar(key_info_t key_info, keys_info_t *keys_info);
static void cmd_percent(key_info_t key_info, keys_info_t *keys_info);
static void cmd_comma(key_info_t key_info, keys_info_t *keys_info);
static void cmd_zero(key_info_t key_info, keys_info_t *keys_info);
static void cmd_colon(key_info_t key_info, keys_info_t *keys_info);
static void cmd_semicolon(key_info_t key_info, keys_info_t *keys_info);
static void cmd_slash(key_info_t key_info, keys_info_t *keys_info);
static void cmd_question(key_info_t key_info, keys_info_t *keys_info);
static void cmd_C(key_info_t key_info, keys_info_t *keys_info);
static void cmd_D(key_info_t key_info, keys_info_t *keys_info);
static void cmd_F(key_info_t key_info, keys_info_t *keys_info);
static void cmd_G(key_info_t key_info, keys_info_t *keys_info);
static void cmd_H(key_info_t key_info, keys_info_t *keys_info);
static void cmd_L(key_info_t key_info, keys_info_t *keys_info);
static void cmd_M(key_info_t key_info, keys_info_t *keys_info);
static void cmd_N(key_info_t key_info, keys_info_t *keys_info);
static void cmd_O(key_info_t key_info, keys_info_t *keys_info);
static void cmd_d(key_info_t key_info, keys_info_t *keys_info);
static void delete(key_info_t key_info, int use_trash);
static void cmd_av(key_info_t key_info, keys_info_t *keys_info);
static void cmd_cl(key_info_t key_info, keys_info_t *keys_info);
static void cmd_cp(key_info_t key_info, keys_info_t *keys_info);
static void cmd_cw(key_info_t key_info, keys_info_t *keys_info);
static void cmd_f(key_info_t key_info, keys_info_t *keys_info);
static void cmd_gA(key_info_t key_info, keys_info_t *keys_info);
static void cmd_ga(key_info_t key_info, keys_info_t *keys_info);
static void cmd_gg(key_info_t key_info, keys_info_t *keys_info);
static void cmd_gl(key_info_t key_info, keys_info_t *keys_info);
static void cmd_gU(key_info_t key_info, keys_info_t *keys_info);
static void cmd_gu(key_info_t key_info, keys_info_t *keys_info);
static void do_gu(int upper);
static void cmd_gv(key_info_t key_info, keys_info_t *keys_info);
static void restore_previous_selection(void);
static void select_first_one(void);
static void cmd_h(key_info_t key_info, keys_info_t *keys_info);
static void cmd_i(key_info_t key_info, keys_info_t *keys_info);
static void cmd_j(key_info_t key_info, keys_info_t *keys_info);
static void cmd_k(key_info_t key_info, keys_info_t *keys_info);
static void go_to_prev(key_info_t key_info, keys_info_t *keys_info, int def,
		int step);
static void cmd_l(key_info_t key_info, keys_info_t *keys_info);
static void go_to_next(key_info_t key_info, keys_info_t *keys_info, int def,
		int step);
static void cmd_m(key_info_t key_info, keys_info_t *keys_info);
static void cmd_q_colon(key_info_t key_info, keys_info_t *keys_info);
static void cmd_q_slash(key_info_t key_info, keys_info_t *keys_info);
static void cmd_q_question(key_info_t key_info, keys_info_t *keys_info);
static void activate_search(int count, int back, int external);
static void cmd_n(key_info_t key_info, keys_info_t *keys_info);
static void search(key_info_t key_info, int backward);
static void cmd_v(key_info_t key_info, keys_info_t *keys_info);
static void change_amend_type(AmendType new_amend_type);
static void cmd_y(key_info_t key_info, keys_info_t *keys_info);
static void accept_and_leave(int save_msg);
static void reject_and_leave(void);
static void leave_clearing_selection(int go_to_top, int save_msg);
static void update_marks(view_t *view);
static void cmd_zd(key_info_t key_info, keys_info_t *keys_info);
static void cmd_zf(key_info_t key_info, keys_info_t *keys_info);
static void cmd_left_paren(key_info_t key_info, keys_info_t *keys_info);
static void cmd_right_paren(key_info_t key_info, keys_info_t *keys_info);
static void cmd_z_k(key_info_t key_info, keys_info_t *keys_info);
static void cmd_z_j(key_info_t key_info, keys_info_t *keys_info);
static void cmd_lb_c(key_info_t key_info, keys_info_t *keys_info);
static void cmd_rb_c(key_info_t key_info, keys_info_t *keys_info);
static void cmd_lb_d(key_info_t key_info, keys_info_t *keys_info);
static void cmd_rb_d(key_info_t key_info, keys_info_t *keys_info);
static void cmd_lb_s(key_info_t key_info, keys_info_t *keys_info);
static void cmd_rb_s(key_info_t key_info, keys_info_t *keys_info);
static void cmd_lb_S(key_info_t key_info, keys_info_t *keys_info);
static void cmd_rb_S(key_info_t key_info, keys_info_t *keys_info);
static void cmd_lb_z(key_info_t key_info, keys_info_t *keys_info);
static void cmd_rb_z(key_info_t key_info, keys_info_t *keys_info);
static void cmd_left_curly_bracket(key_info_t key_info, keys_info_t *keys_info);
static void cmd_right_curly_bracket(key_info_t key_info,
		keys_info_t *keys_info);
static void find_goto(int ch, int count, int backward);
static void select_up_one(view_t *view, int start_pos);
static void select_down_one(view_t *view, int start_pos);
static void apply_selection(int pos);
static void revert_selection(int pos);
static void set_pos(int pos);
static void goto_pos_force_update(int pos);
static void goto_pos(int pos);
static void update_ui(void);
static int move_pos(int pos);
static void handle_mouse_event(key_info_t key_info, keys_info_t *keys_info);

static view_t *view;
static int start_pos;
static int last_fast_search_char;
static int last_fast_search_backward = -1;
static int upwards_range;
static int search_repeat;

/* Currently active amending submode. */
static AmendType amend_type;

static keys_add_info_t builtin_cmds[] = {
	{WK_C_a,        {{&cmd_ctrl_a}, .descr = "increment number in names"}},
	{WK_C_b,        {{&cmd_ctrl_b}, .descr = "scroll page up"}},
	{WK_C_c,        {{&cmd_ctrl_c}, .descr = "abort visual mode"}},
	{WK_C_d,        {{&cmd_ctrl_d}, .descr = "scroll half-page down"}},
	{WK_C_e,        {{&cmd_ctrl_e}, .descr = "scroll one line down"}},
	{WK_C_f,        {{&cmd_ctrl_f}, .descr = "scroll page down"}},
	{WK_C_g,        {{&cmd_ctrl_g}, .descr = "display file info"}},
	{WK_C_l,        {{&cmd_ctrl_l}, .descr = "redraw"}},
	{WK_CR,         {{&cmd_return}, .descr = "leave preserving selection"}},
	{WK_C_n,        {{&cmd_j},      .descr = "go to item below"}},
	{WK_C_p,        {{&cmd_k},      .descr = "go to item above"}},
	{WK_C_u,        {{&cmd_ctrl_u}, .descr = "scroll half-page up"}},
	{WK_C_x,        {{&cmd_ctrl_x}, .descr = "decrement number in names"}},
	{WK_C_y,        {{&cmd_ctrl_y}, .descr = "scroll one line up"}},
	{WK_ESC,        {{&cmd_ctrl_c}, .descr = "abort visual mode"}},
	{WK_QUOTE,      {{&cmd_quote}, FOLLOWED_BY_MULTIKEY, .descr = "navigate to mark", .suggest = &sug_quote}},
	{WK_CARET,      {{&cmd_zero},      .descr = "go to first column"}},
	{WK_DOLLAR,     {{&cmd_dollar},    .descr = "go to last column"}},
	{WK_PERCENT,    {{&cmd_percent},   .descr = "go to [count]% position"}},
	{WK_COMMA,      {{&cmd_comma},     .descr = "repeat char-search backward"}},
	{WK_ZERO,       {{&cmd_zero},      .descr = "go to first column"}},
	{WK_COLON,      {{&cmd_colon},     .descr = "go to cmdline mode"}},
	{WK_SCOLON,     {{&cmd_semicolon}, .descr = "repeat char-search forward"}},
	{WK_SLASH,      {{&cmd_slash},     .descr = "search forward"}},
	{WK_QM,         {{&cmd_question},  .descr = "search backward"}},
	{WK_C,          {{&cmd_C}, .descr = "clone files"}},
	{WK_D,          {{&cmd_D}, .descr = "remove files permanently"}},
	{WK_F,          {{&cmd_F}, FOLLOWED_BY_MULTIKEY, .descr = "char-search backward"}},
	{WK_G,          {{&cmd_G}, .descr = "go to the last item"}},
	{WK_H,          {{&cmd_H}, .descr = "go to top of viewport"}},
	{WK_L,          {{&cmd_L}, .descr = "go to bottom of viewport"}},
	{WK_M,          {{&cmd_M}, .descr = "go to middle of viewport"}},
	{WK_N,          {{&cmd_N}, .descr = "go to previous search match"}},
	{WK_O,          {{&cmd_O}, .descr = "switch active selection bound"}},
	{WK_U,          {{&cmd_gU}, .descr = "convert to uppercase"}},
	{WK_V,          {{&cmd_v},  .descr = "leave/switch to regular visual mode"}},
	{WK_Y,          {{&cmd_y},  .descr = "yank files"}},
	{WK_a WK_v,     {{&cmd_av}, .descr = "leave/switch to amending visual mode"}},
	{WK_d,          {{&cmd_d},  .descr = "remove files"}},
	{WK_f,          {{&cmd_f}, FOLLOWED_BY_MULTIKEY, .descr = "char-search forward"}},
	{WK_c WK_l,     {{&cmd_cl}, .descr = "change symlink target"}},
	{WK_c WK_p,     {{&cmd_cp}, .descr = "change file permissions/attributes"}},
	{WK_c WK_w,     {{&cmd_cw}, .descr = "rename files"}},
	{WK_g WK_A,     {{&cmd_gA}, .descr = "(re)calculate size"}},
	{WK_g WK_a,     {{&cmd_ga}, .descr = "calculate size"}},
	{WK_g WK_g,     {{&cmd_gg}, .descr = "go to the first item"}},
	{WK_g WK_h,     {{&cmd_h},  .descr = "go to item to the left"}},
	{WK_g WK_j,     {{&cmd_j},  .descr = "go to item below"}},
	{WK_g WK_k,     {{&cmd_k},  .descr = "go to item above"}},
	{WK_g WK_l,     {{&cmd_gl}, .descr = "open selection"}},
	{WK_g WK_U,     {{&cmd_gU}, .descr = "convert to uppercase"}},
	{WK_g WK_u,     {{&cmd_gu}, .descr = "convert to lowercase"}},
	{WK_g WK_v,     {{&cmd_gv}, .descr = "restore previous selection"}},
	{WK_h,          {{&cmd_h},  .descr = "go to item to the left"}},
	{WK_i,          {{&cmd_i},  .descr = "open selection, but don't execute it"}},
	{WK_j,          {{&cmd_j},  .descr = "go to item below"}},
	{WK_k,          {{&cmd_k},  .descr = "go to item above"}},
	{WK_l,          {{&cmd_l},  .descr = "open selection/go to item to the right"}},
	{WK_m,          {{&cmd_m}, FOLLOWED_BY_MULTIKEY, .descr = "set a mark", .suggest = &sug_quote}},
	{WK_n,          {{&cmd_n},  .descr = "go to next search match"}},
	{WK_o,          {{&cmd_O},  .descr = "switch active selection bound"}},
	{WK_q WK_COLON, {{&cmd_q_colon},    .descr = "edit cmdline in editor"}},
	{WK_q WK_SLASH, {{&cmd_q_slash},    .descr = "edit forward search pattern in editor"}},
	{WK_q WK_QM,    {{&cmd_q_question}, .descr = "edit backward search pattern in editor"}},
	{WK_u,          {{&cmd_gu}, .descr = "convert to lowercase"}},
	{WK_v,          {{&cmd_v},  .descr = "leave/switch to regular visual mode"}},
	{WK_y,          {{&cmd_y},  .descr = "yank files"}},
	{WK_z WK_b,     {{&modnorm_zb}, .descr = "push cursor to the bottom"}},
	{WK_z WK_d,     {{&cmd_zd}, .descr = "exclude custom view entry"}},
	{WK_z WK_f,     {{&cmd_zf}, .descr = "add selection to filter"}},
	{WK_z WK_t,     {{&modnorm_zt},      .descr = "push cursor to the top"}},
	{WK_z WK_z,     {{&modnorm_zz},      .descr = "center cursor position"}},
	{WK_LP,         {{&cmd_left_paren},  .descr = "go to previous group of files"}},
	{WK_RP,         {{&cmd_right_paren}, .descr = "go to next group of files"}},
	{WK_z WK_k,     {{&cmd_z_k},  .descr = "go to previous sibling dir"}},
	{WK_z WK_j,     {{&cmd_z_j},  .descr = "go to next sibling dir"}},
	{WK_LB WK_c,    {{&cmd_lb_c}, .descr = "go to previous mismatch"}},
	{WK_RB WK_c,    {{&cmd_rb_c}, .descr = "go to next mismatch"}},
	{WK_LB WK_d,    {{&cmd_lb_d}, .descr = "go to previous dir"}},
	{WK_RB WK_d,    {{&cmd_rb_d}, .descr = "go to next dir"}},
	{WK_LB WK_s,    {{&cmd_lb_s}, .descr = "go to previous selected entry"}},
	{WK_RB WK_s,    {{&cmd_rb_s}, .descr = "go to next selected entry"}},
	{WK_LB WK_S,    {{&cmd_lb_S}, .descr = "go to previous selected entry (wrap)"}},
	{WK_RB WK_S,    {{&cmd_rb_S}, .descr = "go to next selected entry (wrap)"}},
	{WK_LB WK_z,    {{&cmd_lb_z}, .descr = "go to first sibling"}},
	{WK_RB WK_z,    {{&cmd_rb_z}, .descr = "go to last sibling"}},
	{WK_LCB,        {{&cmd_left_curly_bracket},  .descr = "go to previous file/dir group"}},
	{WK_RCB,        {{&cmd_right_curly_bracket}, .descr = "go to next file/dir group"}},
#ifdef ENABLE_EXTENDED_KEYS
	{{K(KEY_PPAGE)},   {{&cmd_ctrl_b}, .descr = "scroll page up"}},
	{{K(KEY_NPAGE)},   {{&cmd_ctrl_f}, .descr = "scroll page down"}},
	{{K(KEY_DOWN)},    {{&cmd_j},      .descr = "go to item below"}},
	{{K(KEY_UP)},      {{&cmd_k},      .descr = "go to item above"}},
	{{K(KEY_RIGHT)},   {{&cmd_l},      .descr = "open selection/go to item to the right"}},
#endif /* ENABLE_EXTENDED_KEYS */
	{{K(KEY_MOUSE)}, {{&handle_mouse_event}, FOLLOWED_BY_NONE}},
};

void
modvis_init(void)
{
	int ret_code;

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

	(void)ret_code;
}

void
modvis_enter(VisualSubmodes sub_mode)
{
	const int ub = marks_find_in_view(curr_view, '<');
	const int lb = marks_find_in_view(curr_view, '>');

	if(sub_mode == VS_RESTORE && (ub < 0 || lb < 0))
	{
		return;
	}

	view = curr_view;
	start_pos = view->list_pos;
	vle_mode_set(VISUAL_MODE, VMT_PRIMARY);

	switch(sub_mode)
	{
		case VS_NORMAL:
			amend_type = AT_NONE;
			flist_sel_stash(view);
			backup_selection_flags(view);
			select_first_one();
			break;
		case VS_RESTORE:
			amend_type = AT_NONE;
			flist_sel_stash(view);
			backup_selection_flags(view);
			restore_previous_selection();
			break;
		case VS_AMEND:
			amend_type = AT_APPEND;
			backup_selection_flags(view);
			select_first_one();
			break;
	}

	redraw_view(view);
}

void
modvis_leave(int save_msg, int goto_top, int clear_selection)
{
	if(goto_top)
	{
		int ub = marks_find_in_view(view, '<');
		if(ub != -1)
			view->list_pos = ub;
	}

	if(clear_selection)
	{
		reset_search_results(view);
		restore_selection_flags(view);
		redraw_view(view);
	}

	curr_stats.save_msg = save_msg;
	if(vle_mode_is(VISUAL_MODE))
	{
		vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
	}
}

/* Stores current values of selected flags of all items in the directory for
 * future use. */
static void
backup_selection_flags(view_t *view)
{
	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		view->dir_entry[i].was_selected = view->dir_entry[i].selected;
	}
}

/* Restore previous state of selected flags stored by
 * backup_selection_flags(). */
static void
restore_selection_flags(view_t *view)
{
	int i;

	view->selected_files = 0;
	for(i = 0; i < view->list_rows; ++i)
	{
		dir_entry_t *const entry = &view->dir_entry[i];
		entry->selected = entry->was_selected;
		if(entry->selected)
		{
			++view->selected_files;
		}
	}
}

/* Increments first number in names of marked files of the view [count=1]
 * times. */
static void
cmd_ctrl_a(key_info_t key_info, keys_info_t *keys_info)
{
	call_incdec(def_count(key_info.count));
}

static void
cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_scroll_back(view))
	{
		page_scroll(fpos_get_last_visible_cell(view), -1);
	}
}

static void
cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info)
{
	reject_and_leave();
}

static void
cmd_ctrl_d(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_move_down(view))
	{
		goto_pos(fpos_half_scroll(view, 1));
	}
}

static void
cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_scroll_fwd(view))
	{
		int new_pos = fpos_adjust_for_scroll_back(view, view->run_size);
		fview_scroll_fwd_by(view, view->run_size);
		goto_pos_force_update(new_pos);
	}
}

static void
cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_scroll_fwd(view))
	{
		page_scroll(view->top_line, 1);
	}
}

/* Scrolls pane by one view in both directions. The direction should be 1 or
 * -1. */
static void
page_scroll(int base, int direction)
{
	/* TODO: deduplicate with fpos_scroll_page(). */
	enum { HOR_GAP_SIZE = 2, VER_GAP_SIZE = 1 };
	int offset = fview_is_transposed(view)
	           ? MAX(1, (view->column_count - VER_GAP_SIZE))*view->window_rows
	           : (view->window_rows - HOR_GAP_SIZE)*view->column_count;
	int new_pos = base + direction*offset
	            + view->list_pos%view->run_size - base%view->run_size;
	new_pos = MAX(0, MIN(view->list_rows - 1, new_pos));
	fview_scroll_by(view, direction*offset);
	goto_pos(new_pos);
}

/* Switches amend submodes by round robin scheme. */
static void
cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info)
{
	if(amend_type != AT_NONE)
	{
		change_amend_type(1 + amend_type%(AT_COUNT - 1));
	}
}

static void
cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info)
{
	update_screen(UT_FULL);
	ui_set_cursor(/*visibility=*/0);
}

static void
cmd_return(key_info_t key_info, keys_info_t *keys_info)
{
	update_marks(view);
	if(vle_mode_is(VISUAL_MODE))
	{
		vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
	}
}

static void
cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_move_up(view))
	{
		goto_pos(fpos_half_scroll(view, 0));
	}
}

/* Decrements first number in names of marked files of the view [count=1]
 * times. */
static void
cmd_ctrl_x(key_info_t key_info, keys_info_t *keys_info)
{
	call_incdec(-def_count(key_info.count));
}

/* Increments/decrements first number in names of marked files of the view
 * [count=1] times. */
static void
call_incdec(int count)
{
	flist_set_marking(view, 0);
	int save_msg = fops_incdec(view, count);
	accept_and_leave(save_msg);
}

static void
cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_scroll_back(view))
	{
		int new_pos = fpos_adjust_for_scroll_fwd(view, view->run_size);
		fview_scroll_back_by(view, view->run_size);
		goto_pos_force_update(new_pos);
	}
}

/* Clones selection.  Count specifies number of copies of each file or directory
 * to create (one by default). */
static void
cmd_C(key_info_t key_info, keys_info_t *keys_info)
{
	flist_set_marking(view, 0);
	int save_msg = fops_clone(view, NULL, 0, 0, def_count(key_info.count));
	accept_and_leave(save_msg);
}

static void
cmd_D(key_info_t key_info, keys_info_t *keys_info)
{
	delete(key_info, 0);
}

/* Navigates to previous word which starts with specified character. */
static void
cmd_F(key_info_t key_info, keys_info_t *keys_info)
{
	last_fast_search_char = key_info.multi;
	last_fast_search_backward = 1;

	find_goto(key_info.multi, def_count(key_info.count), 1);
}

static void
cmd_G(key_info_t key_info, keys_info_t *keys_info)
{
	if(key_info.count == NO_COUNT_GIVEN)
	{
		key_info.count = view->list_rows;
	}
	goto_pos(key_info.count - 1);
}

/* Move to the first line of window, selecting as we go. */
static void
cmd_H(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_get_top_pos(view));
}

/* Move to the last line of window, selecting as we go. */
static void
cmd_L(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_get_bottom_pos(view));
}

/* Move to middle line of the window, selecting from start position to there. */
static void
cmd_M(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_get_middle_pos(view));
}

static void
cmd_N(key_info_t key_info, keys_info_t *keys_info)
{
	search(key_info, !curr_stats.last_search_backward);
}

static void
cmd_O(key_info_t key_info, keys_info_t *keys_info)
{
	int t = start_pos;
	start_pos = view->list_pos;
	view->list_pos = t;
	update_ui();
}

static void
cmd_quote(key_info_t key_info, keys_info_t *keys_info)
{
	const int pos = marks_find_in_view(view, key_info.multi);
	if(pos >= 0)
	{
		goto_pos(pos);
	}
}

/* Suggests only marks that point into current view. */
static void
sug_quote(vle_keys_list_cb cb)
{
	if(cfg.sug.flags & SF_MARKS)
	{
		marks_suggest(view, cb, 1);
	}
}

/* Move cursor to the last column in ls-view sub-mode selecting or unselecting
 * files while moving. */
static void
cmd_dollar(key_info_t key_info, keys_info_t *keys_info)
{
	if(!fpos_at_last_col(view))
	{
		goto_pos(fpos_line_end(view));
	}
}

static void
cmd_percent(key_info_t key_info, keys_info_t *keys_info)
{
	int line;
	if(key_info.count == NO_COUNT_GIVEN)
		return;
	if(key_info.count > 100)
		return;
	line = (key_info.count * view->list_rows)/100;
	goto_pos(line - 1);
}

/* Continues navigation to word which starts with specified character in
 * opposite direction. */
static void
cmd_comma(key_info_t key_info, keys_info_t *keys_info)
{
	if(last_fast_search_backward != -1)
	{
		find_goto(last_fast_search_char, def_count(key_info.count),
				!last_fast_search_backward);
	}
}

/* Move cursor to the first column in ls-view sub-mode selecting or unselecting
 * files while moving. */
static void
cmd_zero(key_info_t key_info, keys_info_t *keys_info)
{
	if(!fpos_at_first_col(view))
	{
		goto_pos(fpos_line_start(view));
	}
}

/* Switch to command-line mode. */
static void
cmd_colon(key_info_t key_info, keys_info_t *keys_info)
{
	update_marks(view);
	cmds_vars_set_count(key_info.count);
	modcline_enter(CLS_COMMAND, "");
}

/* Continues navigation to word which starts with specified character in initial
 * direction. */
static void
cmd_semicolon(key_info_t key_info, keys_info_t *keys_info)
{
	if(last_fast_search_backward != -1)
	{
		find_goto(last_fast_search_char, def_count(key_info.count),
				last_fast_search_backward);
	}
}

/* Search forward. */
static void
cmd_slash(key_info_t key_info, keys_info_t *keys_info)
{
	activate_search(key_info.count, 0, 0);
}

/* Search backward. */
static void
cmd_question(key_info_t key_info, keys_info_t *keys_info)
{
	activate_search(key_info.count, 1, 0);
}

/* Leaves visual mode if in amending mode, otherwise switches to amending
 * selection mode. */
static void
cmd_av(key_info_t key_info, keys_info_t *keys_info)
{
	if(amend_type != AT_NONE)
	{
		reject_and_leave();
	}
	else
	{
		change_amend_type(AT_APPEND);
	}
}

static void
cmd_d(key_info_t key_info, keys_info_t *keys_info)
{
	delete(key_info, 1);
}

/* Deletes files. */
static void
delete(key_info_t key_info, int use_trash)
{
	flist_set_marking(view, 0);
	if(fops_delete(view, def_reg(key_info.reg), use_trash))
	{
		accept_and_leave(1);
	}
}

/* Navigates to next word which starts with specified character. */
static void
cmd_f(key_info_t key_info, keys_info_t *keys_info)
{
	last_fast_search_char = key_info.multi;
	last_fast_search_backward = 0;

	find_goto(key_info.multi, def_count(key_info.count), 0);
}

/* Calculate size of selected directories ignoring cached sizes. */
static void
cmd_gA(key_info_t key_info, keys_info_t *keys_info)
{
	fops_size_bg(view, 1);
	accept_and_leave(0);
}

/* Calculate size of selected directories taking cached sizes into account. */
static void
cmd_ga(key_info_t key_info, keys_info_t *keys_info)
{
	fops_size_bg(view, 0);
	accept_and_leave(0);
}

/* Change symbolic link(s). */
static void
cmd_cl(key_info_t key_info, keys_info_t *keys_info)
{
	update_marks(view);
	modvis_leave(0, 1, 0);

	flist_set_marking(view, 0);
	curr_stats.save_msg = fops_retarget(view);
}

/* Change file attributes (permissions or properties). */
static void
cmd_cp(key_info_t key_info, keys_info_t *keys_info)
{
	int ub;

	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
	update_marks(view);
	ub = marks_find_in_view(view, '<');
	if(ub != -1)
	{
		view->list_pos = ub;
	}

	modnorm_cp(view, key_info);
}

/* Renames selected files of the view. */
static void
cmd_cw(key_info_t key_info, keys_info_t *keys_info)
{
	update_marks(view);
	modvis_leave(0, 1, 0);

	flist_set_marking(view, 0);
	(void)fops_rename(view, NULL, 0, 0);
}

static void
cmd_gg(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(def_count(key_info.count) - 1);
}

static void
cmd_gl(key_info_t key_info, keys_info_t *keys_info)
{
	update_marks(view);
	modvis_leave(curr_stats.save_msg, 1, 0);
	rn_open(view, FHE_RUN);
	flist_sel_stash(view);
	redraw_view(view);
}

/* Changes letters in filenames to upper case. */
static void
cmd_gU(key_info_t key_info, keys_info_t *keys_info)
{
	do_gu(1);
}

/* Changes letters in filenames to lower case. */
static void
cmd_gu(key_info_t key_info, keys_info_t *keys_info)
{
	do_gu(0);
}

/* Handles gU and gu commands. */
static void
do_gu(int upper)
{
	flist_set_marking(view, 0);
	int save_msg = fops_case(view, upper);
	accept_and_leave(save_msg);
}

/* Restores selection of previous visual mode usage. */
static void
cmd_gv(key_info_t key_info, keys_info_t *keys_info)
{
	restore_previous_selection();
}

/* Restores selection of previous visual mode usage, if it was in current
 * directory of the view. */
static void
restore_previous_selection(void)
{
	int ub = marks_find_in_view(view, '<');
	int lb = marks_find_in_view(view, '>');

	if(ub < 0 || lb < 0)
		return;

	if(ub > lb)
	{
		int t = ub;
		ub = lb;
		lb = t;

		marks_set_special(view, '<', view->dir_entry[ub].origin,
				view->dir_entry[ub].name);
		marks_set_special(view, '>', view->dir_entry[lb].origin,
				view->dir_entry[lb].name);
	}

	flist_sel_drop(view);

	start_pos = ub;
	view->list_pos = ub;

	select_first_one();

	while(view->list_pos < lb)
		select_down_one(view, start_pos);

	if(upwards_range)
	{
		int t = start_pos;
		start_pos = view->list_pos;
		view->list_pos = t;
	}

	update_ui();
}

/* Performs correct selection of item under the cursor. */
static void
select_first_one(void)
{
	apply_selection(view->list_pos);
}

/* Go backwards [count] (one by default) files in ls-like sub-mode. */
static void
cmd_h(key_info_t key_info, keys_info_t *keys_info)
{
	if(!ui_view_displays_columns(view))
	{
		if(fpos_can_move_left(view))
		{
			go_to_prev(key_info, keys_info, 1, fpos_get_hor_step(view));
		}
		return;
	}

	if(get_current_entry(view)->child_pos != 0)
	{
		const dir_entry_t *entry = get_current_entry(view);
		key_info.count = def_count(key_info.count);
		while(key_info.count-- > 0)
		{
			entry -= entry->child_pos;
		}
		key_info.count = 1;
		go_to_prev(key_info, keys_info, 1,
				view->list_pos - entry_to_pos(view, entry));
	}
}

static void
cmd_i(key_info_t key_info, keys_info_t *keys_info)
{
	rn_open(view, FHE_NO_RUN);
	accept_and_leave(curr_stats.save_msg);
}

static void
cmd_j(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_move_down(view))
	{
		go_to_next(key_info, keys_info, 1, fpos_get_ver_step(view));
	}
}

static void
cmd_k(key_info_t key_info, keys_info_t *keys_info)
{
	if(fpos_can_move_up(view))
	{
		go_to_prev(key_info, keys_info, 1, fpos_get_ver_step(view));
	}
}

/* Moves cursor to one of previous files in the list. */
static void
go_to_prev(key_info_t key_info, keys_info_t *keys_info, int def, int step)
{
	if(key_info.count == NO_COUNT_GIVEN)
		key_info.count = def;
	key_info.count *= step;
	goto_pos(view->list_pos - key_info.count);
}

static void
cmd_l(key_info_t key_info, keys_info_t *keys_info)
{
	if(ui_view_displays_columns(view))
	{
		cmd_gl(key_info, keys_info);
	}
	else if(fpos_can_move_right(view))
	{
		go_to_next(key_info, keys_info, 1, fpos_get_hor_step(view));
	}
}

/* Moves cursor to one of next files in the list. */
static void
go_to_next(key_info_t key_info, keys_info_t *keys_info, int def, int step)
{
	if(key_info.count == NO_COUNT_GIVEN)
		key_info.count = def;
	key_info.count *= step;
	goto_pos(view->list_pos + key_info.count);
}

static void
cmd_m(key_info_t key_info, keys_info_t *keys_info)
{
	const dir_entry_t *const curr = get_current_entry(view);
	if(!fentry_is_fake(curr))
	{
		marks_set_user(view, key_info.multi, curr->origin, curr->name);
	}
}

static void
cmd_n(key_info_t key_info, keys_info_t *keys_info)
{
	search(key_info, curr_stats.last_search_backward);
}

static void
search(key_info_t key_info, int backward)
{
	curr_stats.save_msg = search_next(curr_view, backward, /*stash_selection=*/0,
			/*select_matches=*/0, def_count(key_info.count), &goto_pos);
}

/* Runs external editor to get command-line command and then executes it. */
static void
cmd_q_colon(key_info_t key_info, keys_info_t *keys_info)
{
	leave_clearing_selection(0, 0);
	cmds_run_ext("", 0U, CIT_COMMAND);
}

/* Runs external editor to get search pattern and then executes it. */
static void
cmd_q_slash(key_info_t key_info, keys_info_t *keys_info)
{
	activate_search(key_info.count, 0, 1);
}

/* Runs external editor to get search pattern and then executes it. */
static void
cmd_q_question(key_info_t key_info, keys_info_t *keys_info)
{
	activate_search(key_info.count, 1, 1);
}

/* Activates search of different kinds. */
static void
activate_search(int count, int back, int external)
{
	/* TODO: generalize with normal.c:activate_search(). */

	search_repeat = def_count(count);
	curr_stats.last_search_backward = back;
	if(external)
	{
		CmdInputType type = back ? CIT_VBSEARCH_PATTERN : CIT_VFSEARCH_PATTERN;
		cmds_run_ext("", 0U, type);
	}
	else
	{
		const CmdLineSubmode submode = back ? CLS_VBSEARCH : CLS_VFSEARCH;
		modcline_enter(submode, "");
	}
}

/* Leave visual mode if not in amending mode, otherwise switch to normal visual
 * selection. */
static void
cmd_v(key_info_t key_info, keys_info_t *keys_info)
{
	if(amend_type == AT_NONE)
	{
		reject_and_leave();
	}
	else
	{
		change_amend_type(AT_NONE);
	}
}

/* Changes amend type and updates selected files. */
static void
change_amend_type(AmendType new_amend_type)
{
	amend_type = new_amend_type;
	modvis_update();
}

/* Yanks files. */
static void
cmd_y(key_info_t key_info, keys_info_t *keys_info)
{
	flist_set_marking(view, 0);
	int save_msg = fops_yank(view, def_reg(key_info.reg));
	accept_and_leave(save_msg);
}

/* Accepts selected region and leaves visual mode.  This means forgetting
 * selection that existed before. */
static void
accept_and_leave(int save_msg)
{
	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		view->dir_entry[i].was_selected = 0;
	}
	leave_clearing_selection(1, save_msg);
}

/* Rejects selected region and leaves visual mode.  Previous selection is not
 * touched. */
static void
reject_and_leave(void)
{
	leave_clearing_selection(0, 0);
}

/* Correctly leaves visual mode updating marks, clearing selection and going to
 * the top of selection. */
static void
leave_clearing_selection(int go_to_top, int save_msg)
{
	update_marks(view);
	modvis_leave(save_msg, go_to_top, 1);
}

static void
update_marks(view_t *view)
{
	char start_mark, end_mark;
	const dir_entry_t *start_entry, *end_entry;
	int delta;

	if(start_pos >= view->list_rows)
	{
		start_pos = view->list_rows - 1;
	}

	upwards_range = view->list_pos < start_pos;
	delta = upwards_range ? +1 : -1;

	start_mark = upwards_range ? '<' : '>';
	end_mark = upwards_range ? '>' : '<';

	start_entry = &view->dir_entry[view->list_pos];
	end_entry = &view->dir_entry[start_pos];
	/* Fake entries can't be marked, so skip them on both ends. */
	while(start_entry != end_entry && fentry_is_fake(start_entry))
	{
		start_entry += delta;
	}
	while(start_entry != end_entry && fentry_is_fake(end_entry))
	{
		end_entry -= delta;
	}

	marks_set_special(view, start_mark, start_entry->origin, start_entry->name);
	marks_set_special(view, end_mark, end_entry->origin, end_entry->name);
}

/* Excludes entries from custom view. */
static void
cmd_zd(key_info_t key_info, keys_info_t *keys_info)
{
	flist_custom_exclude(view, key_info.count == 1);
	accept_and_leave(0);
}

/* Filters selected files. */
static void
cmd_zf(key_info_t key_info, keys_info_t *keys_info)
{
	name_filters_add_active(view);
	accept_and_leave(0);
}

/* Moves cursor to the beginning of the previous group of files defined by the
 * primary sorting key. */
static void
cmd_left_paren(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_find_group(view, 0));
}

/* Moves cursor to the beginning of the next group of files defined by the
 * primary sorting key. */
static void
cmd_right_paren(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_find_group(view, 1));
}

/* Go to previous sibling directory entry or do nothing. */
static void
cmd_z_k(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_prev_dir_sibling(view));
}

/* Go to next sibling directory entry or do nothing. */
static void
cmd_z_j(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_next_dir_sibling(view));
}

/* Go to previous mismatched entry or do nothing. */
static void
cmd_lb_c(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_prev_mismatch(view));
}

/* Go to next mismatched entry or do nothing. */
static void
cmd_rb_c(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_next_mismatch(view));
}

/* Go to previous directory entry or do nothing. */
static void
cmd_lb_d(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_prev_dir(view));
}

/* Go to next directory entry or do nothing. */
static void
cmd_rb_d(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_next_dir(view));
}

/* Go to previous selected entry or do nothing. */
static void
cmd_lb_s(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_prev_selected(view));
}

/* Go to next selected entry or do nothing. */
static void
cmd_rb_s(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_next_selected(view));
}

/* Go to previous selected entry wrapping if necessary or do nothing. */
static void
cmd_lb_S(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_prev_selected_wrap(view));
}

/* Go to next selected entry wrapping if necessary or do nothing. */
static void
cmd_rb_S(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_next_selected_wrap(view));
}

/* Go to first sibling of the current entry. */
static void
cmd_lb_z(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_first_sibling(view));
}

/* Go to last sibling of the current entry. */
static void
cmd_rb_z(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_last_sibling(view));
}

/* Moves cursor to the beginning of the previous group of files defined by them
 * being directory or files. */
static void
cmd_left_curly_bracket(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_find_dir_group(view, 0));
}

/* Moves cursor to the beginning of the next group of files defined by them
 * being directory or files. */
static void
cmd_right_curly_bracket(key_info_t key_info, keys_info_t *keys_info)
{
	goto_pos(fpos_find_dir_group(view, 1));
}

/* Navigates to next/previous file which starts with given character. */
static void
find_goto(int ch, int count, int backward)
{
	int pos = fpos_find_by_ch(view, ch, backward, 1);
	if(pos < 0 || pos == view->list_pos)
	{
		return;
	}

	while(count-- > 0)
	{
		goto_pos(pos);
		pos = fpos_find_by_ch(view, ch, backward, 1);
	}
}

/* move up one position in the window, adding to the selection list */
static void
select_up_one(view_t *view, int start_pos)
{
	view->list_pos--;
	if(view->list_pos < 0)
	{
		view->list_pos = 0;
	}
	else if(view->list_pos < start_pos)
	{
		apply_selection(view->list_pos);
	}
	else if(view->list_pos == start_pos)
	{
		apply_selection(view->list_pos);
		revert_selection(view->list_pos + 1);
	}
	else
	{
		revert_selection(view->list_pos + 1);
	}
}

/* move down one position in the window, adding to the selection list */
static void
select_down_one(view_t *view, int start_pos)
{
	++view->list_pos;

	if(view->list_pos >= view->list_rows)
	{
		view->list_pos = view->list_rows - 1;
	}
	else if(view->list_pos > start_pos)
	{
		apply_selection(view->list_pos);
	}
	else if(view->list_pos == start_pos)
	{
		apply_selection(view->list_pos);
		revert_selection(view->list_pos - 1);
	}
	else
	{
		revert_selection(view->list_pos - 1);
	}
}

/* Applies selection effect to item at specified position. */
static void
apply_selection(int pos)
{
	dir_entry_t *const entry = &view->dir_entry[pos];
	if(!fentry_is_valid(entry))
	{
		return;
	}

	switch(amend_type)
	{
		case AT_NONE:
		case AT_APPEND:
			if(!entry->selected)
			{
				++view->selected_files;
				entry->selected = 1;
			}
			break;
		case AT_REMOVE:
			if(entry->selected)
			{
				--view->selected_files;
				entry->selected = 0;
			}
			break;
		case AT_INVERT:
			if(entry->selected && entry->was_selected)
			{
				--view->selected_files;
			}
			else if(!entry->selected && !entry->was_selected)
			{
				++view->selected_files;
			}
			entry->selected = !entry->was_selected;
			break;

		default:
			assert(0 && "Unexpected amending type.");
			break;
	}
}

/* Reverts selection effect to item at specified position. */
static void
revert_selection(int pos)
{
	dir_entry_t *const entry = &view->dir_entry[pos];
	switch(amend_type)
	{
		case AT_NONE:
		case AT_APPEND:
			if(entry->selected && !entry->was_selected)
			{
				--view->selected_files;
			}
			entry->selected = entry->was_selected;
			break;
		case AT_REMOVE:
			if(!entry->selected && entry->was_selected)
			{
				++view->selected_files;
				entry->selected = 1;
			}
			break;
		case AT_INVERT:
			if(entry->selected && !entry->was_selected)
			{
				--view->selected_files;
			}
			else if(!entry->selected && entry->was_selected)
			{
				++view->selected_files;
			}
			entry->selected = entry->was_selected;
			break;

		default:
			assert(0 && "Unexpected amending type.");
			break;
	}
}

void
modvis_update(void)
{
	const int cursor_pos = view->list_pos;
	view->list_pos = start_pos;

	/* Regardless of the mode we've switched to, start with the original state
	 * of selection to have consistent behaviour on `av` and `vav`. */
	restore_selection_flags(view);

	if(amend_type == AT_NONE)
	{
		/* When not amending, original selection doesn't affect visual selection. */
		flist_sel_stash(view);

		/* All selection flags are reset, so this call actually clears the
		 * backup. */
		backup_selection_flags(view);
	}

	select_first_one();
	move_pos(cursor_pos);

	update_ui();
}

int
modvis_find(view_t *view, const char pattern[], int backward, int print_msg,
		int *found)
{
	return search_find(view, pattern, backward, /*stash_selection=*/0,
			/*select_matches=*/0, search_repeat, &goto_pos, print_msg, found);
}

int
modvis_find_interactive(view_t *view, const char pattern[], int backward,
		int *found)
{
	int save_msg = search_find(view, pattern, backward, /*stash_selection=*/0,
			/*select_matches=*/0, search_repeat, &set_pos, /*print_msg=*/0, found);
	modvis_update();
	return save_msg;
}

static void
set_pos(int pos)
{
	view->list_pos = pos;
}

/* Moves cursor from its current position to specified pos selecting or
 * unselecting files while moving.  Always redraws view. */
static void
goto_pos_force_update(int pos)
{
	(void)move_pos(pos);
	update_ui();
}

/* Moves cursor from its current position to specified pos selecting or
 * unselecting files while moving.  Automatically redraws view if needed. */
static void
goto_pos(int pos)
{
	if(move_pos(pos))
	{
		update_ui();
	}
}

/* Updates elements of the screen after visual-mode specific elements were
 * changed. */
static void
update_ui(void)
{
	fpos_set_pos(view, view->list_pos);
	redraw_view(view);
	ui_ruler_update(view, 1);
}

/* Moves cursor from its current position to specified pos selecting or
 * unselecting files while moving.  Don't call it explicitly, call goto_pos()
 * and goto_pos_force_update() instead.  Returns non-zero if cursor was
 * moved. */
static int
move_pos(int pos)
{
	pos = MIN(view->list_rows - 1, MAX(0, pos));
	if(view->list_pos == pos)
	{
		return 0;
	}

	while(view->list_pos < pos)
		select_down_one(view, start_pos);
	while(view->list_pos > pos)
		select_up_one(view, start_pos);

	return 1;
}

const char *
modvis_describe(void)
{
	static const char *descriptions[] = {
		[AT_NONE]   = "VISUAL",
		[AT_APPEND] = "VISUAL (append)",
		[AT_REMOVE] = "VISUAL (remove)",
		[AT_INVERT] = "VISUAL (invert)",
	};
	ARRAY_GUARD(descriptions, AT_COUNT);

	return descriptions[amend_type];
}

int
modvis_is_amending(void)
{
	return vle_mode_is(VISUAL_MODE) && amend_type != AT_NONE;
}

/* 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(view->win, e.y, e.x))
	{
		return;
	}

	if(e.bstate & BUTTON1_PRESSED)
	{
		wmouse_trafo(view->win, &e.y, &e.x, FALSE);

		int list_pos = fview_map_coordinates(curr_view, e.x, e.y);
		if(list_pos >= 0)
		{
			int old_pos = curr_view->list_pos;
			goto_pos(list_pos);

			if(curr_view->list_pos == old_pos)
			{
				cmd_gl(key_info, keys_info);
			}
		}
	}
	else if(e.bstate & BUTTON4_PRESSED)
	{
		cmd_ctrl_y(key_info, keys_info);
	}
	else if(e.bstate & (BUTTON2_PRESSED | BUTTON5_PRESSED))
	{
		cmd_ctrl_e(key_info, keys_info);
	}
}

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