Nuvim makes Neovim state available as native Nushell data.
Rows and columns are zero-based in every Nuvim command. Columns are UTF-8 byte offsets, which matches the Neovim API.
The Nushell plugin connects to an existing Neovim server through MessagePack-RPC.
It uses a command-specific --server override first, then $NVIM, then Neovim's standard runtime socket directory.
Discovery probes each candidate through bounded Neovim RPC, so stale socket files never enter selection.
One live session is selected automatically. Bare nuvim opens Nushell's native picker when more than one editor is running.
This side cannot use nvim-oxi because that crate needs Neovim symbols inside the editor process.
The shared nuvim-protocol crate owns RPC framing, handle types, server discovery, generated API methods, and quickfix conversion.
nuvim-codegen reads nvim --api-info and generates the Rust client and API metadata used by the Nushell plugin.
See the architecture note for the protocol and future session design.
Install the runtime plugin from GitHub:
nix profile install github:roshbhatia/nu_plugin_nvim#nu-pluginRegister the Nushell plugin:
plugin add (which nu_plugin_nuvim | get path.0)Nushell loads the registered nuvim commands on its next start.
Use plugin use nuvim to reload them in an existing session.
Flake consumers can install the plugin without codegen in their runtime closure:
{
inputs.nuvim.url = "github:roshbhatia/nu_plugin_nvim";
outputs = { nixpkgs, nuvim, ... }: {
# Add this package to Home Manager or the system profile.
packages.aarch64-darwin.default =
nuvim.packages.aarch64-darwin.nu-plugin;
};
}packages.<system>.runtime, nu-plugin, and default contain only nu_plugin_nuvim.
packages.<system>.codegen is the maintainer-only API generator.
Neovim needs no editor-side plugin. Start it with --listen, set $NVIM, or let Nuvim discover its socket.
This flake does not expose a default nix run app because a Nushell plugin binary speaks the plugin protocol rather than a user-facing terminal protocol.
Use the declared development shell for every Rust and Neovim dependency:
nix develop
cargo test --workspace
./hack/screenshots.shThe command surface is:
nuvim
nuvim servers
nuvim context
nuvim cursor
nuvim cursor set <row> <column>
nuvim buffers
nuvim buffer use <id>
nuvim text [--buffer <id>] [--start <row>] [--end <row>]
nuvim selection
nuvim open [paths...]
nuvim edit <row> <column> [--end-row <row>] [--end-column <column>] [--buffer <id>]
nuvim replace [--selection] [--buffer <id>]
nuvim transform <closure> [--buffer <id> | --selection] [--preview]
nuvim diagnostics
nuvim quickfix get [--id <id>] [--details] [--window <id>]
nuvim quickfix set [--title <text>] [--action new|append|replace] [--id <id>] [--context <value>] [--window <id>]
nuvim quickfix history [--window <id>]
nuvim quickfix open [--height <rows>]
nuvim scratch [--name <name>] [--filetype <name>]
nuvim command <ex-command>
nuvim call <method> [arguments...]
nuvim lua <code> [arguments...]
nuvim symbols [--buffer <id>]
nuvim references [--buffer <id>] [--row <row> --column <byte>]
nuvim definition [--buffer <id>] [--row <row> --column <byte>]
nuvim node [--buffer <id>] [--row <row> --column <byte>]
nuvim watch buffer [--buffer <id>] [--initial]
nuvim watch save [--buffer <id>]
nuvim watch diagnostics [--buffer <id>]
Every editor command accepts --server <socket-or-host:port>.
Without that flag, Nuvim uses $NVIM or automatically selects the only discovered session.
Run bare nuvim to choose between multiple live sessions with Nushell's native table picker.
To bind later commands to that choice, save its server address:
$env.NVIM = (nuvim | get server)Use nuvim servers for a non-interactive list.
Neovim has no built-in human session name. Nuvim labels each session with its current buffer, working directory, process ID, mode, and socket address.
Buffer, window, and tab records include IDs and server identity.
Raw handle results use {type: "nvim-handle", kind, id, server} records instead of bare integers.
Nuvim rejects a handle from another server, including handles nested in lists, records, or pipeline arguments.
Unknown MessagePack extensions and maps with non-string keys use tagged records without data loss.
nuvim call validates methods and argument counts against generated API metadata.
The generator reads the same nvim --api-info specification used to produce the typed Rust client.
Inspect the current editor state:
nuvim contextFind modified buffers:
nuvim buffers
| where modified
| select id path filetypeFilter diagnostics:
nuvim diagnostics
| where severity == "ERROR"
| select path row column messageOpen paths produced by another command:
git diff --name-only
| lines
| where { not ($in | is-empty) }
| nuvim openReplace the last visual selection:
--selection cannot be combined with --buffer.
nuvim selection
| get text
| str uppercase
| nuvim replace --selectionMove the cursor and insert text at its position:
nuvim cursor set 12 4
"TODO: " | nuvim edit 12 4
nuvim text --start 12 --end 13Switch to a listed buffer:
nuvim buffers
| where path =~ "README.md$"
| first
| get id
| nuvim buffer useSend ripgrep matches to quickfix:
rg TODO --json
| lines
| each { $in | from json }
| where type == "match"
| each { |event|
{
path: $event.data.path.text
row: ($event.data.line_number - 1)
column: $event.data.submatches.0.start
text: ($event.data.lines.text | str trim --right)
type: "I"
}
}
| nuvim quickfix set --title "TODO"
| nuvim quickfix openRaw escape hatches preserve structured values:
nuvim call nvim_get_current_buf
nuvim lua 'return vim.bo.filetype'Nuvim exposes a small observe, navigate, edit, and operate contract for agents. Agents should select a server explicitly, inspect state before a mutation, use range edits instead of simulated keys, and verify the resulting buffer state.
See the agent control contract and its runnable recipe for the exact workflow.
Quickfix input accepts partial records. Missing positions stay unset.
{
path: "/project/src/main.rs"
row: 41
column: 6
end_row: null
end_column: null
text: "some diagnostic"
type: "E"
}Nuvim adds one only when it sends positions into Neovim quickfix functions.
nuvim quickfix get subtracts one before returning records.
Strings become text, and string lists become lines. Other Nushell values use Nushell's expanded structured representation.
ls | nuvim scratch --name files --filetype nuonThe recipes directory contains one folder per workflow. Each folder has a runnable Nushell script and a focused README.
See automation workflows for guarded transforms, quickfix history, semantic queries, and event streams.
CustomValue handles remain deferred until ordinary records prove the command model.
The protocol already keeps handle type, ID, and server identity separate from Nushell values.
nuvim expose should keep a plugin command alive and call EngineInterface::eval_closure_with_stream() for registered closures.
It needs a second callback channel so Neovim never waits on the command channel that must return its result.
Requests and responses should use versioned IDs, structured results, and explicit errors.

