Support a lambda or a partial for the 'operatorfunc' option#8775
Support a lambda or a partial for the 'operatorfunc' option#8775yegappan wants to merge 1 commit intovim:masterfrom
Conversation
Codecov Report
@@ Coverage Diff @@
## master #8775 +/- ##
==========================================
- Coverage 90.15% 90.14% -0.01%
==========================================
Files 151 151
Lines 169356 169369 +13
==========================================
+ Hits 152678 152680 +2
- Misses 16678 16689 +11
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
|
This deserves a good example, or perhaps two, for how the lambda can be useful. |
|
This could be handy for when the opfunc is a script-local function. We can't write this: Because it raises E120: So, we have to write this: Which looks a bit weird. But with a lambda, we could write this instead: One could argue that it still looks weird. I still prefer reading a lambda which can be used in many other contexts, rather than Just 2 remarks. It works with Because it raises It still doesn't work if we manually coerce the lambda into a string: Because this time, it raises It would be nice if could work so that we don't have to escape spaces: Also, it doesn't work with a Vim9 lambda. That is, in a Vim9 script/function, we cannot write this: Because it raises I suspect that's because the option is wrongly evaluated in the legacy context. Possibly relevant todo item:
As a workaround, we have to use the Using the example given at vim9script
nnoremap <expr> <F4> <SID>CountSpaces()
xnoremap <expr> <F4> <SID>CountSpaces()
nnoremap <expr> <F4><F4> <SID>CountSpaces() .. '_'
def CountSpaces(type = ''): string
if type == ''
legacy set operatorfunc={type\ ->\ CountSpaces(type)}
return 'g@'
endif
var sel_save: string = &selection
var reg_save: dict<any> = getreginfo('"')
var cb_save: string = &clipboard
var visual_marks_save: list<list<number>> = [getpos("'<"), getpos("'>")]
try
set clipboard= selection=inclusive
var commands: dict<string> = {line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}
silent execute 'noautocmd keepjumps normal! ' .. get(commands, type, '"')
echom getreg('"')->count(' ')
finally
setreg('"', reg_save)
setpos("'<", visual_marks_save[0])
setpos("'>", visual_marks_save[1])
&clipboard = cb_save
&selection = sel_save
endtry
return ''
enddef
'a b c d e'->setline(1)
feedkeys("\<F4>\<F4>")What's weird is that we don't have to specify the |
|
Hi,
Thanks for trying out the patch and the detailed report. See below for some
comments.
On Sat, Aug 21, 2021 at 9:48 AM lacygoill ***@***.***> wrote:
This could be handy for when the opfunc is a script-local function. We
can't write this:
set operatorfunc=s:MyOpfunc
Because it raises E120:
E120: Using <SID> not in a script context: s:MyOpfunc
So, we have to write this:
let &operatorfunc = expand('<SID>') .. 'MyOpfunc'
Which looks a bit weird.
But with a lambda, we could write this instead:
set operatorfunc={type\ ->\ s:MyOpfunc(type)}
One could argue that it still looks weird. I still prefer reading a lambda
which can be used in many other contexts, rather than expand('<SID>'),
which is less often used.
------------------------------
Just 2 remarks. It works with :set but not with :let. That is, we cannot
write this:
let &operatorfunc = {type -> s:MyOpfunc(type)}
Because it raises E729:
E729: Using a Funcref as a String
It still doesn't work if we manually coerce the lambda into a string:
Yes. Currently this option only supports a string value. So to set the
option using 'let' you need to quote the lambda:
let &operatorfunc = '{type -> s:MyOpfunc(type)}'
let &operatorfunc = {type -> s:MyOpfunc(type)}->string()
^--------^
Because this time, it raises E129:
E129: Function name required
It would be nice if could work so that we don't have to escape spaces:
# ugly
set operatorfunc={type\ ->\ s:MyOpfunc(type)}
^ ^
# nicer
let &operatorfunc = {type -> s:MyOpfunc(type)}
^ ^
Agreed. But supporting this syntax is a lot more work. We need
to add a new option type that can accept function names (string)
or lambdas or funcrefs.
Also, it doesn't work with a Vim9 lambda. That is, in a Vim9
script/function, we cannot write this:
set operatorfunc=(type)\ =>\ MyOpfunc(type)
Yes. I will take a look at this.
Because it raises E117:
E117: Unknown function: (type) => MyOpfunc(type)
I suspect that's because the option is wrongly evaluated in the legacy
context. Possibly relevant todo item:
Use the location where the option was set for deciding whether it's to be
evaluated in Vim9 script context.
As a workaround, we have to use the :legacy modifier:
legacy set operatorfunc={type\ ->\ MyOpfunc(type)}
^----^
Using the example given at :help g@:
Thanks for enhancing the example with the new syntax.
Regards,
Yegappan
… vim9scriptnnoremap <expr> <F4> <SID>CountSpaces()xnoremap <expr> <F4> <SID>CountSpaces()nnoremap <expr> <F4><F4> <SID>CountSpaces() .. '_'
def CountSpaces(type = ''): string
if type == ''
legacy set operatorfunc={type\ ->\ CountSpaces(type)}
return 'g@'
endif
var sel_save: string = &selection
var reg_save: dict<any> = getreginfo('"')
var cb_save: string = &clipboard
var visual_marks_save: list<list<number>> = [getpos("'<"), getpos("'>")]
try
set clipboard= selection=inclusive
var commands: dict<string> = {line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}
silent execute 'noautocmd keepjumps normal! ' .. get(commands, type, '"')
echom getreg('"')->count(' ')
finally
setreg('"', reg_save)
setpos("'<", visual_marks_save[0])
setpos("'>", visual_marks_save[1])
&clipboard = cb_save
&selection = sel_save
endtry
return ''enddef
'a b c d e'->setline(1)feedkeys("\<F4>\<F4>")
What's weird is that we don't have to specify the s: prefix in the legacy
lambda in front of the script-local function name. Usually, in the legacy
context, s: is mandatory; it can only be dropped in the Vim9 context.
|
|
There are several options that use a string value which is the name of a function. And also: Keeping the option type a string avoids having to deal with a different option value in very many places. |
|
Finally had time to look into the patch. It does make sense, I think we can use his for all options that take the name of a function as an argument. |
|
It seems that we can use a partial in a legacy function: vim9script
nnoremap <expr> zd <SID>ZdSetup('zd')
function ZdSetup(cmd)
set operatorfunc=function('s:Zd',\ [a:cmd])
return 'g@l'
endfunction
def Zd(cmd: string, type: string)
execute 'normal! ' .. cmd
enddef
var lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
END
lines->setline(1)
&foldmethod = 'marker'
normal zdBut not in a Vim9 function: vim9script
nnoremap <expr> zd <SID>ZdSetup('zd')
def ZdSetup(cmd: string): string
set operatorfunc=function(Zd,\ [cmd])
return 'g@l'
enddef
def Zd(cmd: string, type: string)
execute 'normal! ' .. cmd
enddef
var lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
END
lines->setline(1)
&foldmethod = 'marker'
normal zdAm I missing something? Shouldn't Vim be able to find the just like it is able to in a legacy function: Note that the issue is not caused by the omission of the |
|
Sorry, I think it's the same issue as the one reported earlier about the lambdas:
|
|
Hi,
On Mon, Nov 15, 2021 at 7:21 PM lacygoill ***@***.***> wrote:
It seems that we can use a partial in a legacy function:
vim9script
nnoremap <expr> zd <SID>ZdSetup('zd')
function ZdSetup(cmd)
set operatorfunc=function('s:Zd',\ [a:cmd])
return ***@***.***'
endfunction
def Zd(cmd: string, type: string)
execute 'normal! ' .. cmd
enddef
var lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
END
lines->setline(1)
&foldmethod = 'marker'
normal zd
the fold is correctly deleted
But not in a Vim9 function:
vim9script
nnoremap <expr> zd <SID>ZdSetup('zd')
def ZdSetup(cmd: string): string
set operatorfunc=function(Zd,\ [cmd])
return ***@***.***'
enddef
def Zd(cmd: string, type: string)
execute 'normal! ' .. cmd
enddef
var lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
END
lines->setline(1)
&foldmethod = 'marker'
normal zd
E121: Undefined variable: cmd
Am I missing something? Shouldn't Vim be able to find the cmd argument in a :def function:
set operatorfunc=function(Zd,\ [cmd])
^^^
just like it is able to in a legacy function:
set operatorfunc=function('s:Zd',\ [a:cmd])
^---^
Note that the issue is not caused by the omission of the s: scope in front of Zd, nor by the omission of the quotes around. None of them are required in the Vim9 context. And it doesn't suppress the error here anyway.
The current implementation only supports legacy functions and
funcrefs. It doesn't support
Vim9 functions.
Regards,
Yegappan
|
|
Hi Bram,
On Mon, Nov 15, 2021 at 11:40 AM Bram Moolenaar ***@***.***> wrote:
Finally had time to look into the patch. It does make sense, I think we
can use his for all options that take the name of a function as an argument.
The use of function() and funcref() is not documented. We should make a
separate section in the help, with some examples, and link to it from the
options that support this format.
We have to make sure that the callback is cleared when exiting. The asan
build doesn't report a leak, but that's probably because the tests end with
making the option empty.
The current implementation only supports legacy functions and doesn't
support compiled
Vim9 functions and lambdas.
Regards,
Yegappan
|
9499dab to
f5308aa
Compare
|
Hi Bram,
On Mon, Nov 15, 2021 at 11:40 AM Bram Moolenaar ***@***.***> wrote:
Finally had time to look into the patch. It does make sense, I think we
can use his for all options that take the name of a function as an argument.
The use of function() and funcref() is not documented. We should make a
separate section in the help, with some examples, and link to it from the
options that support this format.
We have to make sure that the callback is cleared when exiting. The asan
build doesn't report a leak, but that's probably because the tests end with
making the option empty.
I have updated the PR to incorporate your comments.
Regards,
Yegappan
|
|
Hi,
See below for some comments (after patch 8.2.3751)
On Sat, Aug 21, 2021 at 9:48 AM lacygoill ***@***.***> wrote:
This could be handy for when the opfunc is a script-local function. We
can't write this:
set operatorfunc=s:MyOpfunc
This should work now.
Because it raises E120:
E120: Using <SID> not in a script context: s:MyOpfunc
So, we have to write this:
let &operatorfunc = expand('<SID>') .. 'MyOpfunc'
Which looks a bit weird.
But with a lambda, we could write this instead:
set operatorfunc={type\ ->\ s:MyOpfunc(type)}
One could argue that it still looks weird. I still prefer reading a lambda
which can be used in many other contexts, rather than expand('<SID>'),
which is less often used.
------------------------------
Just 2 remarks. It works with :set but not with :let. That is, we cannot
write this:
let &operatorfunc = {type -> s:MyOpfunc(type)}
This should work now.
Because it raises E729:
E729: Using a Funcref as a String
It still doesn't work if we manually coerce the lambda into a string:
let &operatorfunc = {type -> s:MyOpfunc(type)}->string()
^--------^
This also should work now.
Because this time, it raises E129:
E129: Function name required
It would be nice if could work so that we don't have to escape spaces:
# ugly
set operatorfunc={type\ ->\ s:MyOpfunc(type)}
^ ^
# nicer
let &operatorfunc = {type -> s:MyOpfunc(type)}
^ ^
Also, it doesn't work with a Vim9 lambda. That is, in a Vim9
script/function, we cannot write this:
set operatorfunc=(type)\ =>\ MyOpfunc(type)
This should work now.
Because it raises E117:
E117: Unknown function: (type) => MyOpfunc(type)
I suspect that's because the option is wrongly evaluated in the legacy
context. Possibly relevant todo item:
Use the location where the option was set for deciding whether it's to be
evaluated in Vim9 script context.
As a workaround, we have to use the :legacy modifier:
legacy set operatorfunc={type\ ->\ MyOpfunc(type)}
^----^
Can you try the latest version and let us know if you see any problems?
Thanks,
Yegappan
|
|
Thank you very much for all your work on this. I don't know whether it's planned at some point, but we can still not set an opfunc with a simple assignment in Vim9: vim9script
nnoremap <expr> <F4><F4> <SID>CountSpaces() .. '_'
def CountSpaces(type = ''): string
if type == ''
&operatorfunc = (t) => CountSpaces(t)
return 'g@'
endif
normal! '[V']y
echomsg getreg('"')->count(' ')
return ''
enddef
'a b c d e'->setline(1)
feedkeys("\<F4>\<F4>")The issue comes from this line: Which works in legacy: Test: vim9script
nnoremap <expr> <F4><F4> <SID>CountSpaces() .. '_'
def CountSpaces(type = ''): string
if type == ''
legacy let &operatorfunc = {t -> CountSpaces(t)}
return 'g@'
endif
normal! '[V']y
echomsg getreg('"')->count(' ')
return ''
enddef
'a b c d e'->setline(1)
feedkeys("\<F4>\<F4>")Also, still in Vim9, we can not use a partial; neither with Test: vim9script
nnoremap <expr> zd <SID>ZdSetup('zd')
def ZdSetup(cmd: string): string
set operatorfunc=function(Zd,\ [cmd])
return 'g@l'
enddef
def Zd(cmd: string, type: string)
execute 'normal! ' .. cmd
enddef
var lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
END
lines->setline(1)
&foldmethod = 'marker'
normal zdnor with an assignment: Test: vim9script
nnoremap <expr> zd <SID>ZdSetup('zd')
def ZdSetup(cmd: string): string
&operatorfunc = function(Zd, [cmd])
return 'g@l'
enddef
def Zd(cmd: string, type: string)
execute 'normal! ' .. cmd
enddef
var lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
END
lines->setline(1)
&foldmethod = 'marker'
normal zd |
|
Hi,
On Mon, Dec 6, 2021 at 7:36 PM lacygoill ***@***.***> wrote:
Thank you very much for all your work on this. I don't know whether it's
planned at some point, but we can still not set an opfunc with a simple
assignment in Vim9:
Thanks for testing this. Setting the 'opfunc', 'completefunc', etc. options
to a funcref
variable in a compiled Vim9 function is currently not supported.
Regards,
Yegappan
… vim9scriptnnoremap <expr> <F4><F4> <SID>CountSpaces() .. '_'def CountSpaces(type = ''): string
if type == ''
&operatorfunc = (t) => CountSpaces(t)
return 'g@'
endif
normal! '[V']y
echomsg getreg('"')->count(' ')
return ''enddef'a b c d e'->setline(1)feedkeys("\<F4>\<F4>")
E1012: Type mismatch; expected string but got func(any): string
The issue comes from this line:
&operatorfunc = (t) => CountSpaces(t)
Which works in legacy:
legacy let &operatorfunc = {t -> CountSpaces(t)}
Test:
vim9scriptnnoremap <expr> <F4><F4> <SID>CountSpaces() .. '_'def CountSpaces(type = ''): string
if type == ''
legacy let &operatorfunc = {t -> CountSpaces(t)}
return 'g@'
endif
normal! '[V']y
echomsg getreg('"')->count(' ')
return ''enddef'a b c d e'->setline(1)feedkeys("\<F4>\<F4>")
4
------------------------------
Also, still in Vim9, we can not use a partial; neither with :set:
set operatorfunc=function(Zd,\ [cmd])
Test:
vim9scriptnnoremap <expr> zd <SID>ZdSetup('zd')def ZdSetup(cmd: string): string
set operatorfunc=function(Zd,\ [cmd])
return ***@***.***'enddefdef Zd(cmd: string, type: string)
execute 'normal! ' .. cmdenddefvar lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
ENDlines->setline(1)
&foldmethod = 'marker'normal zd
E121: Undefined variable: cmd
nor with an assignment:
&operatorfunc = function(Zd, [cmd])
Test:
vim9scriptnnoremap <expr> zd <SID>ZdSetup('zd')def ZdSetup(cmd: string): string
&operatorfunc = function(Zd, [cmd])
return ***@***.***'enddefdef Zd(cmd: string, type: string)
execute 'normal! ' .. cmdenddefvar lines =<< trim END
some folded lines {{{
some folded lines
some folded lines }}}
ENDlines->setline(1)
&foldmethod = 'marker'normal zd
E1012: Type mismatch; expected string but got func(...): unknown
|
|
The option assignment to a lambda in a :def function was implemented with 8.2.3765. I hope it works for you. The "set" command is not compiled, thus it cannot use variables from its context. You can see this with ":disas": The last example fails because the 'operatorfunc' option is set to the lambda, not the partial. |
|
Actually, using a lambda doesn't work, because the 'operatorfunc' calls the function without the context of where it was defined. |
Problem: Cannot use a lambda for 'operatorfunc'.
Solution: Support using a lambda or partial. (Yegappan Lakshmanan,
closes vim/vim#8775)
vim/vim@777175b
Omit duplicate docs. It's removed in patch 8.2.3623.
Problem: Cannot use a lambda for 'operatorfunc'.
Solution: Support using a lambda or partial. (Yegappan Lakshmanan,
closes vim/vim#8775)
vim/vim@777175b
Omit duplicate docs. It's removed in patch 8.2.3623.
Nvim doesn't seem to need callback_set() as it was omitted when patch 8.1.1437
was first ported.
) Problem: Cannot use a lambda for 'operatorfunc'. Solution: Support using a lambda or partial. (Yegappan Lakshmanan, closes vim/vim#8775) vim/vim@777175b Omit duplicate docs. It's removed in patch 8.2.3623. Nvim doesn't seem to need callback_set() as it was omitted when patch 8.1.1437 was first ported.
The 'operatorfunc' option currently accepts a function name (as a string).
Add support for using a lambda or a partial as a value for this option.