Neovim companion (nvim/) #2

Open
opened 2026-11-12 01:50:51 +01:00 by thatrichan · 6 comments
Owner

Moved from thatrichan/revue#70.

A Neovim plugin doing what the JetBrains plugin (jetbrains/) does, in Neovim's way: commands, a pull request list in a buffer or picker, signs in the gutter, and Revue opening a pull request in the running Neovim that has its branch.

Neovim 0.10 or later, pure Lua, no other dependency (Telescope, fzf-lua and snacks pickers used when present).

Features

  • :Revue / :Revue open: the current branch's pull request in Revue, or its New pull request page (revue --pull-request <dir>).
  • :Revue list: the repository's open pull requests (revue --pulls, with --show, drafts, search, pages) in Revue's sections (This branch, To review, Mine, Others), in a picker when one is installed, else a scratch buffer. <CR> opens the details buffer (revue --pull): review, checks, conflicts, files, conversation, stack; mappings to open in Revue or the browser, yank the link, switch to the branch, create the worktree (--worktree), fetch the branch (--fetch-branch).
  • In buffers: the branch's pull request's changes and pending comments as signs and virtual text (--branch-pull, --pending); :Revue file opens the file in the pull request at the cursor's line; :Revue comment on a visual selection adds a pending comment (--comment, the body typed in a small floating window); :Revue files lists the pull request's files to jump to.
  • A statusline component: the branch's pull request number and state, for lualine and others.
  • require("revue_companion").setup({ path = …, signs = …, keys = … }): the revue executable (empty: PATH, then the install places the JetBrains plugin looks in), no default mappings beyond the buffer-local ones in its own windows.
  • :checkhealth revue_companion: finds revue, reads revue --version's editor <N> line, and says whether Revue is running.

