EDIT2: A user suggested that I may be going about this in a fundamentally wrong way, in particular in that I am not using remote (new-style) plugins and am trying to use async
python code where it might not belong. As such, I would like to briefly reframe the question here and then leave the original text below.
I am a novice attempting to develop a neovim plugin in python3 that runs some long-blocking operations on startup and then reports a result. I would like to do this asynchronously for obvious reasons. Can anyone provide a concrete, minimal example of how to do that that can get me on my feet and coding in the right direction, assuming no particular knowledge of vim plugin development?
ORIGINAL TEXT:
I have a long-running (specifically, long-blocking) plugin written in python that runs once at startup and reports its result when finished. Let's imagine it as
import vim
import time
def plugin_sync():
time.sleep(5)
print("done")
vim.async_call(plugin_sync)
I had hoped this wouldn't impact startup, and indeed it doesn't, if run in isolation.
However, if I add certain other plugins, say using vim-plug and an init vim with lots of work to do like
call plug#begin('~/.local/share/nvim/plugged')
Plug 'me/my-long-running-plugin'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
call plug#end()
let g:coc_global_extensions=["coc-json", "coc-eslint", "coc-tsserver",
\ "coc-snippets", "coc-html", "coc-css", "coc-python", "coc-java",
\ "coc-highlight", "coc-yank", "coc-omnisharp", "coc-emmet",
\ "coc-lists", "coc-marketplace", "coc-neosnippet", "coc-prettier",
\ "coc-clangd", "coc-cmake", "coc-xml" ]
, nvim becomes unresponsive for 5 seconds. Per the docs,
Note that this code will still block the plugin host if it does long-running computations. Intensive computations should be done in a separate thread (or process), and vim.async_call can be used to send results back to Neovim.
, so I'm guessing that the unresponsiveness has to do with another plugin blocking nvim while waiting for the plugin host.
Briefly ignoring the advice in the docs (I'll get back to that), it seemed like maybe the right thing to do was just use proper async python as follows:
import vim
import asyncio
async def plugin_async():
await asyncio.sleep(5)
print("done")
vim.async_call(lambda: asyncio.create_task(plugin_async()))
This doesn't block, but it also doesn't print messages. I know that it's running, because I can insert some side effect like touch
ing a file, but I would also like to be able to print a message after finishing, which is really the whole point of the plugin.
My questions, in order of specificity, are as follows:
- How can I print messages or otherwise deliver feedback to the user from python code running in the
asyncio
event loop? - Is what I've done "valid," in the sense of not having any weird consequences I may not have imagined?
- Circling back to the docs, what is the "standard" way to do this? I've seen suggestions like use neomake or vim-dispatch, or use job control, but I'm very novice with vim script, and I can't say I particularly want to learn a bunch of new concepts to accomplish something that it looks like I can probably just do in pure python. That said, if my way is infeasible or otherwise "wrong," it would be really helpful if responders could give some concrete examples of how to do e.g. what the docs suggest but don't elaborate on.
EDIT: I noticed that vim.eval
doesn't work properly inside of async functions:
import vim
import asyncio
import warnings
async def plugin_async():
try:
vim.eval("g:defined_var")
except Exception as e:
warnings.warn(e)
vim.async_call(lambda: asyncio.create_task(plugin_async()))
Note that I'm using warnings for convenience because they are communicated back to the user, whereas uncaught exceptions are suppressed. This produces
UserWarning: 'NoneType' object has no attribute 'switch'
, which is as yet a mystery to me. The same code works just fine in a sync function, so there's something else going on here that I don't understand. Help following what's going on would be much appreciated.
rplugin/python3/
plug-in? In short, you should callvim.async_call()
only for the parts that don't take a long time... In your example, you should not call it for thetime.sleep(5)
but then call it for theprint("done")
, for example,vim.async_call(print, "done")
. Your use of Pythonasync def
andasyncio
is actually mudding the waters even more... Note thatvim.async_call()
has nothing to do with Async Python. So it would be much easier if you didn't use that in your example... Please edit and add more details aboutrplugin/
and I'll try to write an answer.rplugin/
details to add, but am very open to that approach if you would address long-blocking calls in that stylevim.async_call
and "Intensive computations... separate... process... vim.async_call... send results back to Neovim," is the pattern to e.g. start a process (but not block to join, or maybe register my process handle somehow to be gathered later?), and then callvim.async_call
in the forked (or OS equiv) process to send information back? I tried that just now without success, though it does seem like a plausible interpretation where I might just be missing some details. Apologies if way off base; I'm in a real I don't know what I don't know situation