Home

Awesome

nvim-nio

A library for asynchronous IO in Neovim, inspired by the asyncio library in Python. The library focuses on providing both common asynchronous primitives and asynchronous APIs for Neovim's core.

Motivation

Work has been ongoing around async libraries in Neovim for years, with a lot of discussion around a Neovim core implementation. Much of the motivation behind this library can be seen in that discussion.

nvim-nio aims to provide a simple interface to Lua coroutines that doesn't feel like it gets in the way of your actual logic. You won't even know you're using them. An example of this is error handling. With other libraries, a custom pcall or some other custom handling must be used to catch errors. With nvim-nio, Lua's built-in pcall works exactly as you'd expect.

nvim-nio is focused on providing a great developer experience. The API is well documented with examples and full type annotations, which can all be used by the Lua LSP. It's recommended to use neodev.nvim to get LSP support.

image

Credit to the async library in plenary.nvim and async.nvim for inspiring nvim-nio and its implementation. If Neovim core integrates an async library, nvim-nio will aim to maintain compatibility with it if possible.

Installation

Install with your favourite package manager

lazy.nvim

{ "nvim-neotest/nvim-nio" }

dein:

call dein#add("nvim-neotest/nvim-nio")

vim-plug

Plug 'nvim-neotest/nvim-nio'

packer.nvim

use { "nvim-neotest/nvim-nio" }

Configuration

There are no configuration options currently available.

Usage

nvim-nio is based on the concept of tasks. These tasks represent a series of asynchronous actions that run in a single context. Under the hood, each task is running on a separate lua coroutine.

Tasks are created by providing an async function to nio.run. All async functions must be called from a task.

local nio = require("nio")

local task = nio.run(function()
  nio.sleep(10)
  print("Hello world")
end)

For simple use cases tasks won't be too important but they support features such as cancelling and retrieving stack traces.

nvim-nio comes with built-in modules to help with writing async code. See :help nvim-nio for extensive documentation.

nio.control

Primitives for flow control in async functions

local event = nio.control.event()

local worker = nio.run(function()
  nio.sleep(1000)
  event.set()
end)

local listeners = {
  nio.run(function()
    event.wait()
    print("First listener notified")
  end),
  nio.run(function()
    event.wait()
    print("Second listener notified")
  end),
}

nio.lsp

A fully typed and documented async LSP client library, generated from the LSP specification.

local client = nio.lsp.get_clients({ name = "lua_ls" })[1]

local err, response = client.request.textDocument_semanticTokens_full({
  textDocument = { uri = vim.uri_from_bufnr(0) },
})

assert(not err, err)

for _, token in pairs(response.data) do
  print(token)
end

nio.file

Open and operate on files asynchronously

local file = nio.file.open("test.txt", "w+")

file.write("Hello, World!\n")

local content = file.read(nil, 0)
print(content)

nio.process

Run and control subprocesses asynchronously

local first = nio.process.run({
  cmd = "printf", args = { "hello" }
})

local second = nio.process.run({
  cmd = "cat", stdin = first.stdout
})

local output = second.stdout.read()
print(output)

nio.uv

Async versions of vim.loop functions

local file_path = "README.md"

local open_err, file_fd = nio.uv.fs_open(file_path, "r", 438)
assert(not open_err, open_err)

local stat_err, stat = nio.uv.fs_fstat(file_fd)
assert(not stat_err, stat_err)

local read_err, data = nio.uv.fs_read(file_fd, stat.size, 0)
assert(not read_err, read_err)

local close_err = nio.uv.fs_close(file_fd)
assert(not close_err, close_err)

print(data)

nio.ui

Async versions of vim.ui functions

local value = nio.ui.input({ prompt = "Enter something: " })
print(("You entered: %s"):format(value))

nio.tests

Async versions of plenary.nvim's test functions

nio.tests.it("notifies listeners", function()
  local event = nio.control.event()
  local notified = 0
  for _ = 1, 10 do
    nio.run(function()
      event.wait()
      notified = notified + 1
    end)
  end

  event.set()
  nio.sleep(10)
  assert.equals(10, notified)
end)

Third Party Integration

It is also easy to wrap callback style functions to make them asynchronous using nio.wrap, which allows easily integrating third-party APIs with nvim-nio.

local nio = require("nio")

local sleep = nio.wrap(function(ms, cb)
  vim.defer_fn(cb, ms)
end, 2)

nio.run(function()
  sleep(10)
  print("Slept for 10ms")
end)

Used By

Here are some of the plugins using nvim-nio:

Please open an issue to add any missing entries!