diff --git a/ChangeLog b/ChangeLog index b86ea018d..a715e4e6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -33,11 +33,29 @@ additional places to look for plugins. Thanks to 高浩亮 (a.k.a. haolian9). + Added Ctrl-Y key to command-line mode. It activates fast navigation that + allows entering deep paths by a series of searches for individual path + components. Thanks to Henrik Holst (a.k.a. hholst80) and dmocek. + + Added Ctrl-O key to command-line navigation that goes to parent directory. + + Added Ctrl-N/P keys to command-line navigation to move view cursor up/down. + Thanks to Henrik Holst (a.k.a. hholst80) and dmocek. + + Added Arrows/Home/End/Page Up/Page Down keys to command-line navigation to + move view cursor. Thanks to Henrik Holst (a.k.a. hholst80) and dmocek. + + Added :amap, :anoremap and :aunmap commands to configure mappings in + navigation mode. Thanks to Henrik Holst (a.k.a. hholst80) and dmocek. + Reduced amount of memory consumed by `:compare groupids`. Made `:compare bycontents` not bother reading content of files which have unique size. + Provide basic instructions in the documentation on how mappings work. + Thanks to dmocek. + Fixed segfault on trying to use pipe from Lua after its parent VifmJob object was garbage-collected. Thanks to PRESFIL. diff --git a/THANKS b/THANKS index bfb5d4b43..80e60cb5b 100644 --- a/THANKS +++ b/THANKS @@ -75,6 +75,7 @@ DieSpinne dikiy Diogo Lemos (dmlemos) Dmitry Frank (dimonomid) +dmocek eco0414 Egor Gumenyuk (boo1ean) einhander @@ -105,6 +106,7 @@ Hans Kristian Otnes Berge Hans Petter Jansson (hpjansson) heelsleeh Hendrik Jaeger (henk) +Henrik Holst (hholst80) hofheinz hutou infinitewhileloop diff --git a/data/man/vifm.1 b/data/man/vifm.1 index 094023db4..85f1d6032 100644 --- a/data/man/vifm.1 +++ b/data/man/vifm.1 @@ -1,4 +1,4 @@ -.TH VIFM 1 "10 January 2022" "vifm 0.12.1" +.TH VIFM 1 "14 January 2022" "vifm 0.12.1" .\" --------------------------------------------------------------------------- .SH NAME .\" --------------------------------------------------------------------------- @@ -1158,6 +1158,59 @@ insert result of evaluating an expression. Expression is to be entered via nested command-line prompt (where this key does nothing). Expansion of an erroneous expression is empty. .\" --------------------------------------------------------------------------- +.SH Fast navigation +.\" --------------------------------------------------------------------------- +In order to streamline navigation through directory tree, you can enter a +special form of command-line mode from search or local filter prompt. Once +activated, pressing Enter opens currently selected directory and clears the +prompt in anticipation of the next component of the path. If entry under the +cursor is a file, it is opened and the mode is finished. + +This behaviour is embedded in a command-line mode, but doesn't update input +histories nor expands abbreviations and redefines some of the mode's mappings +for the purpose of faster navigation through the file system rather than +command-line editing. When on, prompt gets "nav" prefix. + +You can enable this behaviour on search by default via a mapping like: +.EX + + nnoremap / / + +.EE +.TP +.BI Ctrl-Y +enter navigation mode. Works only for search and local filter started +from a normal mode and only when 'incsearch' is set ('wrapscan' is also nice to +have set for search). +.TP +.BI Ctrl-Y +return to a regular command-line mode. +.TP +.BI "Enter, Right" +either enter a directory under the cursor without leaving the mode and clear +the prompt or leave the mode and open a file under the cursor. +.TP +.BI "Ctrl-O, Left" +go to parent directory. +.TP +.BI "Ctrl-N, Down" +move view cursor down. +.TP +.BI "Ctrl-P, Up" +move view cursor up. +.TP +.BI "Page Down" +scroll view down. +.TP +.BI "Page Up" +scroll view up. +.TP +.BI Home +move view cursor to the first item. +.TP +.BI End +move view cursor to the last item. +.\" --------------------------------------------------------------------------- .SH Pasting special values .\" --------------------------------------------------------------------------- The shortcuts listed below insert specified values into current cursor @@ -1340,6 +1393,8 @@ line. If you want to use '|' in an argument, precede it with '\\'. These commands see '|' as part of their arguments even when it's escaped: :[range]! + :amap + :anoremap :autocmd :cabbrev :cmap @@ -3196,7 +3251,10 @@ map lhs key sequence to rhs in normal and visual modes. map lhs key sequence to rhs in command line mode. .TP -.BI " :cmap :dmap :mmap :nmap :qmap :vmap" +.BI " :amap :cmap :dmap :mmap :nmap :qmap :vmap" +.TP +.BI ":amap lhs rhs" +map lhs to rhs in navigation mode. .TP .BI ":cm[ap] lhs rhs" map lhs to rhs in command line mode. @@ -3219,6 +3277,9 @@ map lhs to rhs in visual mode. .TP .BI " :*map" .TP +.BI :amap +list all maps in navigation mode. +.TP .BI :cm[ap] list all maps in command line mode. .TP @@ -3239,6 +3300,9 @@ list all maps in visual mode. .TP .BI " :*map beginning" .TP +.BI ":amap beginning" +list all maps in navigation mode that start with the beginning. +.TP .BI ":cm[ap] beginning" list all maps in command line mode that start with the beginning. .TP @@ -3267,7 +3331,11 @@ user mappings in rhs. map the key sequence lhs to rhs for command line mode, but don't expand user mappings in rhs. .TP -.BI " :cnoremap :dnoremap :mnoremap :nnoremap :qnoremap :vnoremap" +.BI " :anoremap :cnoremap :dnoremap :mnoremap :nnoremap :qnoremap :vnoremap" +.TP +.BI ":anoremap lhs rhs" +map the key sequence lhs to rhs for navigation mode, but don't expand user +mappings in rhs. .TP .BI ":cno[remap] lhs rhs" map the key sequence lhs to rhs for command line mode, but don't expand user @@ -3301,7 +3369,10 @@ remove user mapping of lhs from normal and visual modes. .BI ":unm[ap]! lhs" remove user mapping of lhs from command line mode. .TP -.BI " :cunmap :dunmap :munmap :nunmap :qunmap :vunmap" +.BI " :aunmap :cunmap :dunmap :munmap :nunmap :qunmap :vunmap" +.TP +.BI ":aunmap lhs" +remove user mapping of lhs from navigation mode. .TP .BI ":cu[nmap] lhs" remove user mapping of lhs from command line mode. @@ -5366,6 +5437,19 @@ Searches wrap around end of the list. .\" --------------------------------------------------------------------------- .SH Mappings .\" --------------------------------------------------------------------------- +A user mapping like `nnoremap lhs rhs` defines a substitution of the +left-hand-side (LHS) with the right-hand-side (RHS) in the input stream. A +regular mapping (without "nore" in :command's name) expands recognized +sequences in the RHS, while "*noremap" mapping always interprets RHS as if no +user mappings were defined and each key has its builtin meaning. In most cases +you want to use noremap variant and if your RHS includes LHS, only noremap +variant will work because recursion in a mapping is not allowed. + +In order to define a mapping determine in which mode you want to activate it +and use an appropriate "*noremap" :command (e.g., :nnoremap for a +normal mode mapping). RHS doesn't have to limit itself to the mode in which +the mapping was started and can span multiple modes. + .B Map arguments LHS of mappings can be preceded by arguments which take the form of special diff --git a/data/vim/doc/app/vifm-app.txt b/data/vim/doc/app/vifm-app.txt index 38cd89984..779826a67 100644 --- a/data/vim/doc/app/vifm-app.txt +++ b/data/vim/doc/app/vifm-app.txt @@ -1,4 +1,4 @@ -*vifm-app.txt* For Vifm version 0.12.1 Last change: 2023 Jan 10 +*vifm-app.txt* For Vifm version 0.12.1 Last change: 2023 Jan 14 Email for bugs and suggestions: @@ -50,6 +50,7 @@ Tag name structure: Menu or dialog command vifm-m_ :help vifm-m_zh Command-line command vifm-: :help vifm-:quit Command-line editing vifm-c_ :help vifm-c_CTRL-H + Command-line navigation vifm-a_ :help vifm-a_CTRL-Y Vifm command argument vifm-- :help vifm--f Option vifm-' :help vifm-'wrap' @@ -1031,6 +1032,51 @@ Ctrl-R = *vifm-c_CTRL-R_=* via nested command-line prompt (where this key does nothing). Expansion of an erroneous expression is empty. +Fast navigation~ + +In order to streamline navigation through directory tree, you can enter a +special form of command-line mode from search or local filter prompt. Once +activated, pressing Enter opens currently selected directory and clears the +prompt in anticipation of the next component of the path. If entry under the +cursor is a file, it is opened and the mode is finished. + +This behaviour is embedded in a command-line mode, but doesn't update input +histories nor expands abbreviations and redefines some of the mode's mappings +for the purpose of faster navigation through the file system rather than +command-line editing. When on, prompt gets "nav" prefix. + +You can enable this behaviour on search by default via a mapping like: > + nnoremap / / + +Ctrl-Y *vifm-c_CTRL-Y* + enter navigation mode. Works only for search and local filter started + from a normal mode and only when |vifm-'incsearch'| is set + (|vifm-'wrapscan'| is also nice to have set for search). + +Ctrl-Y *vifm-a_CTRL-Y* + return to a regular command-line mode. + +Enter, Right *vifm-a_Enter* *vifm-a_Right* + either enter a directory under the cursor without leaving the mode and + clear the prompt or leave the mode and open a file under the cursor. +Ctrl-O, Left *vifm-a_CTRL-O* *vifm-a_Left* + go to parent directory. + +Ctrl-N, Down *vifm-a_CTRL-N* *vifm-a_Down* + move view cursor down. +Ctrl-P, Up *vifm-a_CTRL-P* *vifm-a_Up* + move view cursor up. + +Page Down *vifm-a_PageDown* + scroll view down. +Page Up *vifm-a_PageUp* + scroll view up. + +Home *vifm-a_Home* + move view cursor to the first item. +End *vifm-a_End* + move view cursor to the last item. + Pasting special values~ The shortcuts listed below insert specified values into current cursor @@ -1188,6 +1234,8 @@ line. If you want to use '|' in an argument, precede it with '\'. These commands see '|' as part of their arguments even when it's escaped: :[range]! + :amap + :anoremap :autocmd :cabbrev :cmap @@ -2669,12 +2717,14 @@ yet supported) and might be changed in future releases, or get an alias. :map! lhs rhs map lhs key sequence to rhs in command line mode. + *vifm-:amap* *vifm-:cmap* *vifm-:cm* *vifm-:dmap* *vifm-:dm* *vifm-:mmap* *vifm-:mm* *vifm-:nmap* *vifm-:nm* *vifm-:qmap* *vifm-:qm* *vifm-:vmap* *vifm-:vm* +:amap lhs rhs - map lhs to rhs in navigation mode. :cm[ap] lhs rhs - map lhs to rhs in command line mode. :dm[ap] lhs rhs - map lhs to rhs in dialog modes. :mm[ap] lhs rhs - map lhs to rhs in menu mode. @@ -2682,6 +2732,7 @@ yet supported) and might be changed in future releases, or get an alias. :qm[ap] lhs rhs - map lhs to rhs in view mode. :vm[ap] lhs rhs - map lhs to rhs in visual mode. +:amap - list all maps of navigation mode. :cm[ap] - list all maps of command line mode. :dm[ap] - list all maps of dialog modes. :mm[ap] - list all maps of menu mode. @@ -2689,6 +2740,8 @@ yet supported) and might be changed in future releases, or get an alias. :qm[ap] - list all maps of view mode. :vm[ap] - list all maps of visual mode. +:amap beginning + list all maps of navigation mode that start with the beginning. :cm[ap] beginning list all maps of command line mode that start with the beginning. :dm[ap] beginning @@ -2710,12 +2763,16 @@ yet supported) and might be changed in future releases, or get an alias. map the key sequence lhs to rhs for command line mode, but don't expand user mappings in rhs. + *vifm-:anoremap* *vifm-:cnoremap* *vifm-:cno* *vifm-:dnoremap* *vifm-:dn* *vifm-:mnoremap* *vifm-:mn* *vifm-:nnoremap* *vifm-:nn* *vifm-:qnoremap* *vifm-:qn* *vifm-:vnoremap* *vifm-:vn* +:anoremap lhs rhs + map the key sequence lhs to rhs for navigation mode, but don't expand + user mappings in rhs. :cno[remap] lhs rhs map the key sequence lhs to rhs for command line mode, but don't expand user mappings in rhs. @@ -2741,12 +2798,14 @@ yet supported) and might be changed in future releases, or get an alias. :unm[ap]! lhs remove user mapping of lhs from command line mode. + *vifm-:aunmap* *vifm-:cunmap* *vifm-:cu* *vifm-:dunmap* *vifm-:du* *vifm-:munmap* *vifm-:mu* *vifm-:nunmap* *vifm-:nun* *vifm-:qunmap* *vifm-:qun* *vifm-:vunmap* *vifm-:vu* +:aunmap lhs - remove user mapping of lhs from navigation mode. :cu[nmap] lhs - remove user mapping of lhs from command line mode. :du[nmap] lhs - remove user mapping of lhs from dialog modes. :mu[nmap] lhs - remove user mapping of lhs from menu mode. @@ -4534,6 +4593,19 @@ Searches wrap around end of the list. -------------------------------------------------------------------------------- *vifm-mappings* +A user mapping like `nnoremap lhs rhs` defines a substitution of the +left-hand-side (LHS) with the right-hand-side (RHS) in the input stream. A +regular mapping (without "nore" in :command's name) expands recognized +sequences in the RHS, while "*noremap" mapping always interprets RHS as if no +user mappings were defined and each key has its builtin meaning. In most +cases you want to use noremap variant and if your RHS includes LHS, only +noremap variant will work because recursion in a mapping is not allowed. + +In order to define a mapping determine in which mode you want to activate it +and use an appropriate "*noremap" :command (e.g., |vifm-:nnoremap| for a +normal mode mapping). RHS doesn't have to limit itself to the mode in which +the mapping was started and can span multiple modes. + Map arguments~ LHS of mappings can be preceded by arguments which take the form of special diff --git a/data/vim/syntax/vifm.vim b/data/vim/syntax/vifm.vim index 21dacb83a..7bb0fe8e3 100644 --- a/data/vim/syntax/vifm.vim +++ b/data/vim/syntax/vifm.vim @@ -1,6 +1,6 @@ " vifm syntax file " Maintainer: xaizek -" Last Change: December 10, 2022 +" Last Change: January 14, 2023 " Inspired By: Vim syntax file by Dr. Charles E. Campbell, Jr. if exists('b:current_syntax') @@ -43,9 +43,9 @@ syntax keyword vifmCommandCN contained syntax keyword vifmPrefixCommands contained windo winrun " Map commands -syntax keyword vifmMap contained dm[ap] dn[oremap] du[nmap] map mm[ap] - \ mn[oremap] mu[nmap] nm[ap] nn[oremap] no[remap] nun[map] qm[ap] qn[oremap] - \ qun[map] unm[ap] vm[ap] vn[oremap] vu[nmap] +syntax keyword vifmMap contained amap anoremap aunmap dm[ap] dn[oremap] du[nmap] + \ map mm[ap] mn[oremap] mu[nmap] nm[ap] nn[oremap] no[remap] nun[map] qm[ap] + \ qn[oremap] qun[map] unm[ap] vm[ap] vn[oremap] vu[nmap] \ skipwhite nextgroup=vifmMapArgs syntax keyword vifmCMapAbbr contained ca[bbrev] cm[ap] cnorea[bbrev] cno[remap] \ cuna[bbrev] cu[nmap] diff --git a/src/cmd_core.c b/src/cmd_core.c index d61f431f5..7b0747923 100644 --- a/src/cmd_core.c +++ b/src/cmd_core.c @@ -1056,6 +1056,8 @@ get_cmd_args_type(const char cmd[]) switch(cmd_id) { case COMMAND_CMD_ID: + case COM_AMAP: + case COM_ANOREMAP: case COM_AUTOCMD: case COM_EXECUTE: case COM_CABBR: diff --git a/src/cmd_handlers.c b/src/cmd_handlers.c index 082c01711..0d83ff3b5 100644 --- a/src/cmd_handlers.c +++ b/src/cmd_handlers.c @@ -117,11 +117,14 @@ static int goto_cmd(const cmd_info_t *cmd_info); static int emark_cmd(const cmd_info_t *cmd_info); static int alink_cmd(const cmd_info_t *cmd_info); +static int amap_cmd(const cmd_info_t *cmd_info); +static int anoremap_cmd(const cmd_info_t *cmd_info); static int apropos_cmd(const cmd_info_t *cmd_info); static int autocmd_cmd(const cmd_info_t *cmd_info); static void aucmd_list_cb(const char event[], const char pattern[], int negated, const char action[], void *arg); static void aucmd_action_handler(const char action[], void *arg); +static int aunmap_cmd(const cmd_info_t *cmd_info); static int bmark_cmd(const cmd_info_t *cmd_info); static int bmarks_cmd(const cmd_info_t *cmd_info); static int bmgo_cmd(const cmd_info_t *cmd_info); @@ -354,10 +357,22 @@ const cmd_add_t cmds_list[] = { .flags = HAS_EMARK | HAS_RANGE | HAS_QUOTED_ARGS | HAS_COMMENT | HAS_QMARK_NO_ARGS | HAS_SELECTION_SCOPE, .handler = &alink_cmd, .min_args = 0, .max_args = NOT_DEF, }, + { .name = "amap", .abbr = NULL, .id = COM_AMAP, + .descr = "map keys in navigation mode", + .flags = HAS_RAW_ARGS, + .handler = &amap_cmd, .min_args = 0, .max_args = NOT_DEF, }, + { .name = "anoremap", .abbr = NULL, .id = COM_ANOREMAP, + .descr = "noremap keys in navigation mode", + .flags = HAS_RAW_ARGS, + .handler = &anoremap_cmd, .min_args = 0, .max_args = NOT_DEF, }, { .name = "apropos", .abbr = NULL, .id = -1, .descr = "query apropos results", .flags = 0, .handler = &apropos_cmd, .min_args = 0, .max_args = NOT_DEF, }, + { .name = "aunmap", .abbr = NULL, .id = -1, + .descr = "unmap user keys in navigation mode", + .flags = HAS_RAW_ARGS, + .handler = &aunmap_cmd, .min_args = 1, .max_args = 1, }, { .name = "autocmd", .abbr = "au", .id = COM_AUTOCMD, .descr = "manage autocommands", .flags = HAS_EMARK | HAS_QUOTED_ARGS, @@ -1090,6 +1105,20 @@ alink_cmd(const cmd_info_t *cmd_info) return link_cmd(cmd_info, 1); } +/* Registers a navigation mapping that allows remapping. */ +static int +amap_cmd(const cmd_info_t *cmd_info) +{ + return do_map(cmd_info, "Navigation", NAV_MODE, /*no_remap=*/0) != 0; +} + +/* Registers a navigation mapping that doesn't allow remapping. */ +static int +anoremap_cmd(const cmd_info_t *cmd_info) +{ + return do_map(cmd_info, "Navigation", NAV_MODE, /*no_remap=*/1) != 0; +} + static int apropos_cmd(const cmd_info_t *cmd_info) { @@ -1215,6 +1244,13 @@ aucmd_list_cb(const char event[], const char pattern[], int negated, vle_tb_append_linef(msg, fmt, event, negated ? "!" : "", pattern, action); } +/* Unregisters a navigation mapping. */ +static int +aunmap_cmd(const cmd_info_t *cmd_info) +{ + return do_unmap(cmd_info->argv[0], NAV_MODE); +} + /* Marks directory with set of tags. */ static int bmark_cmd(const cmd_info_t *cmd_info) diff --git a/src/cmd_handlers.h b/src/cmd_handlers.h index c7a98217a..5ca6026f2 100644 --- a/src/cmd_handlers.h +++ b/src/cmd_handlers.h @@ -31,6 +31,8 @@ enum { COM_CDS = NO_COMPLETION_BOUNDARY, + COM_AMAP, + COM_ANOREMAP, COM_CMAP, COM_CNOREMAP, COM_COMMAND, diff --git a/src/filtering.c b/src/filtering.c index 0e1ac461b..448da9c78 100644 --- a/src/filtering.c +++ b/src/filtering.c @@ -762,7 +762,7 @@ find_nearest_neighour(const view_t *view) } void -local_filter_accept(view_t *view) +local_filter_accept(view_t *view, int update_history) { if(!view->local_filter.in_progress) { @@ -773,7 +773,10 @@ local_filter_accept(view_t *view) local_filter_finish(view); - hists_filter_save(view->local_filter.filter.raw); + if(update_history) + { + hists_filter_save(view->local_filter.filter.raw); + } /* Some of previously selected files could be filtered out, update number of * selected files. */ diff --git a/src/filtering.h b/src/filtering.h index 8488eff76..9127ad078 100644 --- a/src/filtering.h +++ b/src/filtering.h @@ -101,7 +101,7 @@ int local_filter_set(struct view_t *view, const char filter[]); void local_filter_update_view(struct view_t *view, int rel_pos); /* Accepts current value of local filter. */ -void local_filter_accept(struct view_t *view); +void local_filter_accept(struct view_t *view, int update_history); /* Sets local filter non-interactively. List of entries doesn't get updated * immediately, an update is scheduled. */ diff --git a/src/flist_pos.c b/src/flist_pos.c index 316516330..c636df3a0 100644 --- a/src/flist_pos.c +++ b/src/flist_pos.c @@ -29,6 +29,7 @@ #include "ui/fileview.h" #include "ui/ui.h" #include "utils/fs.h" +#include "utils/macros.h" #include "utils/regexp.h" #include "utils/path.h" #include "utils/str.h" @@ -99,6 +100,25 @@ fpos_scroll_up(view_t *view, int lines_count) return 0; } +void +fpos_scroll_page(view_t *view, int base, int direction) +{ + enum { HOR_GAP_SIZE = 2, VER_GAP_SIZE = 1 }; + int old_pos = view->list_pos; + 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 + + old_pos%view->run_size - base%view->run_size; + view->list_pos = MAX(0, MIN(view->list_rows - 1, new_pos)); + scroll_by_files(view, direction*offset); + + /* Updating list_pos ourselves doesn't take into account + * synchronization/updates of the other view, so trigger them. */ + ui_view_schedule_redraw(view); + fpos_set_pos(view, view->list_pos); +} + void fpos_set_pos(view_t *view, int pos) { diff --git a/src/flist_pos.h b/src/flist_pos.h index c62725ff1..95ec4d5ff 100644 --- a/src/flist_pos.h +++ b/src/flist_pos.h @@ -43,6 +43,10 @@ int fpos_scroll_down(struct view_t *view, int lines_count); * position was updated. */ int fpos_scroll_up(struct view_t *view, int lines_count); +/* Scrolls the view by one view up or down. The direction should be -1 (up) + * or 1 (down). */ +void fpos_scroll_page(struct view_t *view, int base, int direction); + /* Moves cursor to specified position. Normalizes it if needed, invokes fview * update function and can synchronize cursor position in the other view. */ void fpos_set_pos(struct view_t *view, int pos); diff --git a/src/modes/cmdline.c b/src/modes/cmdline.c index a04c9dc5a..d4322c10e 100644 --- a/src/modes/cmdline.c +++ b/src/modes/cmdline.c @@ -64,6 +64,7 @@ #include "../flist_pos.h" #include "../flist_sel.h" #include "../marks.h" +#include "../running.h" #include "../search.h" #include "../status.h" #include "dialogs/attr_dialog.h" @@ -74,6 +75,9 @@ #include "visual.h" #include "wk.h" +/* Prompt prefix when navigation is enabled. */ +#define NAV_PREFIX L"nav" + /* History search mode. */ typedef enum { @@ -101,6 +105,8 @@ typedef struct int prev_mode; /* Kind of command-line mode. */ CmdLineSubmode sub_mode; + /* Whether performing quick navigation. */ + int navigating; /* Whether current submode allows external editing. */ int sub_mode_allows_ee; /* CLS_MENU_*-specific data. */ @@ -165,14 +171,23 @@ static void handle_nonempty_input(void); static void update_state(int result, int nmatches); static void set_local_filter(const char value[]); static wchar_t * wcsins(wchar_t src[], const wchar_t ins[], int pos); -static int enter_submode(CmdLineSubmode sub_mode, const char initial[]); +static int enter_submode(CmdLineSubmode sub_mode, const char initial[], + int reenter); static void prepare_cmdline_mode(const wchar_t prompt[], const wchar_t cmd[], complete_cmd_func complete, CmdLineSubmode sub_mode, int allow_ee); +static void init_line_stats(line_stats_t *stat, const wchar_t prompt[], + const wchar_t initial[], complete_cmd_func complete, + CmdLineSubmode sub_mode, int allow_ee, int prev_mode); static void save_view_port(void); static void set_view_port(void); static int is_line_edited(void); static void leave_cmdline_mode(int cancelled); +static void free_line_stats(line_stats_t *stat); +static void cmd_ctrl_a(key_info_t key_info, keys_info_t *keys_info); +static void cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info); static void cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info); +static void cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info); +static void cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info); static void cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info); static CmdInputType cls_to_editable_cit(CmdLineSubmode sub_mode); static void extedit_prompt(const char input[], int cursor_col, int is_expr_reg, @@ -190,6 +205,7 @@ static int draw_wild_popup(int *last_pos, int *pos, int *len); static int compute_wild_menu_height(void); static void cmd_ctrl_k(key_info_t key_info, keys_info_t *keys_info); static void cmd_return(key_info_t key_info, keys_info_t *keys_info); +static void nav_open(void); static int is_input_line_empty(void); static void expand_abbrev(void); TSTATIC const wchar_t * extract_abbrev(line_stats_t *stat, int *pos, @@ -205,9 +221,6 @@ static int is_forward_search(CmdLineSubmode sub_mode); static int is_backward_search(CmdLineSubmode sub_mode); static int replace_wstring(wchar_t **str, const wchar_t with[]); static void cmd_ctrl_n(key_info_t key_info, keys_info_t *keys_info); -#ifdef ENABLE_EXTENDED_KEYS -static void cmd_down(key_info_t key_info, keys_info_t *keys_info); -#endif /* ENABLE_EXTENDED_KEYS */ static void hist_next(line_stats_t *stat, const hist_t *hist, size_t len); static void cmd_ctrl_requals(key_info_t key_info, keys_info_t *keys_info); static void expr_reg_prompt_cb(const char expr[], void *arg); @@ -244,21 +257,29 @@ static wchar_t * next_dot_completion(void); static int insert_dot_completion(const wchar_t completion[]); static int insert_str(const wchar_t str[]); static void find_next_word(void); -static void cmd_left(key_info_t key_info, keys_info_t *keys_info); -static void cmd_right(key_info_t key_info, keys_info_t *keys_info); -static void cmd_home(key_info_t key_info, keys_info_t *keys_info); -static void cmd_end(key_info_t key_info, keys_info_t *keys_info); static void cmd_delete(key_info_t key_info, keys_info_t *keys_info); static void update_cursor(void); TSTATIC void hist_prev(line_stats_t *stat, const hist_t *hist, size_t len); static int replace_input_line(line_stats_t *stat, const char new[]); static const hist_t * pick_hist(void); +static int is_cmdmode(int mode); static void update_cmdline(line_stats_t *stat); static int get_required_height(void); +static void cmd_ctrl_o(key_info_t key_info, keys_info_t *keys_info); static void cmd_ctrl_p(key_info_t key_info, keys_info_t *keys_info); static void cmd_ctrl_t(key_info_t key_info, keys_info_t *keys_info); +static void cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info); +static void nav_start(line_stats_t *stat); +static void nav_stop(line_stats_t *stat); #ifdef ENABLE_EXTENDED_KEYS +static void cmd_down(key_info_t key_info, keys_info_t *keys_info); static void cmd_up(key_info_t key_info, keys_info_t *keys_info); +static void cmd_left(key_info_t key_info, keys_info_t *keys_info); +static void cmd_right(key_info_t key_info, keys_info_t *keys_info); +static void cmd_home(key_info_t key_info, keys_info_t *keys_info); +static void cmd_end(key_info_t key_info, keys_info_t *keys_info); +static void cmd_page_up(key_info_t key_info, keys_info_t *keys_info); +static void cmd_page_down(key_info_t key_info, keys_info_t *keys_info); #endif /* ENABLE_EXTENDED_KEYS */ static void update_cmdline_size(void); TSTATIC int line_completion(line_stats_t *stat); @@ -282,6 +303,7 @@ static keys_add_info_t builtin_cmds[] = { {WK_C_k, {{&cmd_ctrl_k}, .descr = "remove line part to the right"}}, {WK_CR, {{&cmd_return}, .descr = "execute/accept input"}}, {WK_C_n, {{&cmd_ctrl_n}, .descr = "recall next history item"}}, + {WK_C_o, {{&cmd_ctrl_o}, .descr = "nav: go to parent directory"}}, {WK_C_p, {{&cmd_ctrl_p}, .descr = "recall previous history item"}}, {WK_C_t, {{&cmd_ctrl_t}, .descr = "swap adjacent characters"}}, {WK_ESC, {{&cmd_ctrl_c}, .descr = "leave cmdline mode"}}, @@ -290,14 +312,15 @@ static keys_add_info_t builtin_cmds[] = { {WK_C_USCORE, {{&cmd_ctrl_underscore}, .descr = "reset completion"}}, {WK_DELETE, {{&cmd_ctrl_h}, .descr = "remove char to the left"}}, {WK_ESC L"[Z", {{&cmd_shift_tab}, .descr = "complete in reverse order"}}, - {WK_C_b, {{&cmd_left}, .descr = "move cursor to the left"}}, - {WK_C_f, {{&cmd_right}, .descr = "move cursor to the right"}}, - {WK_C_a, {{&cmd_home}, .descr = "move cursor to the beginning"}}, - {WK_C_e, {{&cmd_end}, .descr = "move cursor to the end"}}, + {WK_C_b, {{&cmd_ctrl_b}, .descr = "move cursor to the left"}}, + {WK_C_f, {{&cmd_ctrl_f}, .descr = "move cursor to the right"}}, + {WK_C_a, {{&cmd_ctrl_a}, .descr = "move cursor to the beginning"}}, + {WK_C_e, {{&cmd_ctrl_e}, .descr = "move cursor to the end"}}, {WK_C_d, {{&cmd_delete}, .descr = "delete current character"}}, {WK_C_r WK_EQUALS, {{&cmd_ctrl_requals}, .descr = "invoke expression register prompt"}}, {WK_C_u, {{&cmd_ctrl_u}, .descr = "remove line part to the left"}}, {WK_C_w, {{&cmd_ctrl_w}, .descr = "remove word to the left"}}, + {WK_C_y, {{&cmd_ctrl_y}, .descr = "toggle navigation"}}, {WK_C_x WK_SLASH, {{&cmd_ctrl_xslash}, .descr = "insert last search pattern"}}, {WK_C_x WK_a, {{&cmd_ctrl_xa}, .descr = "insert implicit permanent filter value"}}, {WK_C_x WK_c, {{&cmd_ctrl_xc}, .descr = "insert current file name"}}, @@ -325,14 +348,16 @@ static keys_add_info_t builtin_cmds[] = { #endif #ifdef ENABLE_EXTENDED_KEYS {{K(KEY_BACKSPACE)}, {{&cmd_ctrl_h}, .descr = "remove char to the left"}}, - {{K(KEY_DOWN)}, {{&cmd_down}, .descr = "prefix-complete next history item"}}, - {{K(KEY_UP)}, {{&cmd_up}, .descr = "prefix-complete previous history item"}}, - {{K(KEY_LEFT)}, {{&cmd_left}, .descr = "move cursor to the left"}}, - {{K(KEY_RIGHT)}, {{&cmd_right}, .descr = "move cursor to the right"}}, - {{K(KEY_HOME)}, {{&cmd_home}, .descr = "move cursor to the beginning"}}, - {{K(KEY_END)}, {{&cmd_end}, .descr = "move cursor to the end"}}, + {{K(KEY_DOWN)}, {{&cmd_down}, .descr = "prefix-complete next history item/next item"}}, + {{K(KEY_UP)}, {{&cmd_up}, .descr = "prefix-complete previous history item/prev item"}}, + {{K(KEY_LEFT)}, {{&cmd_left}, .descr = "move cursor to the left/parent dir"}}, + {{K(KEY_RIGHT)}, {{&cmd_right}, .descr = "move cursor to the right/enter entry"}}, + {{K(KEY_HOME)}, {{&cmd_home}, .descr = "move cursor to the beginning/first item"}}, + {{K(KEY_END)}, {{&cmd_end}, .descr = "move cursor to the end/last item"}}, {{K(KEY_DC)}, {{&cmd_delete}, .descr = "delete current character"}}, {{K(KEY_BTAB)}, {{&cmd_shift_tab}, .descr = "complete in reverse order"}}, + {{K(KEY_PPAGE)}, {{&cmd_page_up}, .descr = "nav: scroll page up"}}, + {{K(KEY_NPAGE)}, {{&cmd_page_down}, .descr = "nav: scroll page down"}}, #endif /* ENABLE_EXTENDED_KEYS */ {{K(KEY_MOUSE)}, {{&handle_mouse_event}, FOLLOWED_BY_NONE}}, }; @@ -350,6 +375,16 @@ modcline_init(void) (void)ret_code; } +void +modnav_init(void) +{ + vle_keys_set_def_handler(NAV_MODE, &def_handler); + + int ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), NAV_MODE); + assert(ret_code == 0); + (void)ret_code; +} + /* Handles all keys uncaught by shortcuts. Returns zero on success and non-zero * on error. */ static int @@ -641,7 +676,7 @@ modcline_enter(CmdLineSubmode sub_mode, const char initial[]) "Use modcline_in_menu() for CLS_MENU_* submodes."); assert(sub_mode != CLS_PROMPT && "Use modcline_prompt() for CLS_PROMPT submode."); - (void)enter_submode(sub_mode, initial); + (void)enter_submode(sub_mode, initial, /*reenter=*/0); } void @@ -651,7 +686,7 @@ modcline_in_menu(CmdLineSubmode sub_mode, struct menu_data_t *m) sub_mode == CLS_MENU_BSEARCH) && "modcline_in_menu() is only for CLS_MENU_* submodes."); - if(enter_submode(sub_mode, /*initial=*/"") == 0) + if(enter_submode(sub_mode, /*initial=*/"", /*reenter=*/0) == 0) { input_stat.menu = m; } @@ -659,7 +694,7 @@ modcline_in_menu(CmdLineSubmode sub_mode, struct menu_data_t *m) /* Enters command-line editing submode. Returns zero on success. */ static int -enter_submode(CmdLineSubmode sub_mode, const char initial[]) +enter_submode(CmdLineSubmode sub_mode, const char initial[], int reenter) { wchar_t *winitial; const wchar_t *wprompt; @@ -701,8 +736,23 @@ enter_submode(CmdLineSubmode sub_mode, const char initial[]) wprompt = L"E"; } - prepare_cmdline_mode(wprompt, winitial, complete_func, sub_mode, - /*allow_ee=*/0); + if(reenter) + { + int prev_mode = input_stat.prev_mode; + free_line_stats(&input_stat); + init_line_stats(&input_stat, wprompt, winitial, complete_func, sub_mode, + /*allow_ee=*/0, prev_mode); + + /* Just in case. */ + curr_stats.save_msg = 1; + + stats_redraw_later(); + } + else + { + prepare_cmdline_mode(wprompt, winitial, complete_func, sub_mode, + /*allow_ee=*/0); + } free(winitial); return 0; @@ -778,57 +828,20 @@ static void prepare_cmdline_mode(const wchar_t prompt[], const wchar_t initial[], complete_cmd_func complete, CmdLineSubmode sub_mode, int allow_ee) { - if(vle_mode_get() == CMDLINE_MODE) + if(is_cmdmode(vle_mode_get())) { /* We're recursing into command-line mode. */ ui_sb_unlock(); prev_input_stat = input_stat; } - input_stat.sub_mode = sub_mode; - input_stat.sub_mode_allows_ee = allow_ee; - input_stat.menu = NULL; - input_stat.prompt_callback = NULL; - input_stat.prompt_callback_arg = NULL; - - input_stat.prev_mode = vle_mode_get(); - vle_mode_set(CMDLINE_MODE, VMT_SECONDARY); - line_width = getmaxx(stdscr); - ui_sb_lock(); - input_stat.line = vifm_wcsdup(initial); - input_stat.initial_line = vifm_wcsdup(input_stat.line); - input_stat.index = wcslen(initial); - input_stat.curs_pos = esc_wcswidth(input_stat.line, (size_t)-1); - input_stat.len = input_stat.index; - input_stat.cmd_pos = -1; - input_stat.complete_continue = 0; - input_stat.history_search = HIST_NONE; - input_stat.line_buf = NULL; - input_stat.reverse_completion = 0; - input_stat.complete = complete; - input_stat.search_mode = 0; - input_stat.dot_pos = -1; - input_stat.line_edited = 0; - input_stat.enter_mapping_state = vle_keys_mapping_state(); - input_stat.state = PS_NORMAL; + init_line_stats(&input_stat, prompt, initial, complete, sub_mode, allow_ee, + vle_mode_get()); - if((is_forward_search(sub_mode) || is_backward_search(sub_mode)) && - sub_mode != CLS_VWFSEARCH && sub_mode != CLS_VWBSEARCH) - { - input_stat.search_mode = 1; - } - - if(input_stat.search_mode || sub_mode == CLS_FILTER) - { - save_view_port(); - } - - wcsncpy(input_stat.prompt, prompt, ARRAY_LEN(input_stat.prompt)); - input_stat.prompt_wid = esc_wcswidth(input_stat.prompt, (size_t)-1); - input_stat.curs_pos += input_stat.prompt_wid; + vle_mode_set(CMDLINE_MODE, VMT_SECONDARY); update_cmdline_size(); draw_cmdline_text(&input_stat); @@ -842,6 +855,54 @@ prepare_cmdline_mode(const wchar_t prompt[], const wchar_t initial[], ui_set_cursor(/*visibility=*/1); } +/* Initializes command-line status. */ +static void +init_line_stats(line_stats_t *stat, const wchar_t prompt[], + const wchar_t initial[], complete_cmd_func complete, + CmdLineSubmode sub_mode, int allow_ee, int prev_mode) +{ + stat->sub_mode = sub_mode; + stat->navigating = 0; + stat->sub_mode_allows_ee = allow_ee; + stat->menu = NULL; + stat->prompt_callback = NULL; + stat->prompt_callback_arg = NULL; + + stat->prev_mode = prev_mode; + + stat->line = vifm_wcsdup(initial); + stat->initial_line = vifm_wcsdup(stat->line); + stat->index = wcslen(initial); + stat->curs_pos = esc_wcswidth(stat->line, (size_t)-1); + stat->len = stat->index; + stat->cmd_pos = -1; + stat->complete_continue = 0; + stat->history_search = HIST_NONE; + stat->line_buf = NULL; + stat->reverse_completion = 0; + stat->complete = complete; + stat->search_mode = 0; + stat->dot_pos = -1; + stat->line_edited = 0; + stat->enter_mapping_state = vle_keys_mapping_state(); + stat->state = PS_NORMAL; + + if((is_forward_search(sub_mode) || is_backward_search(sub_mode)) && + sub_mode != CLS_VWFSEARCH && sub_mode != CLS_VWBSEARCH) + { + stat->search_mode = 1; + } + + if(stat->search_mode || sub_mode == CLS_FILTER) + { + save_view_port(); + } + + wcsncpy(stat->prompt, prompt, ARRAY_LEN(stat->prompt)); + stat->prompt_wid = esc_wcswidth(stat->prompt, (size_t)-1); + stat->curs_pos += stat->prompt_wid; +} + /* Stores view port parameters (top line, current position). */ static void save_view_port(void) @@ -901,16 +962,11 @@ is_line_edited(void) static void leave_cmdline_mode(int cancelled) { - free(input_stat.line); - free(input_stat.initial_line); - free(input_stat.line_buf); - input_stat.line = NULL; - input_stat.initial_line = NULL; - input_stat.line_buf = NULL; + free_line_stats(&input_stat); - if(vle_mode_is(CMDLINE_MODE)) + if(is_cmdmode(vle_mode_get())) { - if(input_stat.prev_mode == CMDLINE_MODE) + if(is_cmdmode(input_stat.prev_mode)) { /* We're restoring from a recursive command-line mode. */ input_stat = prev_input_stat; @@ -962,6 +1018,42 @@ leave_cmdline_mode(int cancelled) } } +/* Frees resources allocated for command-line status. */ +static void +free_line_stats(line_stats_t *stat) +{ + free(input_stat.line); + free(input_stat.initial_line); + free(input_stat.line_buf); + input_stat.line = NULL; + input_stat.initial_line = NULL; + input_stat.line_buf = NULL; +} + +/* Moves command-line cursor to the beginning of command-line. */ +static void +cmd_ctrl_a(key_info_t key_info, keys_info_t *keys_info) +{ + input_stat.index = 0; + input_stat.curs_pos = input_stat.prompt_wid; + update_cursor(); +} + +/* Moves command-line cursor to the left. */ +static void +cmd_ctrl_b(key_info_t key_info, keys_info_t *keys_info) +{ + input_stat.history_search = HIST_NONE; + stop_completion(); + + if(input_stat.index > 0) + { + input_stat.index--; + input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index]); + update_cursor(); + } +} + /* Initiates leaving of command-line mode and reverting related changes in other * parts of the interface. */ static void @@ -1008,6 +1100,34 @@ cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info) } } +/* Moves command-line cursor to the end of command-line. */ +static void +cmd_ctrl_e(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.index == input_stat.len) + return; + + input_stat.index = input_stat.len; + input_stat.curs_pos = input_stat.prompt_wid + + esc_wcswidth(input_stat.line, (size_t)-1); + update_cursor(); +} + +/* Moves command-line cursor to the right. */ +static void +cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info) +{ + input_stat.history_search = HIST_NONE; + stop_completion(); + + if(input_stat.index < input_stat.len) + { + input_stat.curs_pos += esc_wcwidth(input_stat.line[input_stat.index]); + input_stat.index++; + update_cursor(); + } +} + /* Opens the editor with already typed in characters, gets entered line and * executes it as if it was typed. */ static void @@ -1034,7 +1154,7 @@ cmd_ctrl_g(key_info_t key_info, keys_info_t *keys_info) if(prompt_ee) { - int is_expr_reg = (prev_mode == CMDLINE_MODE); + int is_expr_reg = is_cmdmode(prev_mode); extedit_prompt(mbstr, index + 1, is_expr_reg, prompt_callback, prompt_callback_arg); } @@ -1427,6 +1547,12 @@ static void cmd_return(key_info_t key_info, keys_info_t *keys_info) { /* TODO: refactor this cmd_return() function. */ + if(input_stat.navigating) + { + nav_open(); + return; + } + stop_completion(); werase(status_bar); wnoutrefresh(status_bar); @@ -1490,7 +1616,7 @@ cmd_return(key_info_t key_info, keys_info_t *keys_info) { if(cfg.inc_search) { - local_filter_accept(curr_view); + local_filter_accept(curr_view, /*update_history=*/1); } else { @@ -1550,6 +1676,39 @@ cmd_return(key_info_t key_info, keys_info_t *keys_info) free(input); } +/* Opens current entry while navigating. */ +static void +nav_open(void) +{ + dir_entry_t *curr = get_current_entry(curr_view); + if(fentry_is_fake(curr)) + { + return; + } + + int is_dir_like = (fentry_is_dir(curr) || is_unc_root(curr_view->curr_dir)); + + CmdLineSubmode sub_mode = input_stat.sub_mode; + if(sub_mode == CLS_FILTER) + { + /* Accepting filter changes list of files, but in case of error it might be + * better. */ + local_filter_accept(curr_view, /*update_history=*/0); + } + + if(is_dir_like) + { + rn_enter_dir(curr_view); + enter_submode(sub_mode, /*initial=*/"", /*reenter=*/1); + nav_start(&input_stat); + } + else + { + leave_cmdline_mode(/*cancelled=*/0); + rn_open(curr_view, FHE_RUN); + } +} + /* Checks whether input line is empty. Returns non-zero if so, otherwise * non-zero is returned. */ static int @@ -1567,8 +1726,9 @@ expand_abbrev(void) const wchar_t *abbrev_rhs; int no_remap; - /* No recursion on expanding abbreviations. */ - if(input_stat.expanding_abbrev) + /* Don't expand command-line abbreviations in navigation and avoid recursion + * on expanding abbreviations. */ + if(input_stat.navigating || input_stat.expanding_abbrev) { return; } @@ -1662,7 +1822,7 @@ save_input_to_history(const keys_info_t *keys_info, const char input[]) } else if(input_stat.sub_mode == CLS_PROMPT) { - const int is_expr_reg = (input_stat.prev_mode == CMDLINE_MODE); + const int is_expr_reg = is_cmdmode(input_stat.prev_mode); save_prompt_to_history(input, is_expr_reg); } } @@ -1767,37 +1927,29 @@ replace_wstring(wchar_t **str, const wchar_t with[]) return 0; } +/* Recalls a newer historical entry or moves view cursor if navigating. */ static void cmd_ctrl_n(key_info_t key_info, keys_info_t *keys_info) { - stop_completion(); - - if(input_stat.history_search == HIST_NONE) - save_users_input(); - - input_stat.history_search = HIST_GO; - - hist_next(&input_stat, pick_hist(), cfg.history_len); -} + if(input_stat.navigating) + { + if(fpos_can_move_down(curr_view)) + { + int new_pos = curr_view->list_pos + fpos_get_ver_step(curr_view); + fpos_set_pos(curr_view, new_pos); + } + return; + } -#ifdef ENABLE_EXTENDED_KEYS -static void -cmd_down(key_info_t key_info, keys_info_t *keys_info) -{ stop_completion(); if(input_stat.history_search == HIST_NONE) save_users_input(); - if(input_stat.history_search != HIST_SEARCH) - { - input_stat.history_search = HIST_SEARCH; - input_stat.hist_search_len = input_stat.len; - } + input_stat.history_search = HIST_GO; hist_next(&input_stat, pick_hist(), cfg.history_len); } -#endif /* ENABLE_EXTENDED_KEYS */ /* Puts next element in the history or restores user input if end of history has * been reached. hist can be NULL, in which case nothing happens. */ @@ -1857,7 +2009,7 @@ cmd_ctrl_requals(key_info_t key_info, keys_info_t *keys_info) input_stat.history_search = HIST_NONE; stop_completion(); - if(input_stat.prev_mode != CMDLINE_MODE) + if(!is_cmdmode(input_stat.prev_mode)) { modcline_prompt("(=)", "", &expr_reg_prompt_cb, /*cb_arg=*/NULL, &expr_reg_prompt_completion, /*allow_ee=*/1); @@ -2384,55 +2536,6 @@ find_next_word(void) } } -static void -cmd_left(key_info_t key_info, keys_info_t *keys_info) -{ - input_stat.history_search = HIST_NONE; - stop_completion(); - - if(input_stat.index > 0) - { - input_stat.index--; - input_stat.curs_pos -= esc_wcwidth(input_stat.line[input_stat.index]); - update_cursor(); - } -} - -static void -cmd_right(key_info_t key_info, keys_info_t *keys_info) -{ - input_stat.history_search = HIST_NONE; - stop_completion(); - - if(input_stat.index < input_stat.len) - { - input_stat.curs_pos += esc_wcwidth(input_stat.line[input_stat.index]); - input_stat.index++; - update_cursor(); - } -} - -static void -cmd_home(key_info_t key_info, keys_info_t *keys_info) -{ - input_stat.index = 0; - input_stat.curs_pos = input_stat.prompt_wid; - update_cursor(); -} - -/* Moves cursor to the end of command-line on Ctrl+E and End. */ -static void -cmd_end(key_info_t key_info, keys_info_t *keys_info) -{ - if(input_stat.index == input_stat.len) - return; - - input_stat.index = input_stat.len; - input_stat.curs_pos = input_stat.prompt_wid + - esc_wcswidth(input_stat.line, (size_t)-1); - update_cursor(); -} - static void cmd_delete(key_info_t key_info, keys_info_t *keys_info) { @@ -2472,9 +2575,40 @@ replace_input_line(line_stats_t *stat, const char new[]) return 0; } +/* Goes to parent directory when in navigation. */ +static void +cmd_ctrl_o(key_info_t key_info, keys_info_t *keys_info) +{ + if(!input_stat.navigating) + { + return; + } + + CmdLineSubmode sub_mode = input_stat.sub_mode; + if(sub_mode == CLS_FILTER) + { + local_filter_cancel(curr_view); + } + + rn_leave(curr_view, /*levels=*/1); + enter_submode(sub_mode, /*initial=*/"", /*reenter=*/1); + nav_start(&input_stat); +} + +/* Recalls an older historical entry or moves view cursor if navigating. */ static void cmd_ctrl_p(key_info_t key_info, keys_info_t *keys_info) { + if(input_stat.navigating) + { + if(fpos_can_move_up(curr_view)) + { + int new_pos = curr_view->list_pos - fpos_get_ver_step(curr_view); + fpos_set_pos(curr_view, new_pos); + } + return; + } + stop_completion(); if(input_stat.history_search == HIST_NONE) @@ -2517,10 +2651,103 @@ cmd_ctrl_t(key_info_t key_info, keys_info_t *keys_info) update_cmdline_text(&input_stat); } +/* Toggles navigation for search/filtering. */ +static void +cmd_ctrl_y(key_info_t key_info, keys_info_t *keys_info) +{ + if(!input_stat.search_mode && input_stat.sub_mode != CLS_FILTER) + { + return; + } + if(!cfg.inc_search || input_stat.prev_mode != NORMAL_MODE) + { + return; + } + + if(!input_stat.navigating) + { + nav_start(&input_stat); + } + else + { + nav_stop(&input_stat); + } + update_cmdline_text(&input_stat); +} + +/* Enables navigation. */ +static void +nav_start(line_stats_t *stat) +{ + stat->navigating = 1; + + size_t nav_prefix_len = wcslen(NAV_PREFIX); + + wchar_t new_prompt[NAME_MAX + 1] = NAV_PREFIX; + wcsncpy(new_prompt + nav_prefix_len, stat->prompt, + ARRAY_LEN(new_prompt) - nav_prefix_len - 1); + + wcscpy(stat->prompt, new_prompt); + stat->prompt_wid += nav_prefix_len; + stat->curs_pos += nav_prefix_len; + + vle_mode_set(NAV_MODE, VMT_SECONDARY); +} + +/* Disables navigation. */ +static void +nav_stop(line_stats_t *stat) +{ + stat->navigating = 0; + + size_t nav_prefix_len = wcslen(NAV_PREFIX); + + memmove(stat->prompt, &stat->prompt[nav_prefix_len], + sizeof(stat->prompt) - sizeof(wchar_t)*nav_prefix_len); + stat->prompt_wid -= nav_prefix_len; + stat->curs_pos -= nav_prefix_len; + + vle_mode_set(CMDLINE_MODE, VMT_SECONDARY); +} + #ifdef ENABLE_EXTENDED_KEYS + +/* Fetches a matching future historical entry or moves view cursor down in + * navigation. */ +static void +cmd_down(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + cmd_ctrl_n(key_info, keys_info); + return; + } + + stop_completion(); + + if(input_stat.history_search == HIST_NONE) + save_users_input(); + + if(input_stat.history_search != HIST_SEARCH) + { + input_stat.history_search = HIST_SEARCH; + input_stat.hist_search_len = input_stat.len; + } + + hist_next(&input_stat, pick_hist(), cfg.history_len); +} + +/* Fetches a matching past historical entry or moves view cursor up in + * navigation. */ static void cmd_up(key_info_t key_info, keys_info_t *keys_info) { + if(input_stat.navigating) + { + cmd_ctrl_p(key_info, keys_info); + return; + } + stop_completion(); if(input_stat.history_search == HIST_NONE) @@ -2534,6 +2761,87 @@ cmd_up(key_info_t key_info, keys_info_t *keys_info) hist_prev(&input_stat, pick_hist(), cfg.history_len); } + +/* Moves command-line cursor to the left or goes to parent directory in + * navigation. */ +static void +cmd_left(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + cmd_ctrl_o(key_info, keys_info); + } + else + { + cmd_ctrl_b(key_info, keys_info); + } +} + +/* Moves command-line cursor to the right or enter active entry in + * navigation. */ +static void +cmd_right(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + cmd_return(key_info, keys_info); + } + else + { + cmd_ctrl_f(key_info, keys_info); + } +} + +/* Moves command-line cursor to the beginning of command-line or moves + * view cursor to the first file in navigation. */ +static void +cmd_home(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + fpos_set_pos(curr_view, 0); + } + else + { + cmd_ctrl_a(key_info, keys_info); + } +} + +/* Moves command-line cursor to the end of command-line or moves view cursor to + * the last file in navigation. */ +static void +cmd_end(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + fpos_set_pos(curr_view, curr_view->list_rows - 1); + } + else + { + cmd_ctrl_e(key_info, keys_info); + } +} + +/* Scrolls view up in navigation. */ +static void +cmd_page_up(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + fview_scroll_page_up(curr_view); + } +} + +/* Scrolls view down in navigation. */ +static void +cmd_page_down(key_info_t key_info, keys_info_t *keys_info) +{ + if(input_stat.navigating) + { + fview_scroll_page_down(curr_view); + } +} + #endif /* ENABLE_EXTENDED_KEYS */ /* Puts previous element in the history. hist can be NULL, in which case @@ -2613,7 +2921,7 @@ pick_hist(void) } if(input_stat.sub_mode == CLS_PROMPT) { - if(input_stat.prev_mode == CMDLINE_MODE) + if(is_cmdmode(input_stat.prev_mode)) { return &curr_stats.exprreg_hist; } @@ -2626,6 +2934,13 @@ pick_hist(void) return NULL; } +/* Checks for a command-line-like mode. */ +static int +is_cmdmode(int mode) +{ + return (mode == CMDLINE_MODE || mode == NAV_MODE); +} + /* Updates command-line properties and redraws it. */ static void update_cmdline(line_stats_t *stat) diff --git a/src/modes/cmdline.h b/src/modes/cmdline.h index 55cd255b4..17c65f9d4 100644 --- a/src/modes/cmdline.h +++ b/src/modes/cmdline.h @@ -53,6 +53,9 @@ typedef int (*complete_cmd_func)(const char cmd[], void *arg); /* Initializes command-line mode. */ void modcline_init(void); +/* Initializes navigation mode, which is nested into the command-line mode. */ +void modnav_init(void); + /* Enters command-line editing mode with specified submode. initial is the * start value. */ void modcline_enter(CmdLineSubmode sub_mode, const char initial[]); @@ -101,6 +104,8 @@ typedef struct int prev_mode; /* Kind of command-line mode. */ CmdLineSubmode sub_mode; + /* Whether performing quick navigation. */ + int navigating; /* Whether current submode allows external editing. */ int sub_mode_allows_ee; /* CLS_MENU_*-specific data. */ diff --git a/src/modes/modes.c b/src/modes/modes.c index b019ae974..e223d203b 100644 --- a/src/modes/modes.c +++ b/src/modes/modes.c @@ -48,6 +48,7 @@ static int mode_flags[] = { MF_USES_COUNT | MF_USES_REGS, /* NORMAL_MODE */ MF_USES_INPUT, /* CMDLINE_MODE */ + MF_USES_INPUT, /* NAV_MODE */ MF_USES_COUNT | MF_USES_REGS, /* VISUAL_MODE */ MF_USES_COUNT, /* MENU_MODE */ MF_USES_COUNT, /* SORT_MODE */ @@ -63,6 +64,7 @@ ARRAY_GUARD(mode_flags, MODES_COUNT); static char uses_input_bar[] = { 1, /* NORMAL_MODE */ 0, /* CMDLINE_MODE */ + 0, /* NAV_MODE */ 1, /* VISUAL_MODE */ 1, /* MENU_MODE */ 1, /* SORT_MODE */ @@ -79,6 +81,7 @@ typedef void (*mode_init_func)(void); static mode_init_func mode_init_funcs[] = { &modnorm_init, /* NORMAL_MODE */ &modcline_init, /* CMDLINE_MODE */ + &modnav_init, /* NAV_MODE */ &modvis_init, /* VISUAL_MODE */ &modmenu_init, /* MENU_MODE */ &init_sort_dialog_mode, /* SORT_MODE */ @@ -115,7 +118,7 @@ modes_pre(void) { /* Do nothing for these modes. */ } - else if(vle_mode_is(CMDLINE_MODE)) + else if(ANY(vle_mode_is, CMDLINE_MODE, NAV_MODE)) { touchwin(status_bar); ui_refresh_win(status_bar); @@ -146,7 +149,7 @@ void modes_post(void) { if(ANY(vle_mode_is, - CMDLINE_MODE, SORT_MODE, CHANGE_MODE, ATTR_MODE, MORE_MODE)) + CMDLINE_MODE, NAV_MODE, SORT_MODE, CHANGE_MODE, ATTR_MODE, MORE_MODE)) { /* Do nothing for these modes. */ return; @@ -184,7 +187,7 @@ modes_post(void) void modes_statusbar_update(void) { - if(vle_mode_is(MORE_MODE) || vle_mode_is(CMDLINE_MODE) || + if(ANY(vle_mode_is, MORE_MODE, CMDLINE_MODE, NAV_MODE) || curr_stats.save_msg != 0) { return; @@ -237,7 +240,7 @@ modes_redraw(void) goto finish; } - if(vle_mode_is(CMDLINE_MODE)) + if(ANY(vle_mode_is, CMDLINE_MODE, NAV_MODE)) { modcline_redraw(); goto finish; @@ -308,7 +311,7 @@ modes_redraw(void) void modes_update(void) { - if(vle_mode_is(CMDLINE_MODE)) + if(ANY(vle_mode_is, CMDLINE_MODE, NAV_MODE)) { modcline_redraw(); return; diff --git a/src/modes/modes.h b/src/modes/modes.h index 89b9ff980..d335e3900 100644 --- a/src/modes/modes.h +++ b/src/modes/modes.h @@ -26,6 +26,7 @@ enum { NORMAL_MODE, CMDLINE_MODE, + NAV_MODE, VISUAL_MODE, MENU_MODE, SORT_MODE, diff --git a/src/modes/normal.c b/src/modes/normal.c index d45c1f5f4..6ca483eba 100644 --- a/src/modes/normal.c +++ b/src/modes/normal.c @@ -88,7 +88,6 @@ 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_space(key_info_t key_info, keys_info_t *keys_info); static void cmd_emarkemark(key_info_t key_info, keys_info_t *keys_info); @@ -519,10 +518,7 @@ 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) { - if(can_scroll_up(curr_view)) - { - page_scroll(fpos_get_last_visible_cell(curr_view), -1); - } + fview_scroll_page_up(curr_view); } /* Resets selection and search highlight. */ @@ -559,31 +555,7 @@ 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) { - if(can_scroll_down(curr_view)) - { - page_scroll(curr_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) -{ - enum { HOR_GAP_SIZE = 2, VER_GAP_SIZE = 1 }; - int old_pos = curr_view->list_pos; - int offset = fview_is_transposed(curr_view) - ? (MAX(1, curr_view->column_count - VER_GAP_SIZE))*curr_view->window_rows - : (curr_view->window_rows - HOR_GAP_SIZE)*curr_view->column_count; - int new_pos = base + direction*offset - + old_pos%curr_view->run_size - base%curr_view->run_size; - curr_view->list_pos = MAX(0, MIN(curr_view->list_rows - 1, new_pos)); - scroll_by_files(curr_view, direction*offset); - - /* Updating list_pos ourselves doesn't take into account - * synchronization/updates of the other view, so trigger them. */ - ui_view_schedule_redraw(curr_view); - fpos_set_pos(curr_view, curr_view->list_pos); + fview_scroll_page_down(curr_view); } static void diff --git a/src/modes/visual.c b/src/modes/visual.c index e849bf0d5..51d68a94b 100644 --- a/src/modes/visual.c +++ b/src/modes/visual.c @@ -442,6 +442,7 @@ cmd_ctrl_f(key_info_t key_info, keys_info_t *keys_info) 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 diff --git a/src/running.c b/src/running.c index c0d209590..1bf0907ad 100644 --- a/src/running.c +++ b/src/running.c @@ -104,7 +104,6 @@ static void run_implicit_prog(view_t *view, const char prog_spec[], int pause, int force_bg); static void view_current_file(const view_t *view); static void follow_link(view_t *view, int follow_dirs, int ultimate); -static void enter_dir(struct view_t *view); static int cd_to_parent_dir(view_t *view); static void extract_last_path_component(const char path[], char buf[]); static char * run_shell_prepare(const char command[], ShellPause pause, @@ -184,7 +183,7 @@ handle_file(view_t *view, FileHandleExec exec, FileHandleLink follow) int dir_like_entry = (is_dir(full_path) || is_unc_root(view->curr_dir)); if(dir_like_entry) { - enter_dir(view); + rn_enter_dir(view); return; } } @@ -466,7 +465,7 @@ run_with_defaults(view_t *view) { if(get_current_entry(view)->type == FT_DIR) { - enter_dir(view); + rn_enter_dir(view); return; } @@ -554,7 +553,7 @@ rn_open_with(view_t *view, const char prog_spec[], int dont_execute, } else if(strcmp(prog_spec, VIFM_PSEUDO_CMD) == 0) { - enter_dir(view); + rn_enter_dir(view); } else if(strchr(prog_spec, '%') != NULL) { @@ -742,9 +741,8 @@ follow_link(view_t *view, int follow_dirs, int ultimate) free(dir); } -/* Handles opening of current entry of the view as a directory. */ -static void -enter_dir(view_t *view) +void +rn_enter_dir(view_t *view) { dir_entry_t *const curr = get_current_entry(view); @@ -756,7 +754,6 @@ enter_dir(view_t *view) char full_path[PATH_MAX + 1]; get_full_path_of(curr, sizeof(full_path), full_path); - if(cd_is_possible(full_path)) { curr_stats.ch_pos = (cfg_ch_pos_on(CHPOS_ENTER) ? 1 : 0); diff --git a/src/running.h b/src/running.h index 01a7f9ba5..1b0f2cbd4 100644 --- a/src/running.h +++ b/src/running.h @@ -46,6 +46,9 @@ struct view_t; /* Handles opening of current file/selection of the view. */ void rn_open(struct view_t *view, FileHandleExec exec); +/* Handles opening of current entry of the view as a directory. */ +void rn_enter_dir(struct view_t *view); + /* Follows file to find its true location (e.g. target of symbolic link) or just * opens it. */ void rn_follow(struct view_t *view, int ultimate); diff --git a/src/tags.c b/src/tags.c index fff110d75..682fa9bb9 100644 --- a/src/tags.c +++ b/src/tags.c @@ -186,8 +186,11 @@ const char *tags[] = { "vifm-:!", "vifm-:!!", "vifm-:alink", + "vifm-:amap", + "vifm-:anoremap", "vifm-:apropos", "vifm-:au", + "vifm-:aunmap", "vifm-:autocmd", "vifm-:bar", "vifm-:bmark", @@ -507,6 +510,19 @@ const char *tags[] = { "vifm-]s", "vifm-]z", "vifm-^", + "vifm-a_CTRL-N", + "vifm-a_CTRL-O", + "vifm-a_CTRL-P", + "vifm-a_CTRL-Y", + "vifm-a_Down", + "vifm-a_End", + "vifm-a_Enter", + "vifm-a_Home", + "vifm-a_Left", + "vifm-a_PageDown", + "vifm-a_PageUp", + "vifm-a_Right", + "vifm-a_Up", "vifm-al", "vifm-app.txt", "vifm-av", @@ -547,6 +563,7 @@ const char *tags[] = { "vifm-c_CTRL-X_m", "vifm-c_CTRL-X_r", "vifm-c_CTRL-X_t", + "vifm-c_CTRL-Y", "vifm-c_CTRL-]", "vifm-c_CTRL-_", "vifm-c_Delete", diff --git a/src/ui/fileview.c b/src/ui/fileview.c index ceb8bf933..b27e73a0d 100644 --- a/src/ui/fileview.c +++ b/src/ui/fileview.c @@ -1111,6 +1111,24 @@ update_scroll_bind_offset(void) curr_stats.scroll_bind_off = rwin_pos - lwin_pos; } +void +fview_scroll_page_up(view_t *view) +{ + if(can_scroll_up(view)) + { + fpos_scroll_page(view, fpos_get_last_visible_cell(view), -1); + } +} + +void +fview_scroll_page_down(view_t *view) +{ + if(can_scroll_down(view)) + { + fpos_scroll_page(view, view->top_line, 1); + } +} + /* Print callback for column_view unit. */ static void column_line_print(const char buf[], size_t offset, AlignType align, diff --git a/src/ui/fileview.h b/src/ui/fileview.h index 4195fb52a..3120f3a71 100644 --- a/src/ui/fileview.h +++ b/src/ui/fileview.h @@ -128,6 +128,12 @@ void scroll_by_files(struct view_t *view, int by); /* Recalculates difference of two panes scroll positions. */ void update_scroll_bind_offset(void); +/* Scrolls the view one page up. */ +void fview_scroll_page_up(struct view_t *view); + +/* Scrolls the view one page down. */ +void fview_scroll_page_down(struct view_t *view); + /* Layout related functions. */ /* Enables/disables ls-like style of the view. */ diff --git a/tests/keys/suite.c b/tests/keys/suite.c index 06d35be8a..058546d54 100644 --- a/tests/keys/suite.c +++ b/tests/keys/suite.c @@ -12,10 +12,11 @@ DEFINE_SUITE(); SETUP() { - static int mode_flags[] = { - MF_USES_REGS | MF_USES_COUNT, - MF_USES_INPUT, - MF_USES_COUNT + static int mode_flags[MODES_COUNT] = { + MF_USES_REGS | MF_USES_COUNT, /* NORMAL_MODE */ + MF_USES_INPUT, /* CMDLINE_MODE */ + MF_USES_INPUT, /* NAV_MODE */ + MF_USES_COUNT /* VISUAL_MODE */ }; vle_keys_init(MODES_COUNT, mode_flags, &silence); diff --git a/tests/misc/cmdline.c b/tests/misc/cmdline.c index a509d6544..a568edb75 100644 --- a/tests/misc/cmdline.c +++ b/tests/misc/cmdline.c @@ -3,6 +3,7 @@ #include #include "../../src/cfg/config.h" +#include "../../src/compat/curses.h" #include "../../src/engine/keys.h" #include "../../src/engine/mode.h" #include "../../src/modes/cmdline.h" @@ -10,9 +11,12 @@ #include "../../src/modes/wk.h" #include "../../src/ui/statusbar.h" #include "../../src/ui/ui.h" +#include "../../src/utils/path.h" #include "../../src/utils/str.h" #include "../../src/builtin_functions.h" #include "../../src/event_loop.h" +#include "../../src/filelist.h" +#include "../../src/status.h" static void prompt_callback(const char response[], void *arg); @@ -47,6 +51,7 @@ TEARDOWN() (void)vle_keys_exec_timed_out(WK_C_c); vle_keys_reset(); + cfg.inc_search = 0; } TEST(expr_reg_completion) @@ -159,13 +164,205 @@ TEST(user_prompt_completion) (void)vle_keys_exec_timed_out(L":echo input('p', 'read/dos', 'dir')" WK_CR); assert_string_equal("read/dos", ui_sb_last()); - /* Preparing input beforehand, because input() runs nested event loop. */ feed_keys(WK_C_i WK_CR); (void)vle_keys_exec_timed_out(L":echo input('p', 'read/dos', 'file')" WK_CR); assert_string_equal("read/dos-eof", ui_sb_last()); } +TEST(cmdline_navigation) +{ + /* This doesn't work outside of search and local filter submodes. */ + make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), + TEST_DATA_PATH, "tree", NULL); + (void)vle_keys_exec_timed_out(L":"); + + (void)vle_keys_exec_timed_out(WK_C_y); + assert_false(stats->navigating); + + (void)vle_keys_exec_timed_out(WK_C_o); + assert_string_equal("tree", get_last_path_component(curr_view->curr_dir)); +} + +TEST(navigation_requires_interactivity) +{ + (void)vle_keys_exec_timed_out(L"/"); + + cfg.inc_search = 0; + (void)vle_keys_exec_timed_out(WK_C_y); + assert_false(stats->navigating); + + cfg.inc_search = 1; + (void)vle_keys_exec_timed_out(WK_C_y); + assert_true(stats->navigating); + (void)vle_keys_exec_timed_out(WK_C_y); + assert_false(stats->navigating); +} + +TEST(navigation_movement) +{ + conf_setup(); + cfg.inc_search = 1; + + make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), + TEST_DATA_PATH, "read", NULL); + populate_dir_list(curr_view, /*reload=*/0); + + (void)vle_keys_exec_timed_out(L"/"); + + (void)vle_keys_exec_timed_out(WK_C_y); + assert_string_equal("binary-data", + curr_view->dir_entry[curr_view->list_pos].name); + (void)vle_keys_exec_timed_out(WK_C_n); + assert_string_equal("dos-eof", + curr_view->dir_entry[curr_view->list_pos].name); + (void)vle_keys_exec_timed_out(WK_C_n); + assert_string_equal("dos-line-endings", + curr_view->dir_entry[curr_view->list_pos].name); + (void)vle_keys_exec_timed_out(WK_C_p); + assert_string_equal("dos-eof", + curr_view->dir_entry[curr_view->list_pos].name); + +#ifdef ENABLE_EXTENDED_KEYS + wchar_t keys[2] = { }; + + keys[0] = K(KEY_UP); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("binary-data", + curr_view->dir_entry[curr_view->list_pos].name); + keys[0] = K(KEY_DOWN); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("dos-eof", + curr_view->dir_entry[curr_view->list_pos].name); + keys[0] = K(KEY_HOME); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("binary-data", + curr_view->dir_entry[curr_view->list_pos].name); + keys[0] = K(KEY_END); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("very-long-line", + curr_view->dir_entry[curr_view->list_pos].name); + keys[0] = K(KEY_LEFT); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("test-data", + get_last_path_component(curr_view->curr_dir)); + keys[0] = K(KEY_RIGHT); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("read", get_last_path_component(curr_view->curr_dir)); + + /* Setup for scrolling. */ + curr_view->window_rows = 5; + setup_grid(curr_view, /*column_count=*/1, curr_view->list_rows, /*init=*/0); + curr_view->top_line = 1; + curr_view->list_pos = curr_view->list_rows - 1; + + keys[0] = K(KEY_PPAGE); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("dos-line-endings", + curr_view->dir_entry[curr_view->list_pos].name); + keys[0] = K(KEY_NPAGE); + (void)vle_keys_exec_timed_out(keys); + assert_string_equal("two-lines", + curr_view->dir_entry[curr_view->list_pos].name); +#endif + + conf_teardown(); +} + +TEST(search_navigation) +{ + conf_setup(); + histories_init(5); + cfg.inc_search = 1; + cfg.wrap_scan = 1; + + make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), + TEST_DATA_PATH, "tree", NULL); + populate_dir_list(curr_view, /*reload=*/0); + + (void)vle_keys_exec_timed_out(L"/" WK_C_y); + + /* Can enter and leave directories. */ + (void)vle_keys_exec_timed_out(L"5" WK_C_m); + assert_string_equal("dir5", get_last_path_component(curr_view->curr_dir)); + (void)vle_keys_exec_timed_out(WK_C_o); + assert_string_equal("tree", get_last_path_component(curr_view->curr_dir)); + (void)vle_keys_exec_timed_out(L"1" WK_C_m); + assert_string_equal("dir1", get_last_path_component(curr_view->curr_dir)); + (void)vle_keys_exec_timed_out(L"2" WK_C_m); + assert_string_equal("dir2", get_last_path_component(curr_view->curr_dir)); + + assert_true(hist_is_empty(&curr_stats.search_hist)); + + cfg.wrap_scan = 0; + histories_init(0); + conf_teardown(); +} + +/* Same as search_navigation test above. Duplicating because filtering is more + * complicated and it's a good idea to verify it also works fine. */ +TEST(filter_navigation) +{ + conf_setup(); + histories_init(5); + cfg.inc_search = 1; + cfg.wrap_scan = 1; + + make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), + TEST_DATA_PATH, "tree", NULL); + populate_dir_list(curr_view, /*reload=*/0); + + (void)vle_keys_exec_timed_out(L"=" WK_C_y); + + /* Can enter and leave directories. */ + (void)vle_keys_exec_timed_out(L"5" WK_C_m); + assert_string_equal("dir5", get_last_path_component(curr_view->curr_dir)); + (void)vle_keys_exec_timed_out(WK_C_o); + assert_string_equal("tree", get_last_path_component(curr_view->curr_dir)); + (void)vle_keys_exec_timed_out(L"1" WK_C_m); + assert_string_equal("dir1", get_last_path_component(curr_view->curr_dir)); + (void)vle_keys_exec_timed_out(L"2" WK_C_m); + assert_string_equal("dir2", get_last_path_component(curr_view->curr_dir)); + + assert_true(hist_is_empty(&curr_stats.filter_hist)); + + cfg.wrap_scan = 0; + histories_init(0); + conf_teardown(); +} + +TEST(navigation_opens_files, IF(not_windows)) +{ + conf_setup(); + cfg.inc_search = 1; + stats_init(&cfg); + + create_executable(SANDBOX_PATH "/script"); + make_file(SANDBOX_PATH "/script", + "#!/bin/sh\n" + "touch " SANDBOX_PATH "/out"); + create_file(SANDBOX_PATH "/in"); + + char vi_cmd[PATH_MAX + 1]; + make_abs_path(vi_cmd, sizeof(vi_cmd), SANDBOX_PATH, "script", NULL); + update_string(&cfg.vi_command, vi_cmd); + + make_abs_path(curr_view->curr_dir, sizeof(curr_view->curr_dir), SANDBOX_PATH, + "", NULL); + populate_dir_list(curr_view, /*reload=*/0); + + /* This should create "out" file. */ + (void)vle_keys_exec_timed_out(L"/" WK_C_y WK_C_m); + + update_string(&cfg.vi_command, NULL); + + remove_file(SANDBOX_PATH "/in"); + remove_file(SANDBOX_PATH "/out"); + remove_file(SANDBOX_PATH "/script"); + + conf_teardown(); +} + static void prompt_callback(const char response[], void *arg) { diff --git a/tests/misc/cmdline_editing.c b/tests/misc/cmdline_editing.c index 2baaaa4ab..ae5f6f731 100644 --- a/tests/misc/cmdline_editing.c +++ b/tests/misc/cmdline_editing.c @@ -173,6 +173,94 @@ TEST(left_kill) assert_wstring_equal(L"", stats->line); } +TEST(moving_cursor) +{ + (void)vle_keys_exec_timed_out(L"abc"); + assert_wstring_equal(L"abc", stats->line); + + (void)vle_keys_exec_timed_out(WK_C_b); + assert_int_equal(2, stats->index); + (void)vle_keys_exec_timed_out(WK_C_f); + assert_int_equal(3, stats->index); + (void)vle_keys_exec_timed_out(WK_C_a); + assert_int_equal(0, stats->index); + (void)vle_keys_exec_timed_out(WK_C_e); + assert_int_equal(3, stats->index); + +#ifdef ENABLE_EXTENDED_KEYS + wchar_t keys[2] = { }; + + keys[0] = K(KEY_LEFT); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(2, stats->index); + keys[0] = K(KEY_RIGHT); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(3, stats->index); + keys[0] = K(KEY_HOME); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(0, stats->index); + keys[0] = K(KEY_END); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(3, stats->index); +#endif +} + +TEST(word_operations) +{ + (void)vle_keys_exec_timed_out(L"aa bb cc"); + assert_wstring_equal(L"aa bb cc", stats->line); + + /* Fill cfg.word_chars as if it was initialized from isspace() function. */ + memset(&cfg.word_chars, 1, sizeof(cfg.word_chars)); + cfg.word_chars['\x00'] = 0; cfg.word_chars['\x09'] = 0; + cfg.word_chars['\x0a'] = 0; cfg.word_chars['\x0b'] = 0; + cfg.word_chars['\x0c'] = 0; cfg.word_chars['\x0d'] = 0; + cfg.word_chars['\x20'] = 0; + +#ifndef __PDCURSES__ + (void)vle_keys_exec_timed_out(WK_ESC WK_b); + assert_int_equal(6, stats->index); + (void)vle_keys_exec_timed_out(WK_ESC WK_b); + assert_int_equal(3, stats->index); + (void)vle_keys_exec_timed_out(WK_ESC WK_b); + assert_int_equal(0, stats->index); + (void)vle_keys_exec_timed_out(WK_ESC WK_f); + assert_int_equal(2, stats->index); + (void)vle_keys_exec_timed_out(WK_ESC WK_f); + assert_int_equal(5, stats->index); + (void)vle_keys_exec_timed_out(WK_ESC WK_b); + assert_int_equal(3, stats->index); + (void)vle_keys_exec_timed_out(WK_ESC WK_d); + assert_int_equal(3, stats->index); + assert_wstring_equal(L"aa cc", stats->line); +#else + wchar_t keys[2] = { }; + + keys[0] = K(ALT_B); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(6, stats->index); + keys[0] = K(ALT_B); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(3, stats->index); + keys[0] = K(ALT_B); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(0, stats->index); + keys[0] = K(ALT_F); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(2, stats->index); + keys[0] = K(ALT_F); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(5, stats->index); + keys[0] = K(ALT_B); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(3, stats->index); + keys[0] = K(ALT_D); + (void)vle_keys_exec_timed_out(keys); + assert_int_equal(3, stats->index); + assert_wstring_equal(L"aa cc", stats->line); +#endif +} + TEST(history) { cfg.history_len = 4; diff --git a/tests/misc/filtering.c b/tests/misc/filtering.c index 6a621d4e0..e3b0537a4 100644 --- a/tests/misc/filtering.c +++ b/tests/misc/filtering.c @@ -441,11 +441,11 @@ TEST(custom_tree_can_restore_files_after_local_filter_interactive) assert_int_equal(5, lwin.list_rows); assert_int_equal(0, local_filter_set(&lwin, "t")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(2, lwin.list_rows); assert_int_equal(0, local_filter_set(&lwin, "")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(5, lwin.list_rows); } diff --git a/tests/misc/flist_tree_filtering.c b/tests/misc/flist_tree_filtering.c index 6c0c8c10c..110a68382 100644 --- a/tests/misc/flist_tree_filtering.c +++ b/tests/misc/flist_tree_filtering.c @@ -123,7 +123,7 @@ TEST(leafs_are_not_matched_by_local_filtering) assert_int_equal(4, lwin.list_rows); assert_int_equal(1, local_filter_set(&lwin, "\\.")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(1, lwin.list_rows); validate_tree(&lwin); @@ -145,7 +145,7 @@ TEST(leafs_are_returned_if_local_filter_is_emptied) assert_success(load_tree(&lwin, SANDBOX_PATH, cwd)); assert_int_equal(4, lwin.list_rows); assert_int_equal(0, local_filter_set(&lwin, ".")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(2, lwin.list_rows); validate_tree(&lwin); @@ -156,7 +156,7 @@ TEST(leafs_are_returned_if_local_filter_is_emptied) /* ".." should appear after filter is emptied. */ assert_int_equal(0, local_filter_set(&lwin, "")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(4, lwin.list_rows); validate_tree(&lwin); @@ -218,7 +218,7 @@ TEST(nodes_are_reparented_on_filtering) validate_tree(&lwin); assert_int_equal(0, local_filter_set(&lwin, "2")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(2, lwin.list_rows); validate_tree(&lwin); } @@ -230,7 +230,7 @@ TEST(sorting_of_filtered_list_accounts_for_tree) validate_tree(&lwin); assert_int_equal(0, local_filter_set(&lwin, "file|dir4")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(6, lwin.list_rows); validate_tree(&lwin); diff --git a/tests/misc/flist_tree_folding.c b/tests/misc/flist_tree_folding.c index 127a95adc..95e595acc 100644 --- a/tests/misc/flist_tree_folding.c +++ b/tests/misc/flist_tree_folding.c @@ -210,7 +210,7 @@ TEST(folds_of_custom_tree_are_not_lost_on_filtering) /* filter */ assert_int_equal(0, local_filter_set(&lwin, "[34]")); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_int_equal(2, lwin.list_rows); /* unfold */ diff --git a/tests/misc/navigation.c b/tests/misc/navigation.c index 526fbf6d8..887142b2a 100644 --- a/tests/misc/navigation.c +++ b/tests/misc/navigation.c @@ -67,7 +67,7 @@ TEST(local_filter_is_reset_in_cv_to_follow_mark) assert_true(flist_custom_active(&lwin)); local_filter_set(&lwin, "b"); - local_filter_accept(&lwin); + local_filter_accept(&lwin, /*update_history=*/1); assert_true(flist_custom_active(&lwin)); assert_success(marks_goto(&lwin, 'a')); diff --git a/tests/test-data/syntax-highlight/syntax.vifm b/tests/test-data/syntax-highlight/syntax.vifm index 86ce05e7d..208987964 100644 --- a/tests/test-data/syntax-highlight/syntax.vifm +++ b/tests/test-data/syntax-highlight/syntax.vifm @@ -511,6 +511,9 @@ st sto stop keepsel +amap +anoremap +aunmap " the words following ":" should be highlighted as :commands nnoremap lhs :session