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 1e1bfd7b492e439f1f4e845940330c17501326c5

Allow selectors and multikeys for Lua keys
Author: xaizek
Author date (UTC): 2022-03-07 16:40
Committer name: xaizek
Committer date (UTC): 2022-03-07 17:10
Parent(s): c020359ae28878b6dec7117ea3695ea314a146f5
Signing key: 99DC5E4DB05F6BE2
Tree: f5d9dbbc2996363b63987f81b2b982598076f7c6
File Lines added Lines deleted
data/vim/doc/app/vifm-lua.txt 6 0
src/lua/vifm_keys.c 51 3
tests/lua/api_keys.c 57 1
File data/vim/doc/app/vifm-lua.txt changed (mode: 100644) (index 95039099b..123626d34)
... ... Possible fields of {key}:
570 570 Unsupported values are ignored. Unsupported values are ignored.
571 571 - "isselector" (boolean) (default: false) - "isselector" (boolean) (default: false)
572 572 Whether this handler defines a selector rather than a regular key. Whether this handler defines a selector rather than a regular key.
573 - "followedby" (string) (default: "none")
574 "none", "selector" (e.g., "j" in "dj") or "keyarg" (e.g., "q" in "'q").
573 575 - "handler" (function) - "handler" (function)
574 576 Handler which accepts {info} and returns a table for selector handlers. Handler which accepts {info} and returns a table for selector handlers.
575 577 See below. See below.
 
... ... Fields of {info} argument for {key}.handler:
582 584 Count preceding the key or nil if none. Count preceding the key or nil if none.
583 585 - "register" (string) - "register" (string)
584 586 Register name or nil if none was specified. Register name or nil if none was specified.
587 - "indexes" (table) (present if key is followed by selector)
588 Table of indexes returned by selector key.
589 - "keyarg" (string) (present if key is followed by keyarg)
590 Key argument (e.g., "x" in "mx" sequence).
585 591
586 592 Fields of table returned by {key}.handler for selectors: Fields of table returned by {key}.handler for selectors:
587 593 - "indexes" (table) - "indexes" (table)
File src/lua/vifm_keys.c changed (mode: 100644) (index 1c59fc4cb..8aacb1f09)
... ... VLUA_DECLARE_SAFE(keys_add);
42 42
43 43 static void parse_modes(vlua_t *vlua, char modes[MODES_COUNT]); static void parse_modes(vlua_t *vlua, char modes[MODES_COUNT]);
44 44 static void lua_key_handler(key_info_t key_info, keys_info_t *keys_info); static void lua_key_handler(key_info_t key_info, keys_info_t *keys_info);
45 static void build_handler_args(lua_State *lua, key_info_t key_info);
45 static void build_handler_args(lua_State *lua, key_info_t key_info,
46 const keys_info_t *keys_info);
46 47 static int extract_indexes(lua_State *lua, keys_info_t *keys_info); static int extract_indexes(lua_State *lua, keys_info_t *keys_info);
47 48 static int deduplicate_ints(int array[], int count); static int deduplicate_ints(int array[], int count);
48 49 static int int_sorter(const void *first, const void *second); static int int_sorter(const void *first, const void *second);
 
... ... VLUA_API(keys_add)(lua_State *lua)
94 95 is_selector = lua_toboolean(lua, -1); is_selector = lua_toboolean(lua, -1);
95 96 } }
96 97
98 FollowedBy followed_by = FOLLOWED_BY_NONE;
99 if(check_opt_field(lua, 1, "followedby", LUA_TSTRING))
100 {
101 const char *value = lua_tostring(lua, -1);
102 if(strcmp(value, "none") == 0)
103 {
104 followed_by = FOLLOWED_BY_NONE;
105 }
106 else if(strcmp(value, "selector") == 0)
107 {
108 followed_by = FOLLOWED_BY_SELECTOR;
109 }
110 else if(strcmp(value, "keyarg") == 0)
111 {
112 followed_by = FOLLOWED_BY_MULTIKEY;
113 }
114 else
115 {
116 return luaL_error(lua, "Unrecognized value for `followedby`: %s", value);
117 }
118 }
119
97 120 char modes[MODES_COUNT] = { }; char modes[MODES_COUNT] = { };
98 121 check_field(lua, 1, "modes", LUA_TTABLE); check_field(lua, 1, "modes", LUA_TTABLE);
99 122 parse_modes(vlua, modes); parse_modes(vlua, modes);
 
... ... VLUA_API(keys_add)(lua_State *lua)
108 131 key_conf_t key = { key_conf_t key = {
109 132 .data.handler = &lua_key_handler, .data.handler = &lua_key_handler,
110 133 .descr = descr, .descr = descr,
134 .followed = followed_by,
111 135 }; };
112 136
113 137 key.user_data = state_store_pointer(vlua, handler); key.user_data = state_store_pointer(vlua, handler);
 
... ... lua_key_handler(key_info_t key_info, keys_info_t *keys_info)
185 209 int is_selector = lua_toboolean(lua, -1); int is_selector = lua_toboolean(lua, -1);
186 210 lua_getfield(lua, -2, "handler"); lua_getfield(lua, -2, "handler");
187 211
188 build_handler_args(lua, key_info);
212 build_handler_args(lua, key_info, keys_info);
213
214 /* After we've built handler arguments, this list isn't needed anymore. */
215 free(keys_info->indexes);
216 keys_info->indexes = NULL;
217 keys_info->count = 0;
189 218
190 219 curr_stats.save_msg = 0; curr_stats.save_msg = 0;
191 220
 
