I'm trying to make a small extension/bug fix to a plugin I rely on (referenced as Plugin
in the code snips), therefore I'm constrained by its existing API.
I've got the functionality I want somewhat working, however due to inconsistent typing to what the Plugin
's API is expecting, it is not quite working fully.
Code
So here's my somewhat functional code addition to Plugin
; with the context explained beneath:
function! s:conditional_func(mode, ActionFnRef, key)
if a:mode.Enabled
call a:ActionFnRef(a:key)
else
call Plugin#Pass#Action(a:key)
endif
endfunction
function! s:nop_exception_register(mode, key)
let existing_keymap = Plugin#Get#Keymap()
let ActionFnRef = existing_keymap[a:mode][a:key]
call Plugin#Keymap#Register(function('s:conditional_func',
\ [a:mode, ActionFnRef]),
\ "Nop", a:key)
endfunction
" s:nop_exception_register is later called via another function
In s:conditional_func
:
a:mode.Enabled
is a boolean for the status ofa:mode
a:ActionFnRef
will be some existing Plugin function, which I only want called (with its own "key-pressed" argumenta:key
) whena:mode.Enabled == 1
Plugin#Pass#Action
is a Plugin function that effectively performs no operation when passeda:key
(in practice, it provides the user with a feedback message, but that's unimportant here)
In s:nop_exception_register
:
the
Plugin#Get#Keymap()
returns adict
of all existing keymaps, grouped by their modes, then by the actual keys within each modethereby the actual function defining the action to be performed when a key is pressed in its mode is captured by the assignment to
ActionFnRef
Plugin#Keymap#Register
is the built-in API call for registering actions and their keys in the correct mode
Problem Description
The function signature for Plugin#Keymap#Register
is actually (v:t_string, v:t_string, v:t_string), and NOT (v:t_func, v:t_string, v:t_string).
Normally, the name of the action function is passed as a string to Plugin#Keymap#Register
, which generates the Funcref itself by calling function("actionFuncName")
.
I'm able to have the action working as desired (i.e. conditionally depending on the value of s:mode.Enabled
) because I guess calling function
with a Funcref like I did just returns the same Funcref.
However Plugin#Keymap#Register
also generates a dict
map of help descriptions, which uses the passed string names of the action functions as its keys. Therefore passing it a Funcref like I did eventually causes Plugin
code executed downstream of that to error. The help map can still be successfully initialized, which I've confirmed by inspecting it. It will contain items like:
'A': 'Plugin#DoActionW'
'B': 'Plugin#DoActionX'
'C': function('Plugin#DoActionY')
'D': function('Plugin#DoActionZ')
Where items keyed by 'A'
& 'B'
will work correctly, but 'C'
& 'D'
(added by my hack) not quite.
Attempted Workarounds
- Create a named Partial inside
s:nop_exception_register
, and trying to pass its name as a string toPlugin#Keymap#Register
function! s:nop_exception_register(mode, key)
let existing_keymap = Plugin#Get#Keymap()
let ActionFnRef = existing_keymap[a:mode][a:key]
let CondPartial = function('s:conditional_func', [a:mode, ActionFnRef])
call Plugin#Keymap#Register("CondPartial", "Nop", a:key)
endfunction
This causes a Vim error before it's even fully loaded, with E700: Unknown function: CondPartial
displayed in the terminal.
- Place the logic of 's:conditional_func' inside of
s:nop_exception_register
as a closure and pass that toPlugin#Keymap#Register
function! s:nop_exception_register(mode, key)
let existing_keymap = Plugin#Get#Keymap()
let ActionFnRef = existing_keymap[a:mode][a:key]
func! s:_cond_call_action(key) closure
if a:mode.Enabled
call ActionFnRef(a:key)
else
call Plugin#Pass#Action(a:key)
endif
endfunc
call Plugin#Keymap#Register("s:_cond_call_action", "Nop", a:key)
endfunction
Same error: E700: Unknown function: s:_cond_call_action
Also tried to use
eval()
, something likestring(eval("function('s:cond_return_func', [a:mode, ActionFnRef])"))
, wheres:cond_return_func
basically returns a Partial likeCondPartial
from workaround 1 above. This also errors, not on Vim startup, but at runtime when the relevant part of thePlugin
code is called:E117: Unknown function: <evaluated representation of s:cond_return_func>
I've also considered "dereferencing" a Funcref to its string name if somehow possible, like how in Python one can do
<funcname>.__name__
to get the string identifier of the function. Though intuitively this seems unlikely to be possible in Vimscript, as I don't believe functions/funcrefs in Vimscript are like objects in Python. But ofc, please correct me if I'm simply being ignorant here.
So basically, to me it looks like my problem is not fully understanding how to resolve variable/function scopes in Vimscript.
Also I know the two functions from the first code snippet I showed can almost certainly be refactored into one, which I plan on doing, once this more glaring issue is resolved.
I apologize in advance for this long and verbose post, but I just wanted to make sure the problem is fully described. I'm pretty much at my wit's end, please help.
Plugin
is a certain state (whena:mode.Enabled == 1
to be precise), which this is not always the case, therefore I needed the function to be modified to take into account of this state information. Which is why I ended up using a Partial, and considered using a closure as well.