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