2

I'm configuring my vim with Vim Script.

I have set my own custom grep function as below:

function! CustomGrepCore(...)
    if a:0 == 0
        " do something
    else if a:0 == 1
        " do something2
    else
        " do something3
endfunction

function! CustomGrep(...)
    let param = a:000
    let F = {p -> call(function('CustomGrepCore'), p)}
    call F(param)
endfunction

command! -nargs=? Grep :call CustomGrep(<f-args>)

The code above worked as expected. I can execute :Grep xxx to do my custom grep in my vim.

Now I want to use the vim 8 feature: timer_start to make my custom grep async, as my understanding, I only need to re-code the function CustomGrep:

function! CustomGrep(...)
    let param = a:000
    let F = {p -> call(function('CustomGrepCore'), p)}
    call timer_start(10, {param -> execute("call F(param)")}, "")   " ERROR: Invalid argument
endfunction

But I always got the error: Invalid argument.

How to fix this?

Besides, as you see I used the timer_start, I knew that it would generate a job ID, which allows us to pause/stop the job. In my case, need I stop the job explicitly?

Add one more thing

There is one more issue: how to pass the varargs from the lambda in the function CustomGrep to the function CustomGrepCore.

I've found this link: https://www.reddit.com/r/vim/comments/3761po/vimscript_question_passing_arguments_from_a/

But we have a different case here. Because we used execute() in the function CustomGrep. So is it possible to pass varargs from execute() to the function CustomGrepCore? The parameter of execute() is a string, but if we convert a:000 to string(string(a:000)), I have to change the function CustomGrepCore, because if I pass string(a:000) to CustomGrepCore, the value of a:0 will always be 1.

So is it possible to pass the varargs from the execute() to another function? If not, well, I have to change the function CustomGrepCore.

6
  • 1
    You need to get the param part out of the string; iirc in BLayer’s answer to your previous question he closed the string, added the argument, then reopened it to finish the parens.
    – D. Ben Knoble
    Aug 28, 2020 at 12:31
  • @D.BenKnoble I may be stupid but I really can't figure out how to do it... Could you please show it to me?
    – Yves
    Aug 28, 2020 at 12:59
  • Not stupid! It’s not easy to wrap your head around all the strings and such. I’ll try to see if I can do it later
    – D. Ben Knoble
    Aug 28, 2020 at 13:04
  • @BLayer Well, no reason. I just want to call CustomGrepCore(param) in the timer_start so that I can make the grep async. But call timer_start(10, {param -> execute("call CustomGrepCore(param)")}, "") will generate an error. It doesn't seem that param can be passed into the lambda correctly.
    – Yves
    Aug 28, 2020 at 18:04
  • My point is that it will be easier to work with timer_start if you convert to a straightforward function call first. Try that and let me know if you are still stuck.
    – B Layer
    Aug 28, 2020 at 18:12

1 Answer 1

2

This question is largely addressing some follow-up questions OP had in response to an answer I gave to another question that they posted. This one also talks about a solution involving lambda functions and closures that is valid but more complicated than needed for most use cases. For both those reasons I recommend you check out that Q&A first: How to start an async function in Vim 8 . If you want a little extra insight about the topic (and some peripheral topics) you can, of course, come back to this one afterwards.

I'm wondering why you are using a FuncRef in the original code to make the call to CustomGrepCore(). There doesn't seem to be an explicit need for it. In the new code it makes things complicated because you have to deal with, in effect, two levels of indirection (FuncRef + lambda) instead of just one for the lambda.

So my first suggestion is to use something like call CustomGrepCore(param) before even touching the timer stuff.

Regardless of the above, though, let's look at the second argument to timer_start. This stuff is tricky, no doubt, but you really strayed from what I wrote. There are three mismatches between what you have

{param -> execute("call F(param)")}

and what I have

{-> execute("call LongRunningFun('" . a:patt . "')", "")}

First, where did the param preceding -> come from? Toss it.

Second, I am passing two args to execute and you are passing one. You may understand what this means and have done so intentionally but just in case that's not the case... Leaving off the second param is equivalent to running the Ex commands in the first argument with :silent. Passing empty string as second param is equivalent to running them without :silent. When first coding this up I personally wouldn't use silent. After things are working I might add it.

Finally, your first param is a single, static string while mine is a concatenation of two static strings and an expression (a:patt). While you intend to pass the value(s) contained in the local variable param to execute() what you're actually doing is passing the literal string "param". This applies to anything contained in quotes.

Anyways, to avoid dragging this out I'll show you what I would do. First, I wouldn't use -nargs=? in your command. I would use + or * in place of ? depending on whether zero args (:Grep) is a valid call. This will then put each argument in a separate slot in the params list. Also, don't use varargs (...) unless you really need them. They add a level of indirection that makes things tricky. Maybe impossible in this particular case. So change CustomGrepCore so it accepts a single argument which will be a list. Here's a demonstration of how it could work once we get the timer_start part right.

function! CustomGrepCore(args) abort
    if len(a:args)
        echom "First item in args list is " . a:args[0]
    endif
endfunction

command! -nargs=* Grep :call CustomGrep(<f-args>)
Grep hello " prints 'First item in args list is hello'

With all that we can get something that works...

" You can't use string(a:000) directly in param expression. Not yet sure why.
let arglist = string(a:000)
call timer_start(50, { -> execute("call CustomGrepCore(" . arglist .  ")", "")})

A non-obvious part of this is the need to pass the varags list, a:000, through string(). execute() takes as its first param a string (which it evaluates as an expression). You can't concatenate a string and a list. You'll get an error if you try. So we need to convert to a string representation of the list and then concatenate.

Additional note: Barring an explicit need to do otherwise, strongly consider adding "abort" at the end of your function signatures so your functions "fail fast" rather continue on even when a command within fails.

Update: OP asked if it's possible to keep varargs in CustomGrepCore(). This poses some challenges but ... But nothing. Read the update to the answer referenced in the first paragraph of this question. The method there is the simplest path to handling the use case of passing varargs from the caller of timer_start to the callback function invoked by the timer when that function also takes varargs.

10
  • I'm so sorry, I misleaded you on a point. I've re-edited my question. You can recheck my function CustomGrepCore. I used a:0 to do some condition-stuff. If I use string(a:000) in the fuction CustomGrep, I think I have to change the function CustomGrepCore. Because no matter what a:000 is in the function CustomGrep, when the function CustomGrepCore gets the parameter string(a:000), a:0 always equals to 1.
    – Yves
    Aug 29, 2020 at 1:30
  • So, this is an issue, which is about how to pass varargs from one function to another function. I have read this link: reddit.com/r/vim/comments/3761po/… , but I think my case is different, because we used execute() here.
    – Yves
    Aug 29, 2020 at 1:32
  • Ofc, if I have no choice, I could change the function CustomGrepCore.
    – Yves
    Aug 29, 2020 at 1:32
  • This may fall in the "no choice" bucket. I was playing around for a while trying to allow you to use varargs straight through and did not have any luck. Some strange things were happening (example: in the last code fragment above...not being able to use a:000 directly in the execute param). But I'll play around with it some more in the next 24 hours and let you know if I find a solution.
    – B Layer
    Aug 29, 2020 at 1:38
  • 1
    Wonderful code !
    – Yves
    Aug 29, 2020 at 3:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.