xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 9835a336d259548f05c9f8f46e0d98cf33f96e29

Add -focus parameter to :split and :vsplit
Thanks to agguser.

Closes https://github.com/vifm/vifm/issues/1093
Author: xaizek
Author date (UTC): 2026-09-04 08:08
Committer name: xaizek
Committer date (UTC): 2026-09-04 08:30
Parent(s): d669b35b2b7b8112ecad872bf4f8959d7a549c75
Signing key: 99DC5E4DB05F6BE2
Tree: 20ebec5596ea3c40a30b7bcc6a8a64c8c2625b61
File Lines added Lines deleted
ChangeLog 2 0
data/man/vifm.1 12 1
data/vim/doc/app/vifm-app.txt 9 1
src/cmd_handlers.c 75 4
src/tags.c 1 0
tests/commands/misc.c 27 0
File ChangeLog changed (mode: 100644) (index 4a604497b..c4cfa74f9)
68 68 Clearing doesn't happen if moving panes while Sixel is displayed, but it Clearing doesn't happen if moving panes while Sixel is displayed, but it
69 69 works otherwise. Patch by Maxim4711. works otherwise. Patch by Maxim4711.
70 70
71 Added -focus parameter to :split and :vsplit. Thanks to agguser.
72
71 73 Updated utf8proc to v2.11.3. Updated utf8proc to v2.11.3.
72 74
73 75 Made documentation on which :commands can have comments a bit more Made documentation on which :commands can have comments a bit more
File data/man/vifm.1 changed (mode: 100644) (index 0a9854eca..6cd8c59a9)
1 .TH VIFM 1 "2 September 2026" "vifm 0.15"
1 .TH VIFM 1 "4 September 2026" "vifm 0.15"
2 2 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
3 3 .SH NAME .SH NAME
4 4 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
 
... ... splits the window horizontally to show both file directories. Also changes
3125 3125 other pane to path (absolute or relative to current directory of active pane). other pane to path (absolute or relative to current directory of active pane).
3126 3126 Macros are expanded. Macros are expanded.
3127 3127 .TP .TP
3128 .BI ":sp[lit] -focus ..."
3129 see "\-focus parameter" below.
3130 .TP
3128 3131 .BI " :stop" .BI " :stop"
3129 3132 .TP .TP
3130 3133 .BI :st[op] .BI :st[op]
 
... ... split the window vertically to show both file directories. And changes other
3364 3367 pane to path (absolute or relative to current directory of active pane). pane to path (absolute or relative to current directory of active pane).
3365 3368 Macros are expanded. Macros are expanded.
3366 3369 .TP .TP
3370 .BI ":vs[plit] -focus ..."
3371 see "\-focus parameter" below.
3372 .TP
3367 3373 .BI " :wincmd" .BI " :wincmd"
3368 3374 .TP .TP
3369 3375 .BI ":[count]winc[md] {arg}" .BI ":[count]winc[md] {arg}"
 
... ... In both cases `cp` prints an error and doesn't create anything at the
3676 3682 destination but vifm copies symbolic links as is to preserve file structure. destination but vifm copies symbolic links as is to preserve file structure.
3677 3683 This implementation detail may change in the future or become configurable. This implementation detail may change in the future or become configurable.
3678 3684
3685 .TP
3686 .BI "\-focus parameter"
3687 This parameter makes :split and :vsplit activate the other pane
3688 at the end of their operation.
3689
3679 3690 .TP .TP
3680 3691 .BI "\-skip parameter" .BI "\-skip parameter"
3681 3692 This parameter makes :copy, :move, :alink and :rlink automatically skip source This parameter makes :copy, :move, :alink and :rlink automatically skip source
File data/vim/doc/app/vifm-app.txt changed (mode: 100644) (index 0985d7c98..795c9a42b)
1 *vifm-app.txt* For Vifm version 0.15 Last change: 2026 September 2
1 *vifm-app.txt* For Vifm version 0.15 Last change: 2026 September 4
2 2
3 3 Email for bugs and suggestions: <xaizek@posteo.net> Email for bugs and suggestions: <xaizek@posteo.net>
4 4
 
... ... yet supported) and might be changed in future releases, or get an alias.
2648 2648 split window horizontally to show both file directories. Also changes split window horizontally to show both file directories. Also changes
2649 2649 other pane to the path (absolute or relative to current directory of other pane to the path (absolute or relative to current directory of
2650 2650 active pane). Macros are expanded. active pane). Macros are expanded.
2651 :sp[lit] -focus ...
2652 see |vifm-focus-param|.
2651 2653
2652 2654 *vifm-:stop* *vifm-:st* *vifm-:stop* *vifm-:st*
2653 2655 :st[op] :st[op]
 
