claude-preview.nvim
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 88 GitHub stars
Code Fail
- execSync — Synchronous shell command execution in opencode-plugin/nvim.ts
- process.env — Environment variable access in opencode-plugin/nvim.ts
Permissions Pass
- Permissions — No dangerous permissions requested
This is a Neovim plugin that provides a live diff preview of file changes made by AI coding agents (like Claude Code or OpenCode), allowing users to review and approve edits before they are written to disk.
Security Assessment
The overall risk is Medium. The plugin does not request explicitly dangerous permissions and does not appear to make unauthorized external network requests. However, the automated audit raised a flag for synchronous shell command execution (`execSync`) and environment variable access (`process.env`) within its OpenCode integration script. Because its core function involves intercepting AI edits and executing shell commands to display diffs, this behavior is expected. Still, users should verify exactly which local commands and environment variables are being accessed. No hardcoded secrets were detected.
Quality Assessment
The project exhibits strong health and maintenance signals. It is licensed under the permissive and standard MIT license. It is actively maintained, with its most recent code push occurring today. Additionally, it has garnered 88 GitHub stars, indicating a healthy level of community trust and usage for a niche developer tool.
Verdict
Use with caution—while the tool is actively maintained and safe in concept, developers should quickly review its shell execution scripts before integrating it into their local development environment.
A Neovim plugin that shows a live diff preview of AI coding agent's file edits before you accept or reject them.
claude-preview.nvim
A Neovim plugin that shows a diff preview before your AI coding agent applies any file change — letting you review exactly what's changing before accepting.
Supports Claude Code and OpenCode as backends.
Demo
Claude Code

