1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "menu.h"
21 
22 #include <curses.h>
23 
24 #include <assert.h> /* assert() */
25 #include <errno.h> /* errno */
26 #include <stddef.h> /* NULL wchar_t */
27 #include <stdio.h> /* pclose() popen() */
28 #include <stdlib.h> /* free() */
29 #include <string.h> /* strerror() */
30 
31 #include "../cfg/config.h"
32 #include "../compat/curses.h"
33 #include "../compat/reallocarray.h"
34 #include "../engine/cmds.h"
35 #include "../engine/keys.h"
36 #include "../engine/mode.h"
37 #include "../menus/menus.h"
38 #include "../modes/dialogs/msg_dialog.h"
39 #include "../ui/fileview.h"
40 #include "../ui/statusbar.h"
41 #include "../ui/ui.h"
42 #include "../utils/macros.h"
43 #include "../utils/path.h"
44 #include "../utils/str.h"
45 #include "../utils/string_array.h"
46 #include "../utils/test_helpers.h"
47 #include "../utils/utils.h"
48 #include "../cmd_core.h"
49 #include "../cmd_completion.h"
50 #include "../flist_sel.h"
51 #include "../status.h"
52 #include "cmdline.h"
53 #include "modes.h"
54 #include "wk.h"
55 
56 static const int SCROLL_GAP = 2;
57 
58 static int swap_range(void);
59 static int resolve_mark(char mark);
60 static char * menu_expand_macros(const char str[], int for_shell, int *usr1,
61 		int *usr2);
62 static char * menu_expand_envvars(const char *str);
63 static void post(int id);
64 static void menu_select_range(int id, const cmd_info_t *cmd_info);
65 static int skip_at_beginning(int id, const char *args);
66 
67 static int key_handler(wchar_t key);
68 static void cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info);
69 static int can_scroll_menu_up(const menu_data_t *menu);
70 static void cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info);
71 static void cmd_ctrl_d(key_info_t key_info, keys_info_t *keys_info);
72 static void cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info);
73 static void cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info);
74 static int can_scroll_menu_down(const menu_data_t *menu);
75 static void change_menu_top(menu_data_t *menu, int delta);
76 static void cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info);
77 static void cmd_return(key_info_t key_info, keys_info_t *keys_info);
78 static void update_ui_on_leaving(void);
79 static void cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info);
80 static int get_effective_menu_scroll_offset(const menu_data_t *menu);
81 static void cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info);
82 static void cmd_slash(key_info_t key_info, keys_info_t *keys_info);
83 static void cmd_percent(key_info_t key_info, keys_info_t *keys_info);
84 static void cmd_colon(key_info_t key_info, keys_info_t *keys_info);
85 static void cmd_qmark(key_info_t key_info, keys_info_t *keys_info);
86 static void cmd_B(key_info_t key_info, keys_info_t *keys_info);
87 static void cmd_G(key_info_t key_info, keys_info_t *keys_info);
88 static void cmd_H(key_info_t key_info, keys_info_t *keys_info);
89 static void cmd_L(key_info_t key_info, keys_info_t *keys_info);
90 static void cmd_M(key_info_t key_info, keys_info_t *keys_info);
91 static void cmd_N(key_info_t key_info, keys_info_t *keys_info);
92 static void cmd_b(key_info_t key_info, keys_info_t *keys_info);
93 static void dump_into_custom_view(int very);
94 static void cmd_dd(key_info_t key_info, keys_info_t *keys_info);
95 static void cmd_gf(key_info_t key_info, keys_info_t *keys_info);
96 static int pass_combination_to_khandler(const wchar_t keys[]);
97 static void cmd_gg(key_info_t key_info, keys_info_t *keys_info);
98 static void cmd_j(key_info_t key_info, keys_info_t *keys_info);
99 static void cmd_k(key_info_t key_info, keys_info_t *keys_info);
100 static void cmd_n(key_info_t key_info, keys_info_t *keys_info);
101 static void cmd_v(key_info_t key_info, keys_info_t *keys_info);
102 static void cmd_zb(key_info_t key_info, keys_info_t *keys_info);
103 static void cmd_zH(key_info_t key_info, keys_info_t *keys_info);
104 static void cmd_zL(key_info_t key_info, keys_info_t *keys_info);
105 static void cmd_zh(key_info_t key_info, keys_info_t *keys_info);
106 static void cmd_zl(key_info_t key_info, keys_info_t *keys_info);
107 static void cmd_zt(key_info_t key_info, keys_info_t *keys_info);
108 static void cmd_zz(key_info_t key_info, keys_info_t *keys_info);
109 static int all_lines_visible(const menu_data_t *menu);
110 static int goto_cmd(const cmd_info_t *cmd_info);
111 static int nohlsearch_cmd(const cmd_info_t *cmd_info);
112 static int quit_cmd(const cmd_info_t *cmd_info);
113 static int write_cmd(const cmd_info_t *cmd_info);
114 static void leave_menu_mode(int reset_selection);
115 
116 static view_t *view;
117 static menu_data_t *menu;
118 static int last_search_backward;
119 static int was_redraw;
120 static int saved_top, saved_pos;
121 
122 static keys_add_info_t builtin_cmds[] = {
123 	{WK_C_b,     {{&cmd_ctrl_b},  .descr = "scroll page up"}},
124 	{WK_C_c,     {{&cmd_ctrl_c},  .descr = "leave menu mode"}},
125 	{WK_C_d,     {{&cmd_ctrl_d},  .descr = "scroll half-page down"}},
126 	{WK_C_e,     {{&cmd_ctrl_e},  .descr = "scroll one line down"}},
127 	{WK_C_f,     {{&cmd_ctrl_f},  .descr = "scroll page down"}},
128 	{WK_C_l,     {{&cmd_ctrl_l},  .descr = "redraw"}},
129 	{WK_CR,      {{&cmd_return},  .descr = "pick current item"}},
130 	{WK_C_n,     {{&cmd_j},       .descr = "go to item below"}},
131 	{WK_C_p,     {{&cmd_k},       .descr = "go to item above"}},
132 	{WK_C_u,     {{&cmd_ctrl_u},  .descr = "scroll half-page up"}},
133 	{WK_C_y,     {{&cmd_ctrl_y},  .descr = "scroll one line up"}},
134 	{WK_ESC,     {{&cmd_ctrl_c},  .descr = "leave menu mode"}},
135 	{WK_SLASH,   {{&cmd_slash},   .descr = "search forward"}},
136 	{WK_PERCENT, {{&cmd_percent}, .descr = "go to [count]% position"}},
137 	{WK_COLON,   {{&cmd_colon},   .descr = "go to cmdline mode"}},
138 	{WK_QM,      {{&cmd_qmark},   .descr = "search backward"}},
139 	{WK_B,       {{&cmd_B},       .descr = "make unsorted custom view"}},
140 	{WK_G,       {{&cmd_G},       .descr = "go to the last item"}},
141 	{WK_H,       {{&cmd_H},       .descr = "go to top of viewport"}},
142 	{WK_L,       {{&cmd_L},       .descr = "go to bottom of viewport"}},
143 	{WK_M,       {{&cmd_M},       .descr = "go to middle of viewport"}},
144 	{WK_N,       {{&cmd_N},       .descr = "go to previous search match"}},
145 	{WK_Z WK_Z,  {{&cmd_ctrl_c},  .descr = "leave menu mode"}},
146 	{WK_Z WK_Q,  {{&cmd_ctrl_c},  .descr = "leave menu mode"}},
147 	{WK_b,       {{&cmd_b},       .descr = "make custom view"}},
148 	{WK_d WK_d,  {{&cmd_dd},      .descr = "remove files"}},
149 	{WK_g WK_f,  {{&cmd_gf},      .descr = "navigate to file location"}},
150 	{WK_g WK_g,  {{&cmd_gg},      .descr = "go to the first item"}},
151 	{WK_j,       {{&cmd_j},       .descr = "go to item below"}},
152 	{WK_k,       {{&cmd_k},       .descr = "go to item above"}},
153 	{WK_l,       {{&cmd_return},  .descr = "pick current item"}},
154 	{WK_n,       {{&cmd_n},       .descr = "go to next search match"}},
155 	{WK_q,       {{&cmd_ctrl_c},  .descr = "leave menu mode"}},
156 	{WK_v,       {{&cmd_v},       .descr = "use items as Vim quickfix list"}},
157 	{WK_z WK_b,  {{&cmd_zb},      .descr = "push cursor to the bottom"}},
158 	{WK_z WK_H,  {{&cmd_zH},      .descr = "scroll page left"}},
159 	{WK_z WK_L,  {{&cmd_zL},      .descr = "scroll page right"}},
160 	{WK_z WK_h,  {{&cmd_zh},      .descr = "scroll one column left"}},
161 	{WK_z WK_l,  {{&cmd_zl},      .descr = "scroll one column right"}},
162 	{WK_z WK_t,  {{&cmd_zt},      .descr = "push cursor to the top"}},
163 	{WK_z WK_z,  {{&cmd_zz},      .descr = "center cursor position"}},
164 #ifdef ENABLE_EXTENDED_KEYS
165 	{{K(KEY_PPAGE)},       {{&cmd_ctrl_b}, .descr = "scroll page up"}},
166 	{{K(KEY_NPAGE)},       {{&cmd_ctrl_f}, .descr = "scroll page down"}},
167 	{{K(KEY_UP)},          {{&cmd_k},      .descr = "go to item above"}},
168 	{{K(KEY_DOWN)},        {{&cmd_j},      .descr = "go to item below"}},
169 	{{K(KEY_RIGHT)},       {{&cmd_return}, .descr = "pick current item"}},
170 	{{K(KEY_HOME)},        {{&cmd_gg},     .descr = "go to the first item"}},
171 	{{K(KEY_END)},         {{&cmd_G},      .descr = "go to the last item"}},
172 	{{WC_z, K(KEY_LEFT)},  {{&cmd_zh},     .descr = "scroll one column left"}},
173 	{{WC_z, K(KEY_RIGHT)}, {{&cmd_zl},     .descr = "scroll one column right"}},
174 #endif /* ENABLE_EXTENDED_KEYS */
175 };
176 
177 /* Specification of builtin commands. */
178 static const cmd_add_t commands[] = {
179 	{ .name = "",                  .abbr = NULL,    .id = -1,
180 	  .descr = "navigate to specific line",
181 	  .flags = HAS_RANGE,
182 	  .handler = &goto_cmd,        .min_args = 0,   .max_args = 0, },
183 	{ .name = "exit",              .abbr = "exi",   .id = -1,
184 	  .descr = "exit the application",
185 	  .flags = 0,
186 	  .handler = &quit_cmd,        .min_args = 0,   .max_args = 0, },
187 	{ .name = "nohlsearch",        .abbr = "noh",   .id = -1,
188 	  .descr = "reset highlighting of search matches",
189 	  .flags = 0,
190 	  .handler = &nohlsearch_cmd,  .min_args = 0,   .max_args = 0, },
191 	{ .name = "quit",              .abbr = "q",     .id = -1,
192 	  .descr = "exit the application",
193 	  .flags = 0,
194 	  .handler = &quit_cmd,        .min_args = 0,   .max_args = 0, },
195 	{ .name = "write",             .abbr = "w",     .id = COM_MENU_WRITE,
196 	  .descr = "write all menu lines into a file",
197 	  .flags = HAS_QUOTED_ARGS,
198 	  .handler = &write_cmd,       .min_args = 1,   .max_args = 1, },
199 	{ .name = "xit",               .abbr = "x",     .id = -1,
200 	  .descr = "exit the application",
201 	  .flags = 0,
202 	  .handler = &quit_cmd,        .min_args = 0,   .max_args = 0, },
203 };
204 
205 /* Settings for the cmds unit. */
206 static cmds_conf_t cmds_conf = {
207 	.complete_args = &complete_args,
208 	.swap_range = &swap_range,
209 	.resolve_mark = &resolve_mark,
210 	.expand_macros = &menu_expand_macros,
211 	.expand_envvars = &menu_expand_envvars,
212 	.post = &post,
213 	.select_range = &menu_select_range,
214 	.skip_at_beginning = &skip_at_beginning,
215 };
216 
217 static int
swap_range(void)218 swap_range(void)
219 {
220 	return 0;
221 }
222 
223 static int
resolve_mark(char mark)224 resolve_mark(char mark)
225 {
226 	return -1;
227 }
228 
229 /* Implementation of macros expansion callback for cmds unit.  Returns newly
230  * allocated memory. */
231 static char *
menu_expand_macros(const char str[],int for_shell,int * usr1,int * usr2)232 menu_expand_macros(const char str[], int for_shell, int *usr1, int *usr2)
233 {
234 	return strdup(str);
235 }
236 
237 static char *
menu_expand_envvars(const char * str)238 menu_expand_envvars(const char *str)
239 {
240 	return strdup(str);
241 }
242 
243 static void
post(int id)244 post(int id)
245 {
246 }
247 
248 static void
menu_select_range(int id,const cmd_info_t * cmd_info)249 menu_select_range(int id, const cmd_info_t *cmd_info)
250 {
251 }
252 
253 static int
skip_at_beginning(int id,const char * args)254 skip_at_beginning(int id, const char *args)
255 {
256 	return -1;
257 }
258 
259 void
modmenu_init(void)260 modmenu_init(void)
261 {
262 	int ret_code;
263 
264 	ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), MENU_MODE);
265 	assert(ret_code == 0);
266 
267 	(void)ret_code;
268 
269 	vle_keys_set_def_handler(MENU_MODE, &key_handler);
270 
271 	/* Double initialization can happen in tests. */
272 	if(cmds_conf.inner == NULL)
273 	{
274 		vle_cmds_init(0, &cmds_conf);
275 		vle_cmds_add(commands, ARRAY_LEN(commands));
276 	}
277 }
278 
279 static int
key_handler(wchar_t key)280 key_handler(wchar_t key)
281 {
282 	const wchar_t shortcut[] = {key, L'\0'};
283 
284 	if(pass_combination_to_khandler(shortcut) && menu->len == 0)
285 	{
286 		show_error_msg("No more items in the menu", "Menu will be closed");
287 		leave_menu_mode(1);
288 	}
289 
290 	return 0;
291 }
292 
293 void
modmenu_enter(menu_data_t * m,view_t * active_view)294 modmenu_enter(menu_data_t *m, view_t *active_view)
295 {
296 	if(curr_stats.load_stage >= 0 && curr_stats.load_stage < 2)
297 	{
298 		return;
299 	}
300 
301 	assert(m->len > 0 && "Menu cannot be empty.");
302 
303 	werase(status_bar);
304 
305 	view = active_view;
306 	menu = m;
307 	vle_mode_set(MENU_MODE, VMT_PRIMARY);
308 	stats_refresh_later();
309 	was_redraw = 0;
310 
311 	vle_cmds_init(0, &cmds_conf);
312 }
313 
314 void
modmenu_abort(void)315 modmenu_abort(void)
316 {
317 	leave_menu_mode(1);
318 }
319 
320 void
modmenu_reenter(menu_data_t * m)321 modmenu_reenter(menu_data_t *m)
322 {
323 	assert(vle_mode_is(MENU_MODE) && "Can't reenter if not in menu mode.");
324 	assert(m->len > 0 && "Menu cannot be empty.");
325 
326 	menus_replace_data(m);
327 	menus_full_redraw(m->state);
328 	menu = m;
329 }
330 
331 void
modmenu_pre(void)332 modmenu_pre(void)
333 {
334 	touchwin(ruler_win);
335 	ui_refresh_win(ruler_win);
336 }
337 
338 void
modmenu_post(void)339 modmenu_post(void)
340 {
341 	if(stats_update_fetch() != UT_NONE)
342 	{
343 		modmenu_full_redraw();
344 	}
345 	ui_sb_msg(curr_stats.save_msg ? NULL : "");
346 }
347 
348 void
modmenu_full_redraw(void)349 modmenu_full_redraw(void)
350 {
351 	was_redraw = 1;
352 	menus_full_redraw(menu->state);
353 }
354 
355 static void
cmd_ctrl_b(key_info_t key_info,keys_info_t * keys_info)356 cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info)
357 {
358 	if(can_scroll_menu_up(menu))
359 	{
360 		const int s = get_effective_menu_scroll_offset(menu);
361 		const int off = (getmaxy(menu_win) - 2) - SCROLL_GAP;
362 		menu->pos = modmenu_last_line(menu) - off;
363 		change_menu_top(menu, -off);
364 		if(cfg.scroll_off > 0 &&
365 				menu->top + (getmaxy(menu_win) - 3) - menu->pos < s)
366 		{
367 			menu->pos -= s - (menu->top + (getmaxy(menu_win) - 3) - menu->pos);
368 		}
369 
370 		modmenu_partial_redraw();
371 	}
372 }
373 
374 /* Returns non-zero if menu can be scrolled up. */
375 static int
can_scroll_menu_up(const menu_data_t * menu)376 can_scroll_menu_up(const menu_data_t *menu)
377 {
378 	return menu->top > 0;
379 }
380 
381 static void
cmd_ctrl_c(key_info_t key_info,keys_info_t * keys_info)382 cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info)
383 {
384 	leave_menu_mode(1);
385 }
386 
387 static void
cmd_ctrl_d(key_info_t key_info,keys_info_t * keys_info)388 cmd_ctrl_d(key_info_t key_info, keys_info_t *keys_info)
389 {
390 	const int s = get_effective_menu_scroll_offset(menu);
391 	menus_erase_current(menu->state);
392 	menu->top += DIV_ROUND_UP(getmaxy(menu_win) - 3, 2);
393 	menu->pos += DIV_ROUND_UP(getmaxy(menu_win) - 3, 2);
394 	if(cfg.scroll_off > 0 && menu->pos - menu->top < s)
395 	{
396 		menu->pos += s - (menu->pos - menu->top);
397 	}
398 
399 	modmenu_partial_redraw();
400 }
401 
402 static void
cmd_ctrl_e(key_info_t key_info,keys_info_t * keys_info)403 cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info)
404 {
405 	if(can_scroll_menu_down(menu))
406 	{
407 		int off = MAX(cfg.scroll_off, 0);
408 		if(menu->pos <= menu->top + off)
409 		{
410 			menu->pos = menu->top + 1 + off;
411 		}
412 
413 		++menu->top;
414 		modmenu_partial_redraw();
415 	}
416 }
417 
418 static void
cmd_ctrl_f(key_info_t key_info,keys_info_t * keys_info)419 cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info)
420 {
421 	if(can_scroll_menu_down(menu))
422 	{
423 		const int s = get_effective_menu_scroll_offset(menu);
424 		const int off = (getmaxy(menu_win) - 2) - SCROLL_GAP;
425 		menu->pos = menu->top + off;
426 		change_menu_top(menu, off);
427 		if(cfg.scroll_off > 0 && menu->pos - menu->top < s)
428 		{
429 			menu->pos += s - (menu->pos - menu->top);
430 		}
431 
432 		modmenu_partial_redraw();
433 	}
434 }
435 
436 /* Returns non-zero if menu can be scrolled down. */
437 static int
can_scroll_menu_down(const menu_data_t * menu)438 can_scroll_menu_down(const menu_data_t *menu)
439 {
440 	return modmenu_last_line(menu) < menu->len - 1;
441 }
442 
443 /* Moves top line of the menu ensuring that its value is correct. */
444 static void
change_menu_top(menu_data_t * menu,int delta)445 change_menu_top(menu_data_t *menu, int delta)
446 {
447 	menu->top =
448 		MAX(MIN(menu->top + delta, menu->len - (getmaxy(menu_win) - 2)), 0);
449 }
450 
451 int
modmenu_last_line(const menu_data_t * menu)452 modmenu_last_line(const menu_data_t *menu)
453 {
454 	return menu->top + (getmaxy(menu_win) - 2) - 1;
455 }
456 
457 /* Redraw TUI. */
458 static void
cmd_ctrl_l(key_info_t key_info,keys_info_t * keys_info)459 cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info)
460 {
461 	modmenu_full_redraw();
462 }
463 
464 static void
cmd_return(key_info_t key_info,keys_info_t * keys_info)465 cmd_return(key_info_t key_info, keys_info_t *keys_info)
466 {
467 	static menu_data_t *saved_menu;
468 
469 	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
470 	saved_menu = menu;
471 	if(menu->execute_handler != NULL && menu->execute_handler(view, menu))
472 	{
473 		vle_mode_set(MENU_MODE, VMT_PRIMARY);
474 		modmenu_full_redraw();
475 		return;
476 	}
477 
478 	if(!vle_mode_is(MENU_MODE))
479 	{
480 		menus_reset_data(saved_menu);
481 	}
482 	else if(menu != saved_menu)
483 	{
484 		menus_reset_data(saved_menu);
485 		modmenu_partial_redraw();
486 	}
487 
488 	update_ui_on_leaving();
489 }
490 
491 /* Updates UI on leaving the mode trying to minimize efforts to do this. */
492 static void
update_ui_on_leaving(void)493 update_ui_on_leaving(void)
494 {
495 	if(was_redraw)
496 	{
497 		update_screen(UT_FULL);
498 	}
499 	else
500 	{
501 		ui_view_title_update(curr_view);
502 		update_all_windows();
503 	}
504 }
505 
506 static void
cmd_ctrl_u(key_info_t key_info,keys_info_t * keys_info)507 cmd_ctrl_u(key_info_t key_info, keys_info_t *keys_info)
508 {
509 	const int s = get_effective_menu_scroll_offset(menu);
510 	menus_erase_current(menu->state);
511 
512 	if(cfg.scroll_off > 0 && menu->top + getmaxy(menu_win) - menu->pos < s)
513 	{
514 		menu->pos -= s - (menu->top + (getmaxy(menu_win) - 3) - menu->pos);
515 	}
516 
517 	menu->top -= DIV_ROUND_UP(getmaxy(menu_win) - 3, 2);
518 	if(menu->top < 0)
519 	{
520 		menu->top = 0;
521 	}
522 	menu->pos -= DIV_ROUND_UP(getmaxy(menu_win) - 3, 2);
523 
524 	modmenu_partial_redraw();
525 }
526 
527 /* Returns scroll offset value for the menu taking menu height into account. */
528 static int
get_effective_menu_scroll_offset(const menu_data_t * menu)529 get_effective_menu_scroll_offset(const menu_data_t *menu)
530 {
531 	return MIN(DIV_ROUND_UP(getmaxy(menu_win) - 3, 2) - 1, cfg.scroll_off);
532 }
533 
534 static void
cmd_ctrl_y(key_info_t key_info,keys_info_t * keys_info)535 cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info)
536 {
537 	if(can_scroll_menu_up(menu))
538 	{
539 		int off = MAX(cfg.scroll_off, 0);
540 		if(menu->pos >= menu->top + getmaxy(menu_win) - 3 - off)
541 		{
542 			menu->pos = menu->top - 1 + getmaxy(menu_win) - 3 - off;
543 		}
544 
545 		--menu->top;
546 		modmenu_partial_redraw();
547 	}
548 }
549 
550 static void
cmd_slash(key_info_t key_info,keys_info_t * keys_info)551 cmd_slash(key_info_t key_info, keys_info_t *keys_info)
552 {
553 	last_search_backward = 0;
554 	menus_search_reset(menu->state, last_search_backward,
555 			def_count(key_info.count));
556 	modcline_enter(CLS_MENU_FSEARCH, "", menu);
557 }
558 
559 /* Jump to percent of list. */
560 static void
cmd_percent(key_info_t key_info,keys_info_t * keys_info)561 cmd_percent(key_info_t key_info, keys_info_t *keys_info)
562 {
563 	int line;
564 	if(key_info.count == NO_COUNT_GIVEN || key_info.count > 100)
565 	{
566 		return;
567 	}
568 	line = (key_info.count*menu->len)/100;
569 	menus_set_pos(menu->state, line);
570 	ui_refresh_win(menu_win);
571 }
572 
573 static void
cmd_colon(key_info_t key_info,keys_info_t * keys_info)574 cmd_colon(key_info_t key_info, keys_info_t *keys_info)
575 {
576 	cmds_conf.begin = 1;
577 	cmds_conf.current = menu->pos;
578 	cmds_conf.end = menu->len;
579 	modcline_enter(CLS_MENU_COMMAND, "", menu);
580 }
581 
582 static void
cmd_qmark(key_info_t key_info,keys_info_t * keys_info)583 cmd_qmark(key_info_t key_info, keys_info_t *keys_info)
584 {
585 	last_search_backward = 1;
586 	menus_search_reset(menu->state, last_search_backward,
587 			def_count(key_info.count));
588 	modcline_enter(CLS_MENU_BSEARCH, "", menu);
589 }
590 
591 /* Populates very custom (unsorted) view with list of files. */
592 static void
cmd_B(key_info_t key_info,keys_info_t * keys_info)593 cmd_B(key_info_t key_info, keys_info_t *keys_info)
594 {
595 	dump_into_custom_view(1);
596 }
597 
598 static void
cmd_G(key_info_t key_info,keys_info_t * keys_info)599 cmd_G(key_info_t key_info, keys_info_t *keys_info)
600 {
601 	if(key_info.count == NO_COUNT_GIVEN)
602 	{
603 		key_info.count = menu->len;
604 	}
605 
606 	menus_erase_current(menu->state);
607 	menus_set_pos(menu->state, key_info.count - 1);
608 	ui_refresh_win(menu_win);
609 }
610 
611 static void
cmd_H(key_info_t key_info,keys_info_t * keys_info)612 cmd_H(key_info_t key_info, keys_info_t *keys_info)
613 {
614 	int top;
615 	int off = MAX(cfg.scroll_off, 0);
616 	if(off > getmaxy(menu_win)/2)
617 	{
618 		return;
619 	}
620 
621 	top = (menu->top == 0) ? 0 : (menu->top + off);
622 
623 	menus_erase_current(menu->state);
624 	menus_set_pos(menu->state, top);
625 	ui_refresh_win(menu_win);
626 }
627 
628 static void
cmd_L(key_info_t key_info,keys_info_t * keys_info)629 cmd_L(key_info_t key_info, keys_info_t *keys_info)
630 {
631 	int top;
632 	int off;
633 	if(menu->key_handler != NULL)
634 	{
635 		if(pass_combination_to_khandler(L"L"))
636 		{
637 			return;
638 		}
639 	}
640 
641 	off = MAX(cfg.scroll_off, 0);
642 	if(off > getmaxy(menu_win)/2)
643 	{
644 		return;
645 	}
646 
647 	top = menu->top + getmaxy(menu_win);
648 	if(menu->top + getmaxy(menu_win) < menu->len - 1)
649 	{
650 		top -= off;
651 	}
652 
653 	menus_erase_current(menu->state);
654 	menus_set_pos(menu->state, top - 3);
655 	ui_refresh_win(menu_win);
656 }
657 
658 /* Moves cursor to the middle of the window. */
659 static void
cmd_M(key_info_t key_info,keys_info_t * keys_info)660 cmd_M(key_info_t key_info, keys_info_t *keys_info)
661 {
662 	int new_pos;
663 	if(menu->len < getmaxy(menu_win))
664 	{
665 		new_pos = DIV_ROUND_UP(menu->len, 2);
666 	}
667 	else
668 	{
669 		new_pos = menu->top + DIV_ROUND_UP(getmaxy(menu_win) - 3, 2);
670 	}
671 
672 	menus_erase_current(menu->state);
673 	menus_set_pos(menu->state, MAX(0, new_pos - 1));
674 	ui_refresh_win(menu_win);
675 }
676 
677 static void
cmd_N(key_info_t key_info,keys_info_t * keys_info)678 cmd_N(key_info_t key_info, keys_info_t *keys_info)
679 {
680 	key_info.count = def_count(key_info.count);
681 	while(key_info.count-- > 0)
682 	{
683 		menus_search_repeat(menu->state, !last_search_backward);
684 	}
685 }
686 
687 /* Populates custom view with list of files. */
688 static void
cmd_b(key_info_t key_info,keys_info_t * keys_info)689 cmd_b(key_info_t key_info, keys_info_t *keys_info)
690 {
691 	dump_into_custom_view(0);
692 }
693 
694 /* Makes custom view of specified type out of menu items. */
695 static void
dump_into_custom_view(int very)696 dump_into_custom_view(int very)
697 {
698 	if(menus_to_custom_view(menu->state, view, very) != 0)
699 	{
700 		show_error_msg("Menu transformation",
701 				"No valid paths discovered in menu content");
702 		return;
703 	}
704 
705 	leave_menu_mode(1);
706 }
707 
708 static void
cmd_dd(key_info_t key_info,keys_info_t * keys_info)709 cmd_dd(key_info_t key_info, keys_info_t *keys_info)
710 {
711 	if(pass_combination_to_khandler(L"dd") && menu->len == 0)
712 	{
713 		show_error_msg("Menu is closing", "No more items in the menu");
714 		leave_menu_mode(1);
715 	}
716 }
717 
718 /* Passes "gf" shortcut to menu as otherwise the shortcut is not available. */
719 static void
cmd_gf(key_info_t key_info,keys_info_t * keys_info)720 cmd_gf(key_info_t key_info, keys_info_t *keys_info)
721 {
722 	(void)pass_combination_to_khandler(L"gf");
723 }
724 
725 /* Gives menu-specific keyboard routine to process the shortcut.  Returns zero
726  * if the shortcut wasn't processed, otherwise non-zero is returned. */
727 static int
pass_combination_to_khandler(const wchar_t keys[])728 pass_combination_to_khandler(const wchar_t keys[])
729 {
730 	KHandlerResponse handler_response;
731 
732 	if(menu->key_handler == NULL)
733 	{
734 		return 0;
735 	}
736 
737 	handler_response = menu->key_handler(view, menu, keys);
738 
739 	switch(handler_response)
740 	{
741 		case KHR_REFRESH_WINDOW:
742 			ui_refresh_win(menu_win);
743 			return 1;
744 		case KHR_CLOSE_MENU:
745 			leave_menu_mode(1);
746 			return 1;
747 		case KHR_MORPHED_MENU:
748 			assert(!vle_mode_is(MENU_MODE) && "Wrong use of KHR_MORPHED_MENU.");
749 			return 1;
750 		case KHR_UNHANDLED:
751 			return 0;
752 
753 		default:
754 			assert(0 && "Unknown menu-specific keyboard handler response.");
755 			return 0;
756 	}
757 }
758 
759 static void
cmd_gg(key_info_t key_info,keys_info_t * keys_info)760 cmd_gg(key_info_t key_info, keys_info_t *keys_info)
761 {
762 	menus_erase_current(menu->state);
763 	menus_set_pos(menu->state, def_count(key_info.count) - 1);
764 	ui_refresh_win(menu_win);
765 }
766 
767 static void
cmd_j(key_info_t key_info,keys_info_t * keys_info)768 cmd_j(key_info_t key_info, keys_info_t *keys_info)
769 {
770 	if(menu->pos != menu->len - 1)
771 	{
772 		menus_erase_current(menu->state);
773 		menu->pos += def_count(key_info.count);
774 		menus_set_pos(menu->state, menu->pos);
775 		ui_refresh_win(menu_win);
776 	}
777 }
778 
779 static void
cmd_k(key_info_t key_info,keys_info_t * keys_info)780 cmd_k(key_info_t key_info, keys_info_t *keys_info)
781 {
782 	if(menu->pos != 0)
783 	{
784 		menus_erase_current(menu->state);
785 		menu->pos -= def_count(key_info.count);
786 		menus_set_pos(menu->state, menu->pos);
787 		ui_refresh_win(menu_win);
788 	}
789 }
790 
791 static void
cmd_n(key_info_t key_info,keys_info_t * keys_info)792 cmd_n(key_info_t key_info, keys_info_t *keys_info)
793 {
794 	key_info.count = def_count(key_info.count);
795 	while(key_info.count-- > 0)
796 	{
797 		menus_search_repeat(menu->state, last_search_backward);
798 	}
799 }
800 
801 /* Handles current content of the menu to Vim as quickfix list. */
802 static void
cmd_v(key_info_t key_info,keys_info_t * keys_info)803 cmd_v(key_info_t key_info, keys_info_t *keys_info)
804 {
805 	int bg;
806 	const char *vi_cmd;
807 	FILE *vim_stdin;
808 	char *cmd;
809 	int i;
810 	int qf = 1;
811 
812 	/* If both first and last lines do not contain colons, treat lines as list of
813 	 * file names. */
814 	if(strchr(menu->items[0], ':') == NULL &&
815 			strchr(menu->items[menu->len - 1], ':') == NULL)
816 	{
817 		qf = 0;
818 	}
819 
820 	ui_shutdown();
821 	stats_refresh_later();
822 
823 	vi_cmd = cfg_get_vicmd(&bg);
824 	if(!qf)
825 	{
826 		char *const arg = shell_like_escape("+exe 'bd!|args' "
827 				"join(map(getline('1','$'),'fnameescape(v:val)'))", 0);
828 		cmd = format_str("%s %s +argument%d -", vi_cmd, arg, menu->pos + 1);
829 		free(arg);
830 	}
831 	else if(menu->pos == 0)
832 	{
833 		/* For some reason +cc1 causes noisy messages on status line, so handle this
834 		 * case separately. */
835 		cmd = format_str("%s +cgetbuffer +bd! +cfirst -", vi_cmd);
836 	}
837 	else
838 	{
839 		cmd = format_str("%s +cgetbuffer +bd! +cfirst +cc%d -", vi_cmd,
840 				menu->pos + 1);
841 	}
842 
843 	vim_stdin = popen(cmd, "w");
844 	free(cmd);
845 
846 	if(vim_stdin == NULL)
847 	{
848 		recover_after_shellout();
849 		show_error_msg("Vim QuickFix", "Failed to send list of files to editor.");
850 		return;
851 	}
852 
853 	for(i = 0; i < menu->len; ++i)
854 	{
855 		fputs(menu->items[i], vim_stdin);
856 		putc('\n', vim_stdin);
857 	}
858 
859 	pclose(vim_stdin);
860 	recover_after_shellout();
861 }
862 
863 static void
cmd_zb(key_info_t key_info,keys_info_t * keys_info)864 cmd_zb(key_info_t key_info, keys_info_t *keys_info)
865 {
866 	if(can_scroll_menu_up(menu))
867 	{
868 		if(menu->pos < getmaxy(menu_win))
869 		{
870 			menu->top = 0;
871 		}
872 		else
873 		{
874 			menu->top = menu->pos - (getmaxy(menu_win) - 3);
875 		}
876 		modmenu_partial_redraw();
877 	}
878 }
879 
880 static void
cmd_zH(key_info_t key_info,keys_info_t * keys_info)881 cmd_zH(key_info_t key_info, keys_info_t *keys_info)
882 {
883 	if(menu->hor_pos != 0)
884 	{
885 		menu->hor_pos = MAX(0, menu->hor_pos - (getmaxx(menu_win) - 4));
886 		modmenu_partial_redraw();
887 	}
888 }
889 
890 static void
cmd_zL(key_info_t key_info,keys_info_t * keys_info)891 cmd_zL(key_info_t key_info, keys_info_t *keys_info)
892 {
893 	menu->hor_pos += getmaxx(menu_win) - 4;
894 	modmenu_partial_redraw();
895 }
896 
897 static void
cmd_zh(key_info_t key_info,keys_info_t * keys_info)898 cmd_zh(key_info_t key_info, keys_info_t *keys_info)
899 {
900 	if(menu->hor_pos != 0)
901 	{
902 		menu->hor_pos = MAX(0, menu->hor_pos - def_count(key_info.count));
903 		modmenu_partial_redraw();
904 	}
905 }
906 
907 static void
cmd_zl(key_info_t key_info,keys_info_t * keys_info)908 cmd_zl(key_info_t key_info, keys_info_t *keys_info)
909 {
910 	menu->hor_pos += def_count(key_info.count);
911 	modmenu_partial_redraw();
912 }
913 
914 static void
cmd_zt(key_info_t key_info,keys_info_t * keys_info)915 cmd_zt(key_info_t key_info, keys_info_t *keys_info)
916 {
917 	if(can_scroll_menu_down(menu))
918 	{
919 		if(menu->len - menu->pos >= getmaxy(menu_win) - 3 + 1)
920 			menu->top = menu->pos;
921 		else
922 			menu->top = menu->len - (getmaxy(menu_win) - 3 + 1);
923 		modmenu_partial_redraw();
924 	}
925 }
926 
927 static void
cmd_zz(key_info_t key_info,keys_info_t * keys_info)928 cmd_zz(key_info_t key_info, keys_info_t *keys_info)
929 {
930 	if(!all_lines_visible(menu))
931 	{
932 		if(menu->pos <= (getmaxy(menu_win) - 3)/2)
933 			menu->top = 0;
934 		else if(menu->pos > menu->len - DIV_ROUND_UP(getmaxy(menu_win) - 3, 2))
935 			menu->top = menu->len - (getmaxy(menu_win) - 3 + 1);
936 		else
937 			menu->top = menu->pos - DIV_ROUND_UP(getmaxy(menu_win) - 3, 2);
938 
939 		modmenu_partial_redraw();
940 	}
941 }
942 
943 /* Returns non-zero if all menu lines are visible, so no scrolling is needed. */
944 static int
all_lines_visible(const menu_data_t * menu)945 all_lines_visible(const menu_data_t *menu)
946 {
947 	return menu->len <= getmaxy(menu_win) - 2;
948 }
949 
950 void
modmenu_partial_redraw(void)951 modmenu_partial_redraw(void)
952 {
953 	menus_partial_redraw(menu->state);
954 	menus_set_pos(menu->state, menu->pos);
955 	ui_refresh_win(menu_win);
956 }
957 
958 static int
goto_cmd(const cmd_info_t * cmd_info)959 goto_cmd(const cmd_info_t *cmd_info)
960 {
961 	if(cmd_info->end != NOT_DEF)
962 	{
963 		menus_erase_current(menu->state);
964 		menus_set_pos(menu->state, cmd_info->end);
965 		ui_refresh_win(menu_win);
966 	}
967 	return 0;
968 }
969 
970 /* Disables highlight of search result matches. */
971 static int
nohlsearch_cmd(const cmd_info_t * cmd_info)972 nohlsearch_cmd(const cmd_info_t *cmd_info)
973 {
974 	menus_search_reset_hilight(menu->state);
975 	return 0;
976 }
977 
978 static int
quit_cmd(const cmd_info_t * cmd_info)979 quit_cmd(const cmd_info_t *cmd_info)
980 {
981 	leave_menu_mode(1);
982 	return 0;
983 }
984 
985 /* Writes all menu lines into file specified as argument. */
986 static int
write_cmd(const cmd_info_t * cmd_info)987 write_cmd(const cmd_info_t *cmd_info)
988 {
989 	char *const no_tilde = expand_tilde(cmd_info->argv[0]);
990 	if(write_file_of_lines(no_tilde, menu->items, menu->len) != 0)
991 	{
992 		show_error_msg("Failed to open output file", strerror(errno));
993 	}
994 	free(no_tilde);
995 	return 0;
996 }
997 
998 void
modmenu_save_pos(void)999 modmenu_save_pos(void)
1000 {
1001 	saved_top = menu->top;
1002 	saved_pos = menu->pos;
1003 }
1004 
1005 void
modmenu_restore_pos(void)1006 modmenu_restore_pos(void)
1007 {
1008 	menu->top = saved_top;
1009 	menu->pos = saved_pos;
1010 }
1011 
1012 void
modmenu_morph_into_cline(CmdLineSubmode submode,const char input[],int external)1013 modmenu_morph_into_cline(CmdLineSubmode submode, const char input[],
1014 		int external)
1015 {
1016 	/* input might point to part of menu data. */
1017 	char *input_copy;
1018 
1019 	if(input[0] == '\0')
1020 	{
1021 		show_error_msg("Command insertion", "Ignoring empty command");
1022 		return;
1023 	}
1024 
1025 	input_copy = external ? format_str("!%s", input) : strdup(input);
1026 	if(input_copy == NULL)
1027 	{
1028 		show_error_msg("Error", "Not enough memory");
1029 		return;
1030 	}
1031 
1032 	leave_menu_mode(0);
1033 	modcline_enter(submode, input_copy, NULL);
1034 
1035 	free(input_copy);
1036 }
1037 
1038 /* Leaves menu mode, possibly resetting selection.  Does nothing if current mode
1039  * isn't menu mode. */
1040 static void
leave_menu_mode(int reset_selection)1041 leave_menu_mode(int reset_selection)
1042 {
1043 	/* Some menu implementation could have switched mode from one of handlers. */
1044 	if(!vle_mode_is(MENU_MODE))
1045 	{
1046 		return;
1047 	}
1048 
1049 	menus_reset_data(menu);
1050 
1051 	if(reset_selection)
1052 	{
1053 		flist_sel_stash(view);
1054 		redraw_view(view);
1055 	}
1056 
1057 	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
1058 
1059 	update_ui_on_leaving();
1060 }
1061 
1062 void
modmenu_run_command(const char cmd[])1063 modmenu_run_command(const char cmd[])
1064 {
1065 	if(exec_command(cmd, view, CIT_COMMAND) < 0)
1066 	{
1067 		ui_sb_err("An error occurred while trying to execute command");
1068 	}
1069 	vle_cmds_init(0, &cmds_conf);
1070 }
1071 
1072 /* Provides access to tests to a local static variable. */
1073 TSTATIC menu_data_t *
menu_get_current(void)1074 menu_get_current(void)
1075 {
1076 	return menu;
1077 }
1078 
1079 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1080 /* vim: set cinoptions+=t0 filetype=c : */
1081