by Cannon07
A Neovim plugin that shows a live diff preview of AI coding agent's file edits before you accept or reject them.
# Add to your Claude Code skills
git clone https://github.com/Cannon07/code-preview.nvimA 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.


No comments yet. Be the first to share your thoughts!
nvim --headless -lFor Claude Code backend:
For OpenCode backend:
{
"Cannon07/code-preview.nvim",
config = function()
require("code-preview").setup()
end,
}
vim.opt.rtp:prepend("/path/to/code-preview.nvim")
require("code-preview").setup()
setup():CodePreviewInstallClaudeCodeHooks — writes hooks to .claude/settings.local.json<leader>dq to close the diff manuallysetup():CodePreviewInstallOpenCodeHooks — copies the plugin to .opencode/plugins/~/.config/opencode/opencode.json) has permission prompts enabled:
{
"permission": {
"edit": "ask",
"bash": "ask"
}
}
<leader>dq to close the diff manuallyAI 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).
All options with defaults:
require("code-preview").setup({
debug = false, -- enable debug logging to stdpath("log")/code-preview.log
diff = {
layout = "tab", -- "tab" (new tab) | "vsplit" (current tab) | "inline" (GitHub-style)
labels = { current = "CURRENT", proposed = "PROPOSED" },
equalize = true, -- 50/50 split widths (tab/vsplit only)
full_file = true, -- show full file, not just diff hunks (tab/vsplit only)
visible_only = false, -- skip diffs for files not open in any Neovim buffer
defer_claude_permissions = false, -- for Claude Code: let its own settings decide, don't prompt
},
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)
},
},
})
| Command | Description |
|---------|-------------|
| :CodePreviewInstallClaudeCodeHooks | Install Claude Code hooks to .claude/settings.local.json |
| :CodePreviewUninstallClaudeCodeHooks | Remove Claude Code hooks (leaves other hooks intact) |
| :CodePreviewInstallOpenCodeHooks | Install OpenCode plugin to .opencode/plugins/ |
| :CodePreviewUninstallOpenCodeHooks | Remove OpenCode plugin |
| :CodePreviewCloseDiff | Manually close the diff (use after rejecting a change) |
| :CodePreviewStatus | Show socket path, hook status, and dependency check |
| :CodePreviewToggleVisibleOnly | Toggle visible_only — show diffs only for open buffers |
| :checkhealth code-preview | Full health check (both backends) |
Migrating? The old
:ClaudePreview*commands still work but show a deprecation warning. They will be removed in a future release.
| Key | Description |
|-----|-------------|
| <leader>dq | Close the diff (same as :CodePreviewCloseDiff) |
code-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 |
+/- signs indicate added/removed lines]c / [c to jump between changesTo use inline diff:
require("code-preview").setup({
diff = { layout = "inline" },
})
If you use neo-tree.nvim, code-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.

| 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:
<leader>dqAll neo-tree options with defaults:
require("code-preview").setup({
neo_tree = {
enabled = true, -- set false to disable neo-tree integration
reveal = true, -- auto-reveal changed files in the tree
reveal_root = "cwd", -- "cwd" (current working dir) or "git" (git root)
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.
code-preview.nvim/
├── lua/code-preview/
│ ├── init.lua setup(), config, commands
│ ├── diff.lua show_diff(), close_diff()
│ ├── log.lua opt-in debug logging
│ ├── changes.lua change status registry (modified/created/deleted)
│ ├── neo_tree.lua neo-tree integration (icons, virtual nodes, reveal)
│ ├── health.lua :checkhealth (both backends)
│ └── backends/
│ ├── claudecode.lua Claude Code hook install/uninstall
│ └── opencode.lua OpenCode plugin install/uninstall
├── bin/ Sha