OpenCode

Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- How it works
- Configuration
- Commands
- Diff Layouts
- Neo-tree Integration
- Testing
- Troubleshooting
Features
- Diff preview — side-by-side or inline diff opens in Neovim before any file is written
- Multiple layouts — tab, vsplit, or GitHub-style inline diff with syntax highlighting
- Neo-tree integration — file tree indicators show which files are being modified, created, or deleted
- Multi-backend — works with Claude Code CLI and OpenCode
- No Python dependency — file transformations use
nvim --headless -l
Requirements
- Neovim >= 0.9
For Claude Code backend:
- Claude Code CLI with hooks support
- jq — for JSON parsing in hook scripts
For OpenCode backend:
- OpenCode >= 1.3.0
Installation
lazy.nvim
{
"Cannon07/claude-preview.nvim",
config = function()
require("claude-preview").setup()
end,
}
Manual (path-based)
vim.opt.rtp:prepend("/path/to/claude-preview.nvim")
require("claude-preview").setup()
Quick Start
Claude Code
- Install the plugin and call
setup() - Open a project in Neovim
- Run
:ClaudePreviewInstallHooks— writes hooks to.claude/settings.local.json - Restart Claude Code CLI in the project directory
- Ask Claude to edit a file — a diff opens automatically in Neovim
- Accept/reject in the CLI; the diff closes automatically on accept
- If rejected, press
<leader>dqto close the diff manually
OpenCode
- Install the plugin and call
setup() - Open a project in Neovim
- Run
:CodePreviewInstallOpenCodeHooks— copies the plugin to.opencode/plugins/ - Ensure your OpenCode config (
~/.config/opencode/opencode.json) has permission prompts enabled:{ "permission": { "edit": "ask", "bash": "ask" } } - Start OpenCode in the project directory
- Ask OpenCode to edit a file — a diff opens automatically in Neovim
- Accept/reject in OpenCode; the diff closes automatically on accept
- If rejected, press
<leader>dqto close the diff manually
How it works
AI Agent (terminal) Neovim
| |
Proposes an Edit |
| |
Hook/plugin fires ──→ compute diff ──→ RPC → show_diff()
| | (side-by-side or inline)
CLI: "Accept? (y/n)" |
| User reviews diff
User accepts/rejects |
| |
Post hook fires ────→ cleanup ─────→ RPC → close_diff()
Claude Code uses shell-based hooks (PreToolUse/PostToolUse) configured in .claude/settings.local.json.
OpenCode uses a TypeScript plugin (tool.execute.before/tool.execute.after) loaded from .opencode/plugins/.
Both backends communicate with Neovim via RPC (nvim --server <socket> --remote-send).
Configuration
All options with defaults:
require("claude-preview").setup({
diff = {
layout = "tab", -- "tab" (new tab) | "vsplit" (current tab) | "inline" (GitHub-style)
labels = { current = "CURRENT", proposed = "PROPOSED" },
auto_close = true, -- close diff after accept
equalize = true, -- 50/50 split widths (tab/vsplit only)
full_file = true, -- show full file, not just diff hunks (tab/vsplit only)
},
highlights = {
current = { -- CURRENT (original) side — tab/vsplit layouts
DiffAdd = { bg = "#4c2e2e" },
DiffDelete = { bg = "#4c2e2e" },
DiffChange = { bg = "#4c3a2e" },
DiffText = { bg = "#5c3030" },
},
proposed = { -- PROPOSED side — tab/vsplit layouts
DiffAdd = { bg = "#2e4c2e" },
DiffDelete = { bg = "#4c2e2e" },
DiffChange = { bg = "#2e3c4c" },
DiffText = { bg = "#3e5c3e" },
},
inline = { -- inline layout
added = { bg = "#2e4c2e" }, -- added line background
removed = { bg = "#4c2e2e" }, -- removed line background
added_text = { bg = "#3a6e3a" }, -- changed characters (added)
removed_text = { bg = "#6e3a3a" }, -- changed characters (removed)
},
},
})
Commands
| Command | Description |
|---|---|
:ClaudePreviewInstallHooks |
Install Claude Code hooks to .claude/settings.local.json |
:ClaudePreviewUninstallHooks |
Remove Claude Code hooks (leaves other hooks intact) |
:CodePreviewInstallOpenCodeHooks |
Install OpenCode plugin to .opencode/plugins/ |
:CodePreviewUninstallOpenCodeHooks |
Remove OpenCode plugin |
:ClaudePreviewCloseDiff |
Manually close the diff (use after rejecting a change) |
:ClaudePreviewStatus |
Show socket path, hook status, and dependency check |
:checkhealth claude-preview |
Full health check (both backends) |
Keymaps
| Key | Description |
|---|---|
<leader>dq |
Close the diff (same as :ClaudePreviewCloseDiff) |
Diff Layouts
claude-preview supports three diff layouts, configured via diff.layout:
| Layout | Description |
|---|---|
"tab" (default) |
Side-by-side diff in a new tab — CURRENT on the left, PROPOSED on the right |
"vsplit" |
Side-by-side diff as a vertical split in the current tab |
"inline" |
GitHub-style unified diff in a single buffer with syntax highlighting preserved |
Inline diff features
- Syntax highlighting — the file's language highlighting is preserved
- Character-level diffs — changed portions within a line are highlighted with a brighter background
- Sign column —
+/-signs indicate added/removed lines - Navigation —
]c/[cto jump between changes
To use inline diff:
require("claude-preview").setup({
diff = { layout = "inline" },
})
Neo-tree Integration (Optional)
If you use neo-tree.nvim, claude-preview will automatically decorate your file tree with visual indicators when changes are proposed. No extra configuration is required — it works out of the box.

What you get
| Status | Icon | Name Color | Description |
|---|---|---|---|
| Modified | | Orange | An existing file is being edited |
| Created | | Cyan + italic | A new file is being created (shown as a virtual node) |
| Deleted | | Red + strikethrough | A file is being deleted via rm |
Additional behaviors:
- Auto-reveal — the tree expands to highlight the changed file
- Virtual nodes — new files/directories appear in the tree before they exist on disk
- Clean focus — git status, diagnostics, and modified indicators are temporarily hidden while changes are pending
- Auto-cleanup — all indicators clear when you accept, reject, or press
<leader>dq
Neo-tree configuration options
All neo-tree options with defaults:
require("claude-preview").setup({
neo_tree = {
enabled = true, -- set false to disable neo-tree integration
position = "right", -- neo-tree window position: "left", "right", "float"
symbols = {
modified = "",
created = "",
deleted = "",
},
highlights = {
modified = { fg = "#e8a838", bold = true },
created = { fg = "#56c8d8", bold = true },
deleted = { fg = "#e06c75", bold = true, strikethrough = true },
},
},
})
Note: Neo-tree is a soft dependency. If neo-tree is not installed, the plugin works exactly as before — only the diff preview.
Architecture
claude-preview.nvim/
├── lua/claude-preview/
│ ├── init.lua setup(), config, commands
│ ├── diff.lua show_diff(), close_diff()
│ ├── hooks.lua install/uninstall for both backends
│ ├── changes.lua change status registry (modified/created/deleted)
│ ├── neo_tree.lua neo-tree integration (icons, virtual nodes, reveal)
│ └── health.lua :checkhealth (both backends)
├── bin/ Claude Code hook scripts
│ ├── claude-preview-diff.sh PreToolUse hook entry point
│ ├── claude-close-diff.sh PostToolUse hook entry point
│ ├── nvim-socket.sh Neovim socket discovery
│ ├── nvim-send.sh RPC send helper
│ ├── apply-edit.lua Single Edit transformer
│ └── apply-multi-edit.lua MultiEdit transformer
└── opencode-plugin/ OpenCode plugin
├── index.ts tool.execute.before/after hooks
├── nvim.ts Neovim socket discovery + RPC
└── edits.ts Edit computation helpers
Testing
The test suite uses plenary.nvim for core plugin tests and shell scripts for backend integration tests. CI runs on both Ubuntu and macOS.
./tests/run.sh # all tests (plugin + backends)
./tests/run.sh plugin # core plugin tests only (plenary busted)
./tests/run.sh backends # all backend integration tests
./tests/run.sh backends/claude # Claude Code backend only
./tests/run.sh backends/opencode # OpenCode backend only
Dependencies: Neovim >= 0.10, jq, bun (for OpenCode tests). Plenary is auto-installed to deps/ on first run.
Recommended companion settings
For buffers to auto-reload after a file is written, add this to your Neovim config:
vim.o.autoread = true
vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold" }, {
command = "checktime",
})
Troubleshooting
Diff doesn't open
- Run
:ClaudePreviewStatus— check thatNeovim socketis found - Run
:checkhealth claude-preview— check for missing dependencies - Restart the CLI agent after installing hooks (hooks are read at startup)
Claude Code hooks not firing
- Run
:ClaudePreviewInstallHooksin the project root - Verify
.claude/settings.local.jsoncontains the hook entries - Ensure
jqis in PATH - Restart Claude Code CLI
OpenCode plugin not loading
- Run
:CodePreviewInstallOpenCodeHooksin the project root - Verify
.opencode/plugins/index.tsexists - Ensure
"permission": { "edit": "ask" }is set in~/.config/opencode/opencode.json - Restart OpenCode
Diff doesn't close after rejecting
- Press
<leader>dqor run:ClaudePreviewCloseDiff— the post hook only fires on accept
License
MIT — see LICENSE
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found