Home

Awesome

🔗 nvim-oxi

Latest version CI Docs

nvim-oxi provides safe and idiomatic Rust bindings to the rich API exposed by the Neovim text editor.

The project is mostly intended for plugin authors, although nothing's stopping end users from writing their Neovim configs in Rust.

How

The traditional way to write Neovim plugins in languages other than the "builtin" ones, i.e. Vimscript or Lua, is via RPC channels. This approach comes with a few limitations mostly due to having to (de)serialize everything to MessagePack-encoded messages, prohibiting things like attaching callbacks to keymaps or scheduling functions.

nvim-oxi takes a different approach. It leverages Rust's foreign function interface (FFI) support to hook straight into the Neovim C code, allowing feature parity with "in process" plugins while also avoiding the need for an extra IO layer.

This thread on the Neovim discourse goes into a bit more detail for anyone who's interested.

Why

Why bother when Neovim already has Lua as a first-class citizen? Mainly two reasons:

Examples

The examples directory contains several examples of how to use nvim-oxi. It also contains instructions on how to setup your Rust crate, where to place the compiled artifacts and how to load the final plugin from Neovim.

If you're still not sure about something feel free to open a new issue and I might add a new example documenting your use case (if it can be done).

Testing

Turning on the test feature enables #[nvim_oxi::test], which replaces the regular #[test] macro and allows you to test a piece of code from within a Neovim instance using Rust's testing framework.

For example:

use nvim_oxi::api;

#[nvim_oxi::test]
fn set_get_del_var() {
    api::set_var("foo", 42).unwrap();
    assert_eq!(Ok(42), api::get_var("foo"));
    assert_eq!(Ok(()), api::del_var("foo"));
}

When cargo test is executed, the generated code will spawn a new Neovim process with the nvim binary in your $PATH, test your code, and exit.

There's a gotcha: you can't have two tests with the same name in the same crate, even if they belong to different modules. For example, this won't work:

mod a {
    #[nvim_oxi::test]
    fn foo() {}
}

mod b {
    #[nvim_oxi::test]
    fn foo() {}
}

Note that all integration tests must live inside a separate cdylib crate with the following build script:

// build.rs
fn main() -> Result<(), nvim_oxi::tests::BuildError> {
    nvim_oxi::tests::build()
}