... ... yet supported) and might be changed in future releases, or get an alias.
2824 2826 split the window vertically to show both file directories. Also changes split the window vertically to show both file directories. Also changes
2825 2827 other pane to the path (absolute or relative to current directory of active other pane to the path (absolute or relative to current directory of active
2826 2828 pane). Macros are expanded. pane). Macros are expanded.
2829 :vs[plit] -focus ...
2830 see |vifm-focus-param|.
2827 2831
2828 2832 :[count]winc[md] {arg} *vifm-:wincmd* *vifm-:winc* :[count]winc[md] {arg} *vifm-:wincmd* *vifm-:winc*
2829 2833 same as running Ctrl-W [count] {arg}. same as running Ctrl-W [count] {arg}.
 
... ... In both cases `cp` prints an error and doesn't create anything at the
3002 3006 destination but vifm copies symbolic links as is to preserve file structure. destination but vifm copies symbolic links as is to preserve file structure.
3003 3007 This implementation detail may change in the future or become configurable. This implementation detail may change in the future or become configurable.
3004 3008
3009 -focus *vifm-focus-param*
3010 This parameter makes |vifm-:split| and |vifm-:vsplit| activate the other pane
3011 at the end of their operation.
3012
3005 3013 -skip *vifm-skip-param* -skip *vifm-skip-param*
3006 3014 This parameter makes |vifm-:copy|, |vifm-:move|, |vifm-:alink| and This parameter makes |vifm-:copy|, |vifm-:move|, |vifm-:alink| and
3007 3015 |vifm-:rlink| automatically skip source files that already exist at the |vifm-:rlink| automatically skip source files that already exist at the
File src/cmd_handlers.c changed (mode: 100644) (index 2c9dfdc21..18e0392a8)
... ... static int volumes_cmd(const cmd_info_t *cmd_info);
330 330 #endif #endif
331 331 static int vsplit_cmd(const cmd_info_t *cmd_info); static int vsplit_cmd(const cmd_info_t *cmd_info);
332 332 static int do_split(const cmd_info_t *cmd_info, SPLIT orientation); static int do_split(const cmd_info_t *cmd_info, SPLIT orientation);
333 static int parse_a_flag(int *argc, char ***argv, const char flag[]);
333 334 static int do_map(const cmd_info_t *cmd_info, const char map_type[], int mode, static int do_map(const cmd_info_t *cmd_info, const char map_type[], int mode,
334 335 int no_remap); int no_remap);
335 336 static int parse_map_args(const char **args, int *with_help); static int parse_map_args(const char **args, int *with_help);
 
