Code Reader is a Neovim plugin for reading AI-authored code and diff walkthroughs next to the files they explain. It opens a three-pane reading layout with source or before/after diff content, the current explanation step, and a navigable outline.
Use it when an AI assistant has produced a structured walkthrough and you want to review the implementation in Neovim without jumping between Markdown, source files, and patches by hand.
- Source walkthroughs driven by
type: code-readerMarkdown files. - Diff walkthroughs driven by
type: code-reader-diffMarkdown files and unified diff patches. - Synchronized code, explanation, and TOC views.
- Step navigation with
:CodeReaderNext,:CodeReaderPrev, TOC entries, and internal[[step-id]]links. - Focus highlighting for the current source range or diff hunk, with optional dimming for unrelated lines.
- Side-by-side diff reading with old/new gutters, changed-line markers, moved-line detection, and inline modified-span highlights.
- Tree-sitter symbol links, with LSP document-highlight support when available.
- Mermaid code block rendering through an optional Node helper.
- Optional integration with
render-markdown.nvimfor richer Markdown rendering. - Optional integration with
neoscroll.nvimfor same-buffer range movement. - Visual-range reference copying with
:CodeReaderCopyRef, useful when asking an AI assistant to create or revise a walkthrough. - Bundled Code Reader Authoring plugin for Codex, including skills for writing code and diff explanations.
Using Neovim's built-in package manager:
vim.pack.add({
{
src = "git@github.com:KudoLayton/code-reader.nvim.git",
name = "code-reader.nvim",
},
}, {
load = true,
})Using lazy.nvim:
{
url = "git@github.com:KudoLayton/code-reader.nvim.git",
name = "code-reader.nvim",
cmd = {
"CodeReaderOpen",
"CodeReaderNext",
"CodeReaderPrev",
"CodeReaderGoto",
"CodeReaderToggleFocus",
"CodeReaderToggleDimming",
"CodeReaderClose",
"CodeReaderCopyRef",
},
}Mermaid rendering is optional. Install the npm dependency from the plugin root if you want mermaid fenced code blocks to render as text diagrams:
npm installWith lazy.nvim, attach the install step as a build hook:
{
url = "git@github.com:KudoLayton/code-reader.nvim.git",
name = "code-reader.nvim",
build = "npm install",
}Run :checkhealth code_reader after installation to inspect Node, npm, the Mermaid helper, beautiful-mermaid, debug logging, and Tree-sitter readiness for diff rendering.
This repository also includes a local Codex plugin at plugins/code-reader-authoring. It provides two authoring skills:
write-code-explanation: creates or revisestype: code-readersource walkthroughs.write-diff-explanation: creates or revisestype: code-reader-diffpatch walkthroughs.
The local marketplace entry is stored in .agents/plugins/marketplace.json and points at ./plugins/code-reader-authoring. After installing or enabling that Codex plugin, ask Codex to use Code Reader Authoring when you want a walkthrough for a feature, module, request flow, or diff.
The authoring plugin also includes a validator:
python plugins/code-reader-authoring/scripts/validate_code_reader_markdown.py --project-root <repo-root> <markdown-file>Use --allow-partial-diff only when the diff walkthrough intentionally does not cover every hunk.
This repository includes an inert demo project under demo/basic. The demo files are shipped with the plugin, but they are not in Neovim auto-load directories and do not affect startup or runtime behavior unless opened explicitly.
cd demo/basic
nvim .Open the source walkthrough:
:CodeReaderOpen .code_reader/walkthrough.mdOpen the diff walkthrough from the same project root:
:CodeReaderOpen .code_reader/diffs/request-update.mdIn the demo, check that step navigation updates the source or diff target while the explanation and TOC keep their own cursors. Use ]r and [r to move between steps, <CR> on TOC entries or explanation links to jump, and q in the explanation or TOC panel to close Code Reader.
Open a Code Reader Markdown file from the project root:
:CodeReaderOpen .code_reader/flow.mdCommands:
:CodeReaderOpen [file]opens the source, explanation, and TOC layout.:CodeReaderNextand:CodeReaderPrevmove between steps.:CodeReaderGoto {index}jumps to a step by list index.:CodeReaderToggleDimmingtoggles dimming for non-focused source and diff lines.:CodeReaderToggleFocustoggles legacy focus mode, which also affects dimming.:CodeReaderClosecloses the explanation and TOC views and clears Code Reader highlights.:'<,'>CodeReaderCopyRef [register]copies the selected visual range as an AI-ready reference.
Inside the explanation panel, [r and ]r move between steps. Press <CR> on links in the Navigation list to jump to related steps or open the source. Navigation links show source or diff movement hints such as (↑3), (↓8), (↗ src/file.lua#L10-L20), or (↓8 src/file.lua#H2).
In the TOC panel, moving the cursor only moves the TOC cursor. Press <CR> to jump to the selected step; the explanation and code views update to that step while focus stays in the TOC. Press <CR> on an internal step link or a treesitter:// symbol link in the explanation panel to activate it.
Visual selections can be copied in the same reference style as refcopy.nvim:
vim.keymap.set("x", "<leader>ry", ":CodeReaderCopyRef<CR>", {
desc = "Copy Code Reader reference",
})The command copies regular source selections as path#Lx-Ly, diff views as logical hunk references such as src/app.lua#H1@new:L3-L5, and Markdown views as the original explanation file range when the selection maps cleanly to source Markdown. Mixed generated text falls back to copying the selected text.
Code Reader works without setup, but options can be passed through require("code_reader").setup().
require("code_reader").setup({
dimming = true,
max_dim_lines = 5000,
smooth_scroll = {
enabled = "auto",
},
mermaid = {
enabled = true,
timeout_ms = 2000,
use_ascii = false,
},
debug = {
enabled = false,
-- log_file = vim.fn.stdpath("log") .. "/code-reader.log",
},
})Set dimming = false to keep the current explanation range highlighted without dimming the rest of the code. Use require("code_reader").toggle_dimming() to toggle dimming from Lua, or pass true or false to set it explicitly.
When neoscroll.nvim is installed, Code Reader uses it automatically for same-buffer source and diff range movement. With mini.animate, Code Reader waits for active scroll animations before revealing the next same-buffer target. File switches and layout changes still jump immediately. Disable smooth-scroll integration with:
require("code_reader").setup({
smooth_scroll = {
enabled = false,
},
})If Node, npm, or beautiful-mermaid is missing, Code Reader keeps the original Mermaid code block visible. Disable Mermaid rendering with:
require("code_reader").setup({
mermaid = {
enabled = false,
},
})Enable debug logging when you need to diagnose rendering behavior on another machine:
require("code_reader").setup({
debug = {
enabled = true,
-- log_file = vim.fn.stdpath("log") .. "/code-reader.log",
},
})If render-markdown.nvim is installed and configured, Code Reader asks it to render the Markdown scratch buffers. This is optional; tables, callouts, and richer Markdown decoration are provided by render-markdown.nvim when present, while Code Reader's own syntax and navigation highlights still work without it.
Explanation files are Markdown files, usually stored under .code_reader/. Step blocks are separated by a line containing only ---.
Use type: code-reader for source walkthroughs:
---
type: code-reader
version: 1
---
<!-- code-reader: front-page -->
# Code Reader Overview
Explain the purpose, scope, and high-level structure of this walkthrough.
---
# 1. Request lifecycle
Source: `src/server.lua#L10-L30`
Explain the top-level flow here. Continue at [[1.1|Parse request]].
[server symbol](<treesitter://src/server.lua?query=(identifier) @code_reader.symbol>)
```mermaid
flowchart TD
Request --> Parse
Parse --> Render
```
---
## 1.1. Parse request
Source: `src/parser.lua#L5-L12`
Explain the nested call-stack detail here.Rules:
- The first frontmatter block identifies the file as
type: code-reader. - An optional first section can be marked as a front page with
<!-- code-reader: front-page -->. - The front page has no source range. It is rendered in the code window with its main content, an automatic source-file summary, and a step TOC.
- The first Markdown heading in a step becomes the step title.
- Heading depth drives TOC nesting, so
## 1.1 ...becomes a nested call-stack step. Nested steps are still separate----delimited step blocks. - GitHub-style
path#Lxandpath#Lx-Lyreferences define the source range. - Optional source hashes can be appended as
path#Lx-Ly@sha256:<hash>. - Obsidian-style
[[step-id]]and[[step-id|label]]links jump to another step in the same explanation file. - Markdown links that start with
treesitter://highlight symbols in the named source file. - Fenced code blocks marked as
mermaidare rendered as text diagrams when Mermaid support is available.
Symbol links must always name the source path:
[run](<treesitter://src/app.lua?query=(identifier) @code_reader.symbol>)The source path is resolved from the project root. The Tree-sitter query selects the seed symbol with @code_reader.symbol; if that capture is not present, the first capture is used. When an LSP client supports textDocument/documentHighlight, Code Reader highlights the matching symbols reported by LSP. Without LSP results, it falls back to highlighting every Tree-sitter capture from the query.
Diff explanations use the same section and navigation model with a different frontmatter type. Store the diff and its explanation together, usually under .code_reader/diffs/:
---
type: code-reader-diff
version: 1
diff: ./change.diff
---
<!-- code-reader: front-page -->
# Diff Overview
Explain the change set.
---
# 1. Toggle flag
Diff: `src/app.lua#H1`
Explain the first hunk.Open the Markdown file with :CodeReaderOpen. Code Reader parses the referenced unified diff, opens a side-by-side before/after view, and shows automatic coverage on the front page.
Rules:
- Coverage is based on changed lines inside hunks, not raw diff header or context lines.
Diff: path#Hnreferences the nth hunk for that file.Diff: path#Hn@old:Lx-LyorDiff: path#Hn@new:Lx-Lynarrows the focused side-specific line range without hiding the rest of the comparable file. Use@afor old and@bfor new when you want Git-style shorthand.- Range bounds can mix absolute file lines with hunk-relative bounds, such as
Diff: path#Hn@new:L(-1)-L(+2)orDiff: path#Hn@old:L(-1)-L22. Relative start bounds are measured from the hunk start, and relative end bounds are measured from the hunk end. - Signed shorthand such as
L-1-L22is accepted, butL(-1)-L22is the recommended written form. padding=Nandpad=Nexpand the focused range around the selected hunk by the same number of lines on both sides, for exampleDiff: path#Hn@new:padding=2.- If the current source matches the pre-change context, Code Reader shows full-file before/after buffers with an in-memory patched after version.
- If the current source already matches the post-change context, Code Reader reconstructs the before version in memory and still shows full-file before/after buffers.
- If the full file cannot be compared but the current hunk can, Code Reader shows a full-file view with only the selected hunk applied. If that also fails, it falls back to the hunk-only side-by-side snippet.
- Diff side-by-side views include old/new line-number gutters,
~/+/-/>markers, moved-line detection, and inline highlights for modified spans. - Diff side-by-side views apply Tree-sitter syntax highlighting to the code portion when a parser is available for the changed file.
- Focus mode also applies to full-file diff views: unrelated rows are dimmed while the current explanation hunk stays highlighted. Dimming is enabled by default and can be disabled with
dimming = false.
Run the Lua-only parser and link tests:
lua tests/parser_spec.lua
lua tests/links_spec.luaRun the Neovim integration tests:
nvim --headless -u NONE -l tests/diff_spec.lua
nvim --headless -u NONE -l tests/diff_render_spec.lua
nvim --headless -u NONE -l tests/mermaid_spec.lua
nvim --headless -u NONE -l tests/health_spec.lua
nvim --headless -u NONE -l tests/nvim_spec.lua
nvim --headless -u NONE -l tests/open_spec.lua
nvim --headless -u NONE -l tests/diff_open_spec.lua
nvim --headless -u NONE -l tests/smooth_scroll_spec.lua
nvim --headless -u NONE -l tests/symbol_spec.lua
nvim --headless -u NONE -l tests/demo_spec.luaValidate authored walkthrough files with:
python plugins/code-reader-authoring/scripts/validate_code_reader_markdown.py --project-root <repo-root> <markdown-file>