I have a vimscript function which uses a combination of vimscript functions and also some calls to :call system(). The vimscript and the system() calls can be 5-10 seconds slow, during which time, Vim is frozen. How do I defer the entire script to another thread? I read online that timer_start() should work, but it doesn't for me.

I tried this:

function! SlowCall()
    let text = system('sleep 3 ; ls')  " An example of a slow system call + some return value
    echo text
    " ... imagine more vimscript functions that use `text`, below ...

call timer_start(0, { -> SlowCall() })

If I source the above script, Vim prevents typing for 3 seconds, instead of running in the background while I continue to work. How do I run SlowCall() in the background without interrupting typing?

  • Thanks for the link. I read through that prior to posting but unfortunately it didn't give me the result I needed. > If you remove that and just have ls does it change anything? It's a bit hard to tell since ls returns results so quickly. If I replace the sleep + ls with find / -name "foo", it still hangs though. So probably it's not an issue specific to sleep Oct 4 '21 at 0:10
  • 1
    Okay. (Kudos for reading that beforehand.) So system() itself? I'll try it when I'm able.
    – B Layer
    Oct 4 '21 at 0:30
  • 1
    Of course, you can't just run system() or any other arbitrary VimScript function and expect it to run asynchronously. There's a dedicated API for running external processes. See :h job. Note that API is incompatible across Vim / Neovim. There are some "compat plugins" but you only need'em if you target both editors and/or find Job API too difficult to use as is.
    – Matt
    Oct 4 '21 at 5:59
  • 1
    I believe Vimscript does not support multiple threads. It can not do async. timer_start is not really async stuff, instead, it allows to schedule execution of some Vimscript to a later time (or to run several times/in loops). But the SlowCall function will occupy the "Vimscript thread". The only solution, as far as I know, is to rely on external threads in other languages (e.g. Python). For this, the job backend is useful, see :help job-start. Oct 4 '21 at 7:03
  • 1
    @KarlYngveLervåg Oh, fiddlesticks. I was under the impression that there was limited multi-threading (for things like timer_start()) but I think you're right. Single-threaded through and through. I've got a couple answers to edit. :P
    – B Layer
    Oct 4 '21 at 9:47

Here's a simple mockup using job_start(); note that in general many things which are straightforward in single-threaded vim (e.g., modify the current buffer) are more complicated when dealing with asynchronous callbacks (e.g., the current buffer may have changed). There are lots of options, so here I'm using :help read-in-close-cb.

function HandleText(channel) abort
  let text = []
  while ch_status(a:channel, {'part': 'out'}) == 'buffered'
    let text += [ch_read(a:channel)]
  let text = join(text, '\n')
  " … imagine more vimscript functions that use `text`, below …

let job = job_start(['sh', '-c', 'sleep 5; exec ls'], #{close_cb: 'HandleText'})

With callback or a combination of out_cb and err_cb, you can handle lines of output as they are returned; this is useful if the job steadily produces information you want to process. The sleep 5; ls is more of a job that "can take some time and [we] don't need intermediate results" (:help read-in-close-cb).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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