... ... const cmd_add_t cmds_list[] = {
857 858 { .name = "split", .abbr = "sp", .id = COM_SPLIT, { .name = "split", .abbr = "sp", .id = COM_SPLIT,
858 859 .descr = "horizontal split layout", .descr = "horizontal split layout",
859 860 .flags = HAS_EMARK | HAS_COMMENT | HAS_MACROS_FOR_CMD, .flags = HAS_EMARK | HAS_COMMENT | HAS_MACROS_FOR_CMD,
860 .handler = &split_cmd, .min_args = 0, .max_args = 1, },
861 .handler = &split_cmd, .min_args = 0, .max_args = NOT_DEF, },
861 862 { .name = "stop", .abbr = "st", .id = -1, { .name = "stop", .abbr = "st", .id = -1,
862 863 .descr = "suspend the process (same as pressing Ctrl-Z)", .descr = "suspend the process (same as pressing Ctrl-Z)",
863 864 .flags = HAS_COMMENT, .flags = HAS_COMMENT,
 
... ... const cmd_add_t cmds_list[] = {
961 962 { .name = "vsplit", .abbr = "vs", .id = COM_VSPLIT, { .name = "vsplit", .abbr = "vs", .id = COM_VSPLIT,
962 963 .descr = "vertical split layout", .descr = "vertical split layout",
963 964 .flags = HAS_EMARK | HAS_COMMENT | HAS_MACROS_FOR_CMD, .flags = HAS_EMARK | HAS_COMMENT | HAS_MACROS_FOR_CMD,
964 .handler = &vsplit_cmd, .min_args = 0, .max_args = 1, },
965 .handler = &vsplit_cmd, .min_args = 0, .max_args = NOT_DEF, },
965 966 { .name = "vunmap", .abbr = "vu", .id = -1, { .name = "vunmap", .abbr = "vu", .id = -1,
966 967 .descr = "unmap user keys in visual mode", .descr = "unmap user keys in visual mode",
967 968 .flags = HAS_RAW_ARGS, .flags = HAS_RAW_ARGS,
 
... ... do_split(const cmd_info_t *cmd_info, SPLIT orientation)
5560 5561 return CMDS_ERR_CUSTOM; return CMDS_ERR_CUSTOM;
5561 5562 } }
5562 5563
5564 int argc = cmd_info->argc;
5565 char **argv = cmd_info->argv;
5566 const int focus = parse_a_flag(&argc, &argv, "-focus");
5567 if(focus < 0)
5568 {
5569 return CMDS_ERR_CUSTOM;
5570 }
5571
5572 if(argc > 1)
5573 {
5574 ui_sb_errf(":%s expects at most 1 positional argument, got %d",
5575 orientation == HSPLIT ? "split" : "vsplit", argc);
5576 return CMDS_ERR_CUSTOM;
5577 }
5578
5563 5579 if(cmd_info->emark) if(cmd_info->emark)
5564 5580 { {
5565 5581 if(curr_stats.number_of_windows == 1) if(curr_stats.number_of_windows == 1)
 
... ... do_split(const cmd_info_t *cmd_info, SPLIT orientation)
5569 5585 } }
5570 5586 else else
5571 5587 { {
5572 if(cmd_info->argc == 1)
5573 cd(other_view, flist_get_dir(curr_view), cmd_info->argv[0]);
5588 if(argc == 1)
5589 {
5590 cd(other_view, flist_get_dir(curr_view), argv[0]);
5591 }
5574 5592 split_view(orientation); split_view(orientation);
5575 5593 } }
5594
5595 if(focus)
5596 {
5597 swap_view_roles();
5598 }
5599
5576 5600 return 0; return 0;
5577 5601 } }
5578 5602
5603 /* Parses a single leading option and adjusts argc/argv to exclude it. Returns
5604 * a negative number on a parsing error, zero if the flag is not present, and a
5605 * positive number if the flag is present. */
5606 static int
5607 parse_a_flag(int *argc, char ***argv, const char flag[])
5608 {
5609 int present = 0;
5610
5611 if(*argc == 0)
5612 {
5613 /* To avoid incrementing NULL *argv by zero at the bottom. */
5614 return present;
5615 }
5616
5617 int i;
5618 for(i = 0; i < *argc; ++i)
5619 {
5620 if(argv[0][i][0] != '-')
5621 {
5622 /* Implicit end of options. */
5623 break;
5624 }
5625 if(strcmp(argv[0][i], "--") == 0)
5626 {
5627 ++i;
5628 /* Explicit end of options. */
5629 break;
5630 }
5631
5632 if(strcmp(argv[0][i], flag) == 0)
5633 {
5634 present = 1;
5635 }
5636 else
5637 {
5638 ui_sb_errf("Unrecognized :command option: %s", argv[0][i]);
5639 return -1;
5640 }
5641 }
5642
5643 *argc -= i;
5644 *argv += i;
5645
5646 return present;
5647 }
5648
5649
5579 5650 static int static int
5580 5651 vunmap_cmd(const cmd_info_t *cmd_info) vunmap_cmd(const cmd_info_t *cmd_info)
5581 5652 { {
File src/tags.c changed (mode: 100644) (index 76643089c..c03cff231)
... ... const char *tags[] = {
676 676 "vifm-filetype()", "vifm-filetype()",
677 677 "vifm-filters", "vifm-filters",
678 678 "vifm-fnameescape()", "vifm-fnameescape()",
679 "vifm-focus-param",
679 680 "vifm-functions", "vifm-functions",
680 681 "vifm-fuse", "vifm-fuse",
681 682 "vifm-gA", "vifm-gA",
File tests/commands/misc.c changed (mode: 100644) (index c7e9461e8..748b2c7a9)
... ... TEST(split)
785 785 assert_int_equal(VSPLIT, curr_stats.split); assert_int_equal(VSPLIT, curr_stats.split);
786 786 assert_int_equal(2, curr_stats.number_of_windows); assert_int_equal(2, curr_stats.number_of_windows);
787 787 assert_string_ends_with("/read", rwin.curr_dir); assert_string_ends_with("/read", rwin.curr_dir);
788
789 /* -focus */
790
791 ui_sb_msg("");
792 assert_failure(cmds_dispatch1("split -flag", &lwin, CIT_COMMAND));
793 assert_string_equal("Unrecognized :command option: -flag", ui_sb_last());
794
795 ui_sb_msg("");
796 assert_failure(cmds_dispatch1("split ../tree -focus", &lwin, CIT_COMMAND));
797 assert_string_equal(":split expects at most 1 positional argument, got 2",
798 ui_sb_last());
799
800 assert_success(cmds_dispatch1("split -focus --", &lwin, CIT_COMMAND));
801 assert_int_equal(HSPLIT, curr_stats.split);
802 assert_int_equal(2, curr_stats.number_of_windows);
803 assert_true(curr_view == &rwin);
804
805 assert_success(cmds_dispatch1("vsplit -focus ../tree", &lwin, CIT_COMMAND));
806 assert_int_equal(VSPLIT, curr_stats.split);
807 assert_int_equal(2, curr_stats.number_of_windows);
808 assert_true(curr_view == &lwin);
809 assert_string_ends_with("/tree", lwin.curr_dir);
810
811 ui_sb_msg("");
812 assert_failure(cmds_dispatch1("split! -focus", &lwin, CIT_COMMAND));
813 assert_string_equal("No arguments are allowed if you use \"!\"",
814 ui_sb_last());
788 815 } }
789 816
790 817 static void static void
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master