Is your feature request about something that is currently impossible or hard to do? Please describe the problem.
It is not easy to manually expand the s: function scope or the pseudo-key <SID>.
That's an issue – for example – when we need to assign a script-local function name to an option; this doesn't work:
let &l:includeexpr = 's:my_includeexpr()'
When pressing gf on a path which can't be found, E120 is raised:
E120: Using <SID> not in a script context: s:my_includeexpr
Describe the solution you'd like
A sid() function which expands the s: function scope and the <SID> pseudo-key into the string <SNR>123_, where 123is the id of the current script. With it, one could write:
let &l:includeexpr = sid() .. 'my_includeexpr()'
Describe alternatives you've considered
The current general workaround is to emulate sid() with a custom function:
if !exists('s:SID') fu s:SID() abort return expand('<sfile>')->matchstr('<SNR>\zs\d\+\ze_SID$')->str2nr() endfu const s:SID = s:SID()->printf('<SNR>%d_') delfu s:SID endif let &l:includeexpr = s:SID .. 'my_includeexpr()'
It works, but it makes the code more verbose. It would be easier to read/write if we had a builtin sid() function.
As an example, that's what vim-matchup does:
function! s:snr() return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_snr$')) endfunction let s:sid = printf("\<SNR>%d_", s:snr())
and vim-Verdin:
function! s:SID() abort return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$') endfunction let s:SID = printf("\<SNR>%s_", s:SID()) delfunction s:SID
and vim-sandwich:
function! s:SID() abort return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$') endfunction let s:SID = printf("\<SNR>%s_", s:SID()) delfunction s:SID
Sometimes, we can also use function():
let &l:includeexpr = function('s:my_includeexpr')->string() .. '()'
But it looks awkward, and a builtin sid() would still make the code easier to read/write.
Besides, we have to make sure that s:my_includeexpr() is defined before setting 'includeexpr'. Similarly, with s:SID(), we have to make sure that the function is defined before we invoke it.
With sid(), there would be no such requirement.
Also, note that function() does not always work. For example, it doesn't for 'operatorfunc':
nno <expr> <c-b> <sid>setup() fu s:setup() let &opfunc = function('s:op')->string() return 'g@' endfu fu s:op(type) echom a:type endfu call feedkeys("\<c-b>l")
E117: Unknown function: function('<SNR>1_op')
Nor for 'completefunc':
let s:matches = ['foo', 'bar', 'baz'] fu s:complete_words(findstart, base) abort if a:findstart return searchpos('\<', 'bnW', line('.'))[1] - 1 else return filter(copy(s:matches), {_, v -> stridx(v, a:base) == 0}) endif endfu let &l:cfu = function('s:complete_words')->string() call feedkeys("i\<c-x>\<c-u>")
E117: Unknown function: function('<SNR>1_complete_words')
And more generally, it probably doesn't work for any option which expects a function name, instead of an expression.
Additional context
It's not an issue just when setting an option. It's also an issue when starting a timer:
call timer_start(0, 's:func') fu s:func(_) abort echom 'test' endfu
E120: Using <SID> not in a script context: s:func
And when we need to invoke a script-local function from a command-line populated by a script:
noremap <expr><silent> <c-b> Func() fu Func() let mode = mode(1) if mode is# "\<c-v>" let mode = "\<c-v>\<c-v>" endif return string(mode)->printf(":\<c-u>call s:func(%s)\<cr>") endfu fu s:func(mode) echom 'the mapping was pressed while in ' .. a:mode .. ' mode' endfu call feedkeys("\<c-b>")
E81: Using <SID> not in a script context
And when we pass the name of script-local function as a third optional argument to input():
fu s:CompleteWords(_a, _l, _p) abort return getline(1, '$')->join(' ')->split('\s\+') \ ->filter('v:val =~# "^\\a\\k\\+$"') \ ->sort()->uniq()->join("\n") endfu let word = input('word: ', '', 'custom,s:CompleteWords') " press Tab while on the input line
E120: Using <SID> not in a script context: s:func
If such a function was provided, I think it should accept an optional boolean argument to additionally make Vim expand the pseudo-key <SNR> into a byte sequence (e.g. <80><fd>R). Most of the time, it's either not needed (e.g. in a timer's callback, or in an option setting), or not desirable (e.g. when invoking a script-local function from a command-line populated by a script). But sometimes, it is needed. See here and there for 2 examples.
:echo sid()
<SNR>123_
:echo sid(1)
<80><fd>R123_
In the future, we may not need sid() to set options, because all the options expecting a function name may accept lambda expressions (and maybe funcrefs/partials?):
I recently sent an email to the list about the support for using lambda
functions with
the following options:balloonexpr, charconvert, completefunc, diffexpr, foldexpr, foldtext,
formatexpr, imactivatefunc, imstatusfunc, includeexpr, indentexpr,
omnifunc, operatorfunc, patchexpr, printexpr, quickfixtextfunc, tagfuncBram responded to the email stating that it will be useful to support it
for these options.
Incidentally, this should also allow us to pass arbitrary data to 'opfunc', which is awkward/cumbersome right now (we need to use global or script-local variables).
But it would still not fix the issue in other contexts (timers, input(), command-line, ...).
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.![]()
Whether a builtin sid() or a custom SID() function, the following still looks awkward (in my opinion):
let &l:includeexpr = sid() .. 'my_includeexpr()'
It would make a lot more sense to allow funcrefs for the above mentioned options, including the input() function. It seems sid() is just a dirty workaround since funcrefs are not accepted (yet). In my opinion users shouldn't worry about these internal script IDs.
For timer_start() I have always wrapped the script-local callback inside a lambda, something like:
call timer_start(1, {-> s:foo()})
Funcrefs are indeed a better solution.
The help already explains how is expanded to 123_. In that line of thought, I think it would be OK to make expand('') work. It's a bit more explicit than sid(), which will look strange to someone who has no clue what that means.
Whether a builtin sid() or a custom SID() function, the following still looks awkward (in my opinion):
Oh yes, I definitely agree. It's just that in some cases, I don't see any alternative; mainly when I have to invoke a script-local function from a command-line populated by a script. I guess that's an edge case which doesn't warrant a dedicated sid() function.
It would make a lot more sense to allow funcrefs for the above mentioned options, including the input() function. It seems sid() is just a dirty workaround since funcrefs are not accepted (yet). In my opinion users shouldn't worry about these internal script IDs.
I'll keep an eye on future PRs, regarding the ability of providing a funcref to '*func' options. When one will be submitted, I'll check input() is also supported.
Closed #6602.
In that line of thought, I think it would be OK to make expand('') work.
That would certainly help reduce the amount of code in most cases I mentioned in the OP.
@lacygoill Does this work now? How to expand this? I am trying to set quickfixtextfunc=s:functioname, what's the best technique for now?
yes and that is expected, because :set does not expect expressions.
@chrisbra Thanks!