1

I have a callback/generator which produces output, possibly after a delay. I'd like to send these outputs to the terminal buffer as they're produced. Here's a mockup:

local term_buf = vim.api.nvim_create_buf(false, true)
local term_chan = vim.api.nvim_open_term(term_buf, {})
vim.api.nvim_open_win(term_buf, false, { split = "right" })

local outputs = { "First", "Second", "Third", }

local generate_result = function()
    os.execute("sleep 1")
    return table.remove(outputs, 1)
end

while true do
    local result = generate_result()
    if not result then
        break
    end
    vim.api.nvim_chan_send(term_chan, result .. "\n")
end

If you run the above you'll find that, instead of opening the terminal and updating once per second, Neovim becomes blocked for three seconds until the terminal opens and all results appear at once.

The closest I've gotten to having this run in 'real time' is to replace the final while loop with a recursive function that only schedule()s the next send after the previous one has been sent. This only works intermittently though, and still blocks Neovim while generate_result() is running:

-- I've tried this instead of the above `while` loop
local function send_next()
    local result = generate_result()
    if not result then
        return
    end
    vim.api.nvim_chan_send(term_chan, result .. "\n")
    vim.schedule(send_next)
end
vim.schedule(send_next)

1 Answer 1

1

In the end I was able to solve this issue by coming at it from a different angle. Here's what I learnt.

Neovim's Lua runtime is single-threaded, so if you have a Lua function which is blocking there's not loads you can do to get around that.

  • Caveat: actually you can use vim.uv to run blocking Lua stuff in other threads, but not all of the standard library is available so, e.g. most vim.api.nvim_ functions won't work.

  • Caveat: you can also use vim.system() to run a system process with a callback to run when it finishes. In the example I gave with sleep 1 this would have been a good solution, but in my real example I had a blocking process in Lua.

In the end I found the best way to get around this was to rework my Lua function (which actually hooked into Rust via mlua) to be non-blocking. I was then able to create a wrapper which checks at regular intervals whether a result has become available, and yields control back to Neovim if not:

local function check_callback()
    -- Continuously check for results until we fail to receive a result
    while true do
        local result = callback()
        -- If idle then the execution is complete
        if result.status == "idle" then
            return
        end
        -- If no data yet, wait a bit (so we don't block the main thread)
        -- and check again later
        if not result.data then
            return vim.defer_fn(check_callback, 50)
        end
        self:_handle_result(result)
    end
end

check_callback()

^ This is the actual code I'm using right now.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.