... ... lua_key_handler(key_info_t key_info, keys_info_t *keys_info)
219 248
220 249 /* Builds table passed to key handler, leaves it at the top of the stack. */ /* Builds table passed to key handler, leaves it at the top of the stack. */
221 250 static void static void
222 build_handler_args(lua_State *lua, key_info_t key_info)
251 build_handler_args(lua_State *lua, key_info_t key_info,
252 const keys_info_t *keys_info)
223 253 { {
224 254 lua_newtable(lua); lua_newtable(lua);
225 255
 
... ... build_handler_args(lua_State *lua, key_info_t key_info)
243 273 lua_pushstring(lua, reg_name); lua_pushstring(lua, reg_name);
244 274 } }
245 275 lua_setfield(lua, -2, "register"); lua_setfield(lua, -2, "register");
276
277 if(keys_info->selector)
278 {
279 int i;
280 lua_newtable(lua);
281 for(i = 0; i < keys_info->count; ++i)
282 {
283 lua_pushinteger(lua, keys_info->indexes[i] + 1);
284 lua_seti(lua, -2, i + 1);
285 }
286 lua_setfield(lua, -2, "indexes");
287 }
288
289 if(key_info.multi != L'\0')
290 {
291 lua_pushfstring(lua, "%U", key_info.multi);
292 lua_setfield(lua, -2, "keyarg");
293 }
246 294 } }
247 295
248 296 /* Extracts selected indexes from "indexes" field of the table at the top of /* Extracts selected indexes from "indexes" field of the table at the top of
File tests/lua/api_keys.c changed (mode: 100644) (index f52219d2f..c3380443a)
... ... SETUP()
35 35 view_setup(&lwin); view_setup(&lwin);
36 36 strcpy(lwin.curr_dir, "/lwin"); strcpy(lwin.curr_dir, "/lwin");
37 37 lwin.list_rows = 2; lwin.list_rows = 2;
38 lwin.list_pos = 1;
38 lwin.list_pos = 0;
39 39 lwin.dir_entry = dynarray_cextend(NULL, lwin.dir_entry = dynarray_cextend(NULL,
40 40 lwin.list_rows*sizeof(*lwin.dir_entry)); lwin.list_rows*sizeof(*lwin.dir_entry));
41 41 lwin.dir_entry[0].name = strdup("file0"); lwin.dir_entry[0].name = strdup("file0");
 
... ... TEST(keys_add_errors)
86 86 assert_true(ends_with(ui_sb_last(), assert_true(ends_with(ui_sb_last(),
87 87 ": Shortcut can't be empty or longer than 15")); ": Shortcut can't be empty or longer than 15"));
88 88
89 assert_failure(vlua_run_string(vlua, "vifm.keys.add {"
90 " shortcut = 'X',"
91 " modes = { 'cmdline', 'normal' },"
92 " handler = handler,"
93 " followedby = 'something',"
94 "}"));
95 assert_true(ends_with(ui_sb_last(),
96 ": Unrecognized value for `followedby`: something"));
97
89 98 assert_failure(vlua_run_string(vlua, "vifm.keys.add {" assert_failure(vlua_run_string(vlua, "vifm.keys.add {"
90 99 " shortcut = 'X'," " shortcut = 'X',"
91 100 " isselector = 10," " isselector = 10,"
 
... ... TEST(keys_add)
232 241 " shortcut = 'X'," " shortcut = 'X',"
233 242 " modes = { 'cmdline', 'normal' }," " modes = { 'cmdline', 'normal' },"
234 243 " description = 'print a message'," " description = 'print a message',"
244 " followedby = 'none',"
235 245 " handler = handler," " handler = handler,"
236 246 "})")); "})"));
237 247 assert_string_equal("true", ui_sb_last()); assert_string_equal("true", ui_sb_last());
 
... ... TEST(keys_add_modes)
307 317 assert_false(vle_keys_user_exists(L"X", MORE_MODE)); assert_false(vle_keys_user_exists(L"X", MORE_MODE));
308 318 } }
309 319
320 TEST(keys_followed_by_selector)
321 {
322 ui_sb_msg("");
323
324 assert_success(vlua_run_string(vlua, "function handler(info)"
325 " print(#info.indexes,"
326 " info.indexes[1],"
327 " info.indexes[2])"
328 "end"));
329 assert_string_equal("", ui_sb_last());
330
331 assert_success(vlua_run_string(vlua, "print(vifm.keys.add {"
332 " shortcut = 'X',"
333 " modes = { 'normal' },"
334 " followedby = 'selector',"
335 " handler = handler,"
336 "})"));
337 assert_string_equal("true", ui_sb_last());
338
339 (void)vle_keys_exec_timed_out(L"Xj");
340 assert_int_equal(1, curr_stats.save_msg);
341 assert_string_equal("2\t1\t2", ui_sb_last());
342 }
343
344 TEST(keys_followed_by_multikey)
345 {
346 ui_sb_msg("");
347
348 assert_success(vlua_run_string(vlua, "function handler(info)"
349 " print(info.keyarg)"
350 "end"));
351 assert_string_equal("", ui_sb_last());
352
353 assert_success(vlua_run_string(vlua, "print(vifm.keys.add {"
354 " shortcut = 'X',"
355 " modes = { 'normal' },"
356 " followedby = 'keyarg',"
357 " handler = handler,"
358 "})"));
359 assert_string_equal("true", ui_sb_last());
360
361 (void)vle_keys_exec_timed_out(L"Xj");
362 assert_int_equal(1, curr_stats.save_msg);
363 assert_string_equal("j", ui_sb_last());
364 }
365
310 366 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
311 367 /* vim: set cinoptions+=t0 : */ /* vim: set cinoptions+=t0 : */
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