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 b6cb254285e5ff6f103d0b8d07f9685a16ba11b6

Add [S/]S shortcuts as wrapping versions of [s/]s
Thanks to aksr.

Closes #1038 on GitHub.
Author: xaizek
Author date (UTC): 2024-12-18 13:02
Committer name: xaizek
Committer date (UTC): 2024-12-18 13:22
Parent(s): a591f55141d3853db2397e250b6be9e9f6327f14
Signing key: 99DC5E4DB05F6BE2
Tree: f30bdcc5588389b9d02d42350075e3ad1d8986a7
File Lines added Lines deleted
ChangeLog 3 0
data/man/vifm.1 6 0
data/vim/doc/app/vifm-app.txt 3 0
src/flist_pos.c 22 0
src/flist_pos.h 6 0
src/modes/normal.c 20 0
src/modes/visual.c 18 0
src/tags.c 2 0
tests/misc/normal.c 64 0
tests/misc/visual.c 35 0
File ChangeLog changed (mode: 100644) (index 95c49ebce..38f8d4228)
95 95 Added selected() builtin function which returns number of currently Added selected() builtin function which returns number of currently
96 96 selected files. selected files.
97 97
98 Added [S and ]S shortcuts as wrapping versions of [s and ]s. Thanks to
99 aksr.
100
98 101 Don't draw right padding on a truncated rightmost column of a transposed Don't draw right padding on a truncated rightmost column of a transposed
99 102 ls-like view. ls-like view.
100 103
File data/man/vifm.1 changed (mode: 100644) (index 560ed6597..e29daa313)
... ... go to the previous selected entry or do nothing.
806 806 .TP .TP
807 807 .BI ]s .BI ]s
808 808 go to the next selected entry or do nothing. go to the next selected entry or do nothing.
809 .TP
810 .BI [S
811 same as [s, but wraps.
812 .TP
813 .BI ]S
814 same as ]s, but wraps.
809 815 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
810 816 .SH Using Count .SH Using Count
811 817 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
File data/vim/doc/app/vifm-app.txt changed (mode: 100644) (index 54509a2b3..eca9437af)
... ... ZZ - same as |vifm-:quit|. *vifm-ZZ*
715 715 ]s *vifm-]s* ]s *vifm-]s*
716 716 go to the next selected entry or do nothing. go to the next selected entry or do nothing.
717 717
718 [S same as |vifm-[s|, but wraps. *vifm-[S*
719 ]S same as |vifm-]s|, but wraps. *vifm-]S*
720
718 721 Using Count~ Using Count~
719 722 *vifm-count* *vifm-[count]* *vifm-count* *vifm-[count]*
720 723 You can use count with commands like yy. You can use count with commands like yy.
File src/flist_pos.c changed (mode: 100644) (index 996be165e..a1d45d494)
... ... fpos_prev_selected(const view_t *view)
761 761 return find_prev(view, view->list_pos, &is_entry_selected); return find_prev(view, view->list_pos, &is_entry_selected);
762 762 } }
763 763
764 int
765 fpos_next_selected_wrap(const view_t *view)
766 {
767 int pos = find_next(view, view->list_pos, &is_entry_selected);
768 if(pos == view->list_pos)
769 {
770 pos = find_next(view, -1, &is_entry_selected);
771 }
772 return pos;
773 }
774
775 int
776 fpos_prev_selected_wrap(const view_t *view)
777 {
778 int pos = find_prev(view, view->list_pos, &is_entry_selected);
779 if(pos == view->list_pos)
780 {
781 pos = find_prev(view, view->list_rows, &is_entry_selected);
782 }
783 return pos;
784 }
785
764 786 int int
765 787 fpos_next_mismatch(const view_t *view) fpos_next_mismatch(const view_t *view)
766 788 { {
File src/flist_pos.h changed (mode: 100644) (index 359c64162..ce59a7f11)
... ... int fpos_next_selected(const struct view_t *view);
190 190 * isn't changed if no previous selected entry is found. */ * isn't changed if no previous selected entry is found. */
191 191 int fpos_prev_selected(const struct view_t *view); int fpos_prev_selected(const struct view_t *view);
192 192
193 /* Version of fpos_next_selected() that wraps. */
194 int fpos_next_selected_wrap(const struct view_t *view);
195
196 /* Version of fpos_prev_selected() that wraps. */
197 int fpos_prev_selected_wrap(const struct view_t *view);
198
193 199 /* Finds position of the next mismatched entry. Returns new position which /* Finds position of the next mismatched entry. Returns new position which
194 200 * isn't changed if no next such entry is found. */ * isn't changed if no next such entry is found. */
195 201 int fpos_next_mismatch(const struct view_t *view); int fpos_next_mismatch(const struct view_t *view);
File src/modes/normal.c changed (mode: 100644) (index afbb98318..72bde22bd)
... ... static void cmd_lb_R(key_info_t key_info, keys_info_t *keys_info);
238 238 static void cmd_rb_R(key_info_t key_info, keys_info_t *keys_info); static void cmd_rb_R(key_info_t key_info, keys_info_t *keys_info);
239 239 static void cmd_lb_s(key_info_t key_info, keys_info_t *keys_info); static void cmd_lb_s(key_info_t key_info, keys_info_t *keys_info);
240 240 static void cmd_rb_s(key_info_t key_info, keys_info_t *keys_info); static void cmd_rb_s(key_info_t key_info, keys_info_t *keys_info);
241 static void cmd_lb_S(key_info_t key_info, keys_info_t *keys_info);
242 static void cmd_rb_S(key_info_t key_info, keys_info_t *keys_info);
241 243 static void cmd_lb_z(key_info_t key_info, keys_info_t *keys_info); static void cmd_lb_z(key_info_t key_info, keys_info_t *keys_info);
242 244 static void cmd_rb_z(key_info_t key_info, keys_info_t *keys_info); static void cmd_rb_z(key_info_t key_info, keys_info_t *keys_info);
243 245 static void cmd_left_curly_bracket(key_info_t key_info, keys_info_t *keys_info); static void cmd_left_curly_bracket(key_info_t key_info, keys_info_t *keys_info);
 
... ... static keys_add_info_t builtin_cmds[] = {
419 421 {WK_RB WK_R, {{&cmd_rb_R}, .descr = "navigate to next sibling dir (wrap)"}}, {WK_RB WK_R, {{&cmd_rb_R}, .descr = "navigate to next sibling dir (wrap)"}},
420 422 {WK_LB WK_s, {{&cmd_lb_s}, .descr = "go to previous selected entry"}}, {WK_LB WK_s, {{&cmd_lb_s}, .descr = "go to previous selected entry"}},
421 423 {WK_RB WK_s, {{&cmd_rb_s}, .descr = "go to next selected entry"}}, {WK_RB WK_s, {{&cmd_rb_s}, .descr = "go to next selected entry"}},
424 {WK_LB WK_S, {{&cmd_lb_S}, .descr = "go to previous selected entry (wrap)"}},
425 {WK_RB WK_S, {{&cmd_rb_S}, .descr = "go to next selected entry (wrap)"}},
422 426 {WK_LB WK_z, {{&cmd_lb_z}, .descr = "go to first sibling"}}, {WK_LB WK_z, {{&cmd_lb_z}, .descr = "go to first sibling"}},
423 427 {WK_RB WK_z, {{&cmd_rb_z}, .descr = "go to last sibling"}}, {WK_RB WK_z, {{&cmd_rb_z}, .descr = "go to last sibling"}},
424 428 {WK_LCB, {{&cmd_left_curly_bracket}, .descr = "go to previous file/dir group"}}, {WK_LCB, {{&cmd_left_curly_bracket}, .descr = "go to previous file/dir group"}},
 
... ... cmd_rb_s(key_info_t key_info, keys_info_t *keys_info)
2117 2121 pick_or_move(keys_info, fpos_next_selected(curr_view)); pick_or_move(keys_info, fpos_next_selected(curr_view));
2118 2122 } }
2119 2123
2124 /* Go to or pick files until and including previous selected entry while
2125 * wrapping or do nothing. */
2126 static void
2127 cmd_lb_S(key_info_t key_info, keys_info_t *keys_info)
2128 {
2129 pick_or_move(keys_info, fpos_prev_selected_wrap(curr_view));
2130 }
2131
2132 /* Go to or pick files until and including next selected entry while wrapping or
2133 * do nothing. */
2134 static void
2135 cmd_rb_S(key_info_t key_info, keys_info_t *keys_info)
2136 {
2137 pick_or_move(keys_info, fpos_next_selected_wrap(curr_view));
2138 }
2139
2120 2140 /* Go to or pick files until and including first sibling of the current /* Go to or pick files until and including first sibling of the current
2121 2141 * entry. */ * entry. */
2122 2142 static void static void
File src/modes/visual.c changed (mode: 100644) (index 020addb06..1937bf7a7)
... ... static void cmd_lb_d(key_info_t key_info, keys_info_t *keys_info);
156 156 static void cmd_rb_d(key_info_t key_info, keys_info_t *keys_info); static void cmd_rb_d(key_info_t key_info, keys_info_t *keys_info);
157 157 static void cmd_lb_s(key_info_t key_info, keys_info_t *keys_info); static void cmd_lb_s(key_info_t key_info, keys_info_t *keys_info);
158 158 static void cmd_rb_s(key_info_t key_info, keys_info_t *keys_info); static void cmd_rb_s(key_info_t key_info, keys_info_t *keys_info);
159 static void cmd_lb_S(key_info_t key_info, keys_info_t *keys_info);
160 static void cmd_rb_S(key_info_t key_info, keys_info_t *keys_info);
159 161 static void cmd_lb_z(key_info_t key_info, keys_info_t *keys_info); static void cmd_lb_z(key_info_t key_info, keys_info_t *keys_info);
160 162 static void cmd_rb_z(key_info_t key_info, keys_info_t *keys_info); static void cmd_rb_z(key_info_t key_info, keys_info_t *keys_info);
161 163 static void cmd_left_curly_bracket(key_info_t key_info, keys_info_t *keys_info); static void cmd_left_curly_bracket(key_info_t key_info, keys_info_t *keys_info);
 
... ... static keys_add_info_t builtin_cmds[] = {
266 268 {WK_RB WK_d, {{&cmd_rb_d}, .descr = "go to next dir"}}, {WK_RB WK_d, {{&cmd_rb_d}, .descr = "go to next dir"}},
267 269 {WK_LB WK_s, {{&cmd_lb_s}, .descr = "go to previous selected entry"}}, {WK_LB WK_s, {{&cmd_lb_s}, .descr = "go to previous selected entry"}},
268 270 {WK_RB WK_s, {{&cmd_rb_s}, .descr = "go to next selected entry"}}, {WK_RB WK_s, {{&cmd_rb_s}, .descr = "go to next selected entry"}},
271 {WK_LB WK_S, {{&cmd_lb_S}, .descr = "go to previous selected entry (wrap)"}},
272 {WK_RB WK_S, {{&cmd_rb_S}, .descr = "go to next selected entry (wrap)"}},
269 273 {WK_LB WK_z, {{&cmd_lb_z}, .descr = "go to first sibling"}}, {WK_LB WK_z, {{&cmd_lb_z}, .descr = "go to first sibling"}},
270 274 {WK_RB WK_z, {{&cmd_rb_z}, .descr = "go to last sibling"}}, {WK_RB WK_z, {{&cmd_rb_z}, .descr = "go to last sibling"}},
271 275 {WK_LCB, {{&cmd_left_curly_bracket}, .descr = "go to previous file/dir group"}}, {WK_LCB, {{&cmd_left_curly_bracket}, .descr = "go to previous file/dir group"}},
 
... ... cmd_rb_s(key_info_t key_info, keys_info_t *keys_info)
1219 1223 goto_pos(fpos_next_selected(view)); goto_pos(fpos_next_selected(view));
1220 1224 } }
1221 1225
1226 /* Go to previous selected entry wrapping if necessary or do nothing. */
1227 static void
1228 cmd_lb_S(key_info_t key_info, keys_info_t *keys_info)
1229 {
1230 goto_pos(fpos_prev_selected_wrap(view));
1231 }
1232
1233 /* Go to next selected entry wrapping if necessary or do nothing. */
1234 static void
1235 cmd_rb_S(key_info_t key_info, keys_info_t *keys_info)
1236 {
1237 goto_pos(fpos_next_selected_wrap(view));
1238 }
1239
1222 1240 /* Go to first sibling of the current entry. */ /* Go to first sibling of the current entry. */
1223 1241 static void static void
1224 1242 cmd_lb_z(key_info_t key_info, keys_info_t *keys_info) cmd_lb_z(key_info_t key_info, keys_info_t *keys_info)
File src/tags.c changed (mode: 100644) (index fd41e245f..0d120cdfe)
... ... const char *tags[] = {
512 512 "vifm-ZQ", "vifm-ZQ",
513 513 "vifm-ZZ", "vifm-ZZ",
514 514 "vifm-[R", "vifm-[R",
515 "vifm-[S",
515 516 "vifm-[c", "vifm-[c",
516 517 "vifm-[count]", "vifm-[count]",
517 518 "vifm-[d", "vifm-[d",
 
... ... const char *tags[] = {
519 520 "vifm-[s", "vifm-[s",
520 521 "vifm-[z", "vifm-[z",
521 522 "vifm-]R", "vifm-]R",
523 "vifm-]S",
522 524 "vifm-]c", "vifm-]c",
523 525 "vifm-]d", "vifm-]d",
524 526 "vifm-]r", "vifm-]r",
File tests/misc/normal.c changed (mode: 100644) (index a88082eb7..7b993f8e6)
... ... TEST(swapping_views_loads_options)
180 180 assert_string_equal("7", vle_opts_get("numberwidth", OPT_LOCAL)); assert_string_equal("7", vle_opts_get("numberwidth", OPT_LOCAL));
181 181 } }
182 182
183 TEST(lb_rb_S)
184 {
185 /* Load file list. */
186 make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH,
187 "existing-files", cwd);
188 load_dir_list(&lwin, /*reload=*/0);
189
190 /* Make a selection. */
191 lwin.dir_entry[1].selected = 1;
192
193 lwin.list_pos = 0;
194 (void)vle_keys_exec_timed_out(WK_LB WK_S);
195 assert_int_equal(1, lwin.list_pos);
196
197 lwin.list_pos = 0;
198 (void)vle_keys_exec_timed_out(WK_RB WK_S);
199 assert_int_equal(1, lwin.list_pos);
200
201 lwin.list_pos = 1;
202 (void)vle_keys_exec_timed_out(WK_LB WK_S);
203 assert_int_equal(1, lwin.list_pos);
204
205 lwin.list_pos = 1;
206 (void)vle_keys_exec_timed_out(WK_RB WK_S);
207 assert_int_equal(1, lwin.list_pos);
208
209 lwin.list_pos = 2;
210 (void)vle_keys_exec_timed_out(WK_LB WK_S);
211 assert_int_equal(1, lwin.list_pos);
212
213 lwin.list_pos = 2;
214 (void)vle_keys_exec_timed_out(WK_RB WK_S);
215 assert_int_equal(1, lwin.list_pos);
216
217 /* Invert the selection to check for corner cases. */
218 lwin.dir_entry[0].selected = 1;
219 lwin.dir_entry[1].selected = 0;
220 lwin.dir_entry[2].selected = 1;
221
222 lwin.list_pos = 0;
223 (void)vle_keys_exec_timed_out(WK_LB WK_S);
224 assert_int_equal(2, lwin.list_pos);
225
226 lwin.list_pos = 0;
227 (void)vle_keys_exec_timed_out(WK_RB WK_S);
228 assert_int_equal(2, lwin.list_pos);
229
230 lwin.list_pos = 1;
231 (void)vle_keys_exec_timed_out(WK_LB WK_S);
232 assert_int_equal(0, lwin.list_pos);
233
234 lwin.list_pos = 1;
235 (void)vle_keys_exec_timed_out(WK_RB WK_S);
236 assert_int_equal(2, lwin.list_pos);
237
238 lwin.list_pos = 2;
239 (void)vle_keys_exec_timed_out(WK_LB WK_S);
240 assert_int_equal(0, lwin.list_pos);
241
242 lwin.list_pos = 2;
243 (void)vle_keys_exec_timed_out(WK_RB WK_S);
244 assert_int_equal(0, lwin.list_pos);
245 }
246
183 247 TEST(gf, IF(not_windows)) TEST(gf, IF(not_windows))
184 248 { {
185 249 char dir_path[PATH_MAX + 1]; char dir_path[PATH_MAX + 1];
File tests/misc/visual.c changed (mode: 100644) (index cf52efd07..6a2a51437)
... ... TEST(modvis_update_after_av_and_cursor_movements)
141 141 assert_false(lwin.dir_entry[2].selected); assert_false(lwin.dir_entry[2].selected);
142 142 } }
143 143
144 TEST(lb_rb_S)
145 {
146 /* Load file list. */
147 make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH,
148 "existing-files", cwd);
149 populate_dir_list(&lwin, /*reload=*/0);
150
151 /* Make a selection. */
152 lwin.dir_entry[0].selected = 1;
153
154 /* More corner cases are tested in the normal mode, both modes use the same
155 * underlying implementation. */
156
157 lwin.list_pos = 0;
158 (void)vle_keys_exec_timed_out(WK_a WK_v WK_LB WK_S);
159 assert_int_equal(0, lwin.list_pos);
160 (void)vle_keys_exec_timed_out(WK_RB WK_S);
161 assert_int_equal(0, lwin.list_pos);
162 (void)vle_keys_exec_timed_out(WK_C_c);
163
164 lwin.list_pos = 1;
165 (void)vle_keys_exec_timed_out(WK_a WK_v WK_LB WK_S);
166 assert_int_equal(0, lwin.list_pos);
167 (void)vle_keys_exec_timed_out(WK_RB WK_S);
168 assert_int_equal(1, lwin.list_pos);
169 (void)vle_keys_exec_timed_out(WK_C_c);
170
171 lwin.list_pos = 2;
172 (void)vle_keys_exec_timed_out(WK_a WK_v WK_LB WK_S);
173 assert_int_equal(0, lwin.list_pos);
174 (void)vle_keys_exec_timed_out(WK_RB WK_S);
175 assert_int_equal(1, lwin.list_pos);
176 (void)vle_keys_exec_timed_out(WK_C_c);
177 }
178
144 179 TEST(cl, IF(not_windows)) TEST(cl, IF(not_windows))
145 180 { {
146 181 conf_setup(); conf_setup();
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