It talks to Revue only through the revue command (Revue's docs/command-line.md, "What editors can ask"), run with vim.system so nothing blocks the editor. No tokens enter Neovim.

The way back: Revue reaching a running Neovim

Every Neovim already listens on an RPC socket (v:servername), so it needs no server of its own:

  • The plugin registers itself in the same folder as the VS Code companion (#1): <Revue's data folder>/companions/nvim-<pid>.json with { "kind": "nvim", "pid", "socket": v:servername, "version" }, written at startup and removed on VimLeavePre. Revue ignores a stale file (pid gone, socket refusing).
  • Revue asks over msgpack-RPC, calling the plugin's two functions, matching the JetBrains plugin's requests: require("revue_companion").projects() (cwd and tabs' working directories, each repository's root, branch and remotes, whether the UI is focused) and require("revue_companion").open({ path, repo, branch, file, line }) (:tcd to the folder in a new tab unless one has the branch, then :edit file at the line).
  • Neovim has no window to bring forward itself: when it runs in a terminal, Revue also focuses that terminal (by the pid's parent terminal app on macOS, the window under $WINDOWID or the terminal's process on Linux), and says so when it can't.
  • In Revue (thatrichan/revue): the companions module reads these files too, and an nvim client (a small msgpack-RPC call over the socket: Unix socket, or named pipe on Windows) does the two requests. The editor chooser (thatrichan/revue#68) then treats Neovim like any other companion.

Where it lives

nvim/ in this repository, laid out as plugin managers expect (nvim/lua/revue_companion/, nvim/plugin/, nvim/doc/revue-companion.txt); lazy.nvim and others install it from this repository with their subdirectory option (lazy.nvim: { "thatrichan/revue-companion", url = …, config = …, dir/subdir = "nvim" }, to check per manager). Tests with busted or mini.test in headless Neovim against a fake revue printing fixture JSON, stylua and luacheck; releases as nvim-vX.Y.Z tags.

If a subdirectory turns out awkward for the plugin managers, a mirror repository (revue-companion.nvim) published from nvim/ by the release workflow is the fallback.

Plan

  1. The companions registration in Revue (shared with VS Code) and its nvim RPC client, with tests against a headless Neovim.
  2. The plugin's commands that only call revue: open, list, details, file, comment, files, health.
  3. Signs and virtual text, the statusline component.
  4. Registration and the two RPC functions, then thatrichan/revue#68 choosing it; focusing the terminal.
  5. Docs: Revue's docs/editors.md gets a Neovim section, and :help revue-companion.
*Moved from thatrichan/revue#70.* A Neovim plugin doing what the JetBrains plugin ([`jetbrains/`](../src/branch/main/jetbrains)) does, in Neovim's way: commands, a pull request list in a buffer or picker, signs in the gutter, and Revue opening a pull request in the running Neovim that has its branch. Neovim 0.10 or later, pure Lua, no other dependency (Telescope, fzf-lua and snacks pickers used when present). ## Features - `:Revue` / `:Revue open`: the current branch's pull request in Revue, or its New pull request page (`revue --pull-request <dir>`). - `:Revue list`: the repository's open pull requests (`revue --pulls`, with `--show`, drafts, search, pages) in Revue's sections (This branch, To review, Mine, Others), in a picker when one is installed, else a scratch buffer. `<CR>` opens the details buffer (`revue --pull`): review, checks, conflicts, files, conversation, stack; mappings to open in Revue or the browser, yank the link, switch to the branch, create the worktree (`--worktree`), fetch the branch (`--fetch-branch`). - **In buffers:** the branch's pull request's changes and pending comments as signs and virtual text (`--branch-pull`, `--pending`); `:Revue file` opens the file in the pull request at the cursor's line; `:Revue comment` on a visual selection adds a pending comment (`--comment`, the body typed in a small floating window); `:Revue files` lists the pull request's files to jump to. - A `statusline` component: the branch's pull request number and state, for lualine and others. - `require("revue_companion").setup({ path = …, signs = …, keys = … })`: the `revue` executable (empty: PATH, then the install places the JetBrains plugin looks in), no default mappings beyond the buffer-local ones in its own windows. - `:checkhealth revue_companion`: finds `revue`, reads `revue --version`'s `editor <N>` line, and says whether Revue is running. It talks to Revue only through the `revue` command (Revue's `docs/command-line.md`, "What editors can ask"), run with `vim.system` so nothing blocks the editor. No tokens enter Neovim. ## The way back: Revue reaching a running Neovim Every Neovim already listens on an RPC socket (`v:servername`), so it needs no server of its own: - **The plugin registers itself** in the same folder as the VS Code companion (#1): `<Revue's data folder>/companions/nvim-<pid>.json` with `{ "kind": "nvim", "pid", "socket": v:servername, "version" }`, written at startup and removed on `VimLeavePre`. Revue ignores a stale file (pid gone, socket refusing). - **Revue asks over msgpack-RPC**, calling the plugin's two functions, matching the JetBrains plugin's requests: `require("revue_companion").projects()` (cwd and tabs' working directories, each repository's root, branch and remotes, whether the UI is focused) and `require("revue_companion").open({ path, repo, branch, file, line })` (`:tcd` to the folder in a new tab unless one has the branch, then `:edit file` at the line). - Neovim has no window to bring forward itself: when it runs in a terminal, Revue also focuses that terminal (by the pid's parent terminal app on macOS, the window under `$WINDOWID` or the terminal's process on Linux), and says so when it can't. - **In Revue (thatrichan/revue):** the `companions` module reads these files too, and an `nvim` client (a small msgpack-RPC call over the socket: Unix socket, or named pipe on Windows) does the two requests. The editor chooser (thatrichan/revue#68) then treats Neovim like any other companion. ## Where it lives `nvim/` in this repository, laid out as plugin managers expect (`nvim/lua/revue_companion/`, `nvim/plugin/`, `nvim/doc/revue-companion.txt`); lazy.nvim and others install it from this repository with their subdirectory option (lazy.nvim: `{ "thatrichan/revue-companion", url = …, config = …, dir/subdir = "nvim" }`, to check per manager). Tests with `busted` or `mini.test` in headless Neovim against a fake `revue` printing fixture JSON, `stylua` and `luacheck`; releases as `nvim-vX.Y.Z` tags. If a subdirectory turns out awkward for the plugin managers, a mirror repository (`revue-companion.nvim`) published from `nvim/` by the release workflow is the fallback. ## Plan 1. The `companions` registration in Revue (shared with VS Code) and its `nvim` RPC client, with tests against a headless Neovim. 2. The plugin's commands that only call `revue`: open, list, details, file, comment, files, health. 3. Signs and virtual text, the statusline component. 4. Registration and the two RPC functions, then thatrichan/revue#68 choosing it; focusing the terminal. 5. Docs: Revue's `docs/editors.md` gets a Neovim section, and `:help revue-companion`.
Author
Owner

Step 1 of nvim/PLAN.md is in (merged in 757fcc8):

  • :Revue open | file | browse | yank | sign-in, with completion and <Plug> mappings; nothing loads at startup.
  • Finding revue as the JetBrains plugin does, gating on editor N, vim.system so nothing blocks.
  • The statusline component require("revue_companion").status(), :checkhealth revue_companion, :help revue-companion.
  • A Lua fake revue, mini.test specs (task nvim:test, coverage 97 %), stylua and selene (task nvim:lint).
  • CI: .forgejo/workflows/nvim.yml on Neovim 0.11.7, stable and nightly.
  • Decided: module revue_companion, Neovim 0.11 the oldest (the plan says so now).

Next: step 2, the pull request list and a pull request's page. The way back needs thatrichan/revue#80 first.

Step 1 of `nvim/PLAN.md` is in (merged in 757fcc8): - `:Revue open | file | browse | yank | sign-in`, with completion and `<Plug>` mappings; nothing loads at startup. - Finding `revue` as the JetBrains plugin does, gating on `editor N`, `vim.system` so nothing blocks. - The statusline component `require("revue_companion").status()`, `:checkhealth revue_companion`, `:help revue-companion`. - A Lua fake `revue`, mini.test specs (`task nvim:test`, coverage 97 %), stylua and selene (`task nvim:lint`). - CI: `.forgejo/workflows/nvim.yml` on Neovim 0.11.7, stable and nightly. - Decided: module `revue_companion`, Neovim 0.11 the oldest (the plan says so now). Next: step 2, the pull request list and a pull request's page. The way back needs thatrichan/revue#80 first.
Author
Owner

Step 2 is in (merged in 8cf6020): the pull request list and a pull request's page.

  • :Revue list [all|involved|to-review|review|reviewed|mine] [drafts|no-drafts]: a revue://pulls/<root> buffer in Revue's sections as folds, starred first; keys <CR> o gx y s f / R g?; the next page loads at the end.
  • :Revue pick through vim.ui.select.
  • A pull request's page, revue://pull/<owner>/<repo>/<n>, read-only Markdown (gO outline): state, review, checks, conflicts, tickets, files, conversation, Slack threads, stack; <CR> opens a file at its line, b fetches and switches to the branch, w makes a worktree in a new tab.
  • Highlight groups, <Plug>(RevueList) / <Plug>(RevuePick), :help sections; 170 specs, coverage 97 %.

Next: step 3, signs and pending comments in your buffers, :Revue diff, files, comment.

Step 2 is in (merged in 8cf6020): the pull request list and a pull request's page. - `:Revue list [all|involved|to-review|review|reviewed|mine] [drafts|no-drafts]`: a `revue://pulls/<root>` buffer in Revue's sections as folds, starred first; keys `<CR>` `o` `gx` `y` `s` `f` `/` `R` `g?`; the next page loads at the end. - `:Revue pick` through `vim.ui.select`. - A pull request's page, `revue://pull/<owner>/<repo>/<n>`, read-only Markdown (`gO` outline): state, review, checks, conflicts, tickets, files, conversation, Slack threads, stack; `<CR>` opens a file at its line, `b` fetches and switches to the branch, `w` makes a worktree in a new tab. - Highlight groups, `<Plug>(RevueList)` / `<Plug>(RevuePick)`, `:help` sections; 170 specs, coverage 97 %. Next: step 3, signs and pending comments in your buffers, `:Revue diff`, `files`, `comment`.
Author
Owner

Step 3 is in (merged in a466f44): the pull request in your own files.

  • Signs by the lines the pull request adds, changes or removes (only while the checkout is at its head, as in JetBrains), and your pending comments as ✎ with their first line as virtual text; refreshed when Revue answers and on focus.
  • ]r / [r (with a count) between the pull request's changes.
  • :Revue diff (editor 3): the file before the pull request in a split, in diff mode.
  • :Revue files: the pull request's files, opened at their first change.
  • :Revue comments: your pending comments and the open conversations in the quickfix list.
  • :Revue comment (Normal, a range or visual): a Markdown float, :w / <C-s> sends it as a pending comment, q gives up.
  • Settings signs / comments, highlight groups, :help revue-companion → IN YOUR FILES; 230 specs, coverage 97 %.

Next: step 4, the review in Neovim (:Revue review).

Step 3 is in (merged in a466f44): the pull request in your own files. - Signs by the lines the pull request adds, changes or removes (only while the checkout is at its head, as in JetBrains), and your pending comments as `✎` with their first line as virtual text; refreshed when Revue answers and on focus. - `]r` / `[r` (with a count) between the pull request's changes. - `:Revue diff` (editor 3): the file before the pull request in a split, in diff mode. - `:Revue files`: the pull request's files, opened at their first change. - `:Revue comments`: your pending comments and the open conversations in the quickfix list. - `:Revue comment` (Normal, a range or visual): a Markdown float, `:w` / `<C-s>` sends it as a pending comment, `q` gives up. - Settings `signs` / `comments`, highlight groups, `:help revue-companion` → IN YOUR FILES; 230 specs, coverage 97 %. Next: step 4, the review in Neovim (`:Revue review`).
Author
Owner

Step 5, the way back, is in (merged in b8202e4), against thatrichan/revue#80 (editor 9):

  • In a Git repository, with Revue at editor 9 and v:servername set, the plugin registers with revue --companion nvim --pid … --socket <v:servername> without blocking, and runs --companion-gone on quit. The register setting turns it off.
  • Revue calls over msgpack-RPC require("revue_companion").projects() (the working directory and each tab's, their repositories, focus) and .open({path, repo, branch, file, line}) (the tab with that repository on the branch, else a new tab with :tcd, then the file at the line). Both are in :help revue-companion-from-revue.
  • :checkhealth says whether Revue knows this Neovim runs.
  • Specs include an RPC round trip into a second Neovim through the socket it registered.

Not yet tried against a real Revue. Still to do: step 4 (the review tab) and step 6 (README, release, mirror).

Step 5, the way back, is in (merged in b8202e4), against thatrichan/revue#80 (`editor 9`): - In a Git repository, with Revue at `editor 9` and `v:servername` set, the plugin registers with `revue --companion nvim --pid … --socket <v:servername>` without blocking, and runs `--companion-gone` on quit. The `register` setting turns it off. - Revue calls over msgpack-RPC `require("revue_companion").projects()` (the working directory and each tab's, their repositories, focus) and `.open({path, repo, branch, file, line})` (the tab with that repository on the branch, else a new tab with `:tcd`, then the file at the line). Both are in `:help revue-companion-from-revue`. - `:checkhealth` says whether Revue knows this Neovim runs. - Specs include an RPC round trip into a second Neovim through the socket it registered. Not yet tried against a real Revue. Still to do: step 4 (the review tab) and step 6 (README, release, mirror).
Author
Owner

Steps 4 and 6 are in (merged in e2c510b), so every step of the plan is built:

  • :Revue review [LINK] (also r on a pull request's page): a tab with the files as a tree (status, lines, 💬 conversations, ✎ pending, ✓ viewed) and the two sides in diff mode (text from Git when the clone has both commits, else --sides). Conversations as virtual lines under their lines, kept level on both sides; ]c/[c, ]f/[f, c to comment (normal or visual, either side), v viewed (editor 7), a float to reply, resolve (editor 5), react (editor 8), edit / delete your pending ones and apply a suggestion; @ completion from mentionable.
  • :Revue submit: the summary in a float with what goes with it, then the verdict (and resolve yours, Slack thread).
  • :Revue threads into the quickfix list.
  • Release: .forgejo/workflows/release-nvim.yml (by hand) tests nvim/ on its own, tags nvim-vX.Y.Z, and publishes nvim/ to thatrichan/revue-companion.nvim when the NVIM_MIRROR_TOKEN secret is set; nvim/README.md is that repository's README.
  • CI was failing on 0.11.7 and stable: the specs depended on how the system runs a file without #!, on the ruler left on the screen by 0.11, and on Revue's answers arriving before a screenshot. Fixed (0e94701); the specs pass on v0.11.7, stable and nightly.

338 specs. Left: creating the mirror repository and its secret, the first release, and trying it against a real Revue.

Steps 4 and 6 are in (merged in e2c510b), so every step of the plan is built: - **`:Revue review [LINK]`** (also `r` on a pull request's page): a tab with the files as a tree (status, lines, 💬 conversations, ✎ pending, ✓ viewed) and the two sides in diff mode (text from Git when the clone has both commits, else `--sides`). Conversations as virtual lines under their lines, kept level on both sides; `]c`/`[c`, `]f`/`[f`, `c` to comment (normal or visual, either side), `v` viewed (editor 7), a float to reply, resolve (editor 5), react (editor 8), edit / delete your pending ones and apply a suggestion; `@` completion from `mentionable`. - **`:Revue submit`**: the summary in a float with what goes with it, then the verdict (and resolve yours, Slack thread). - **`:Revue threads`** into the quickfix list. - **Release**: `.forgejo/workflows/release-nvim.yml` (by hand) tests `nvim/` on its own, tags `nvim-vX.Y.Z`, and publishes `nvim/` to `thatrichan/revue-companion.nvim` when the `NVIM_MIRROR_TOKEN` secret is set; `nvim/README.md` is that repository's README. - CI was failing on 0.11.7 and stable: the specs depended on how the system runs a file without `#!`, on the ruler left on the screen by 0.11, and on Revue's answers arriving before a screenshot. Fixed (0e94701); the specs pass on v0.11.7, stable and nightly. 338 specs. Left: creating the mirror repository and its secret, the first release, and trying it against a real Revue.
Author
Owner

Every step of the plan is built; the way back also skips registering over SSH or in a container, and projects() lists the repositories of other open files. CI passes on 0.11.7, stable and nightly. nvim 0.1.0 is queued (release-nvim), which also fills revue-companion.nvim; Revue's docs/editors.md already has the Neovim section. Checking the release and trying it against Revue 0.44 are in #9.

Every step of the plan is built; the way back also skips registering over SSH or in a container, and `projects()` lists the repositories of other open files. CI passes on 0.11.7, stable and nightly. **nvim 0.1.0** is queued (release-nvim), which also fills `revue-companion.nvim`; Revue's docs/editors.md already has the Neovim section. Checking the release and trying it against Revue 0.44 are in #9.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thatrichan/revue-companion#2
No description provided.