Skip to content

Commit eefefdd

Browse files
committed
feat: visible_only mode, toggle command, neo-tree reveal improvements
- Add diff.visible_only config (default false) — only show diff for files open in a visible nvim window; auto-approve others - Add :ClaudePreviewToggleVisibleOnly command for session toggle - Add neo_tree.reveal config (default true) — disable to skip reveal - Add neo_tree.reveal_root config ("cwd"|"git") — pass dir param to neo-tree reveal instead of changing nvim's cwd - Batch all config + visibility queries into single M.hook_context() RPC - Use vim.uv.fs_realpath() for canonical path resolution - Guard close-diff hook: only clean up when a diff is actually open - Remove post-close reveal to avoid stale CWD prompts
1 parent 4e1e54f commit eefefdd

4 files changed

Lines changed: 102 additions & 35 deletions

File tree

bin/claude-close-diff.sh

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ fi
2323
# Extract file path for post-close reveal
2424
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)"
2525

26-
# Clear neo-tree change indicators and close the diff tab
27-
nvim_send "require('claude-preview.changes').clear_all()" || true
28-
nvim_send "require('claude-preview.diff').close_diff()" || true
29-
# Deferred refresh + reveal so neo-tree picks up changes after Claude writes them to disk
30-
if [[ -n "$FILE_PATH" ]]; then
31-
FILE_PATH_ESC="$(escape_lua "$FILE_PATH")"
32-
nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').refresh() end) vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$FILE_PATH_ESC') end) end, 200) end, 200)" || true
33-
else
26+
# Only clean up if a diff was actually open
27+
DIFF_OPEN=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"require('claude-preview.diff').is_open()\")" 2>/dev/null || echo "false")
28+
29+
if [[ "$DIFF_OPEN" == "true" ]]; then
30+
nvim_send "require('claude-preview.changes').clear_all()" || true
31+
nvim_send "require('claude-preview.diff').close_diff()" || true
3432
nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').refresh() end) end, 200)" || true
3533
fi
3634

bin/claude-preview-diff.sh

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ if [[ "$HAS_NVIM" == "true" ]]; then
138138
DISPLAY_ESC="$(escape_lua "$DISPLAY_NAME")"
139139
FILE_PATH_ESC="$(escape_lua "$FILE_PATH")"
140140

141+
# Query config + file visibility from nvim in a single RPC call
142+
HOOK_CTX=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"require('claude-preview').hook_context('${FILE_PATH_ESC}')\")" 2>/dev/null || echo '{}')
143+
VISIBLE_ONLY=$(echo "$HOOK_CTX" | jq -r '.visible_only // false')
144+
NEO_TREE_REVEAL=$(echo "$HOOK_CTX" | jq -r '.neo_tree_reveal // true')
145+
NEO_TREE_REVEAL_ROOT=$(echo "$HOOK_CTX" | jq -r '.reveal_root // "cwd"')
146+
FILE_VISIBLE=$(echo "$HOOK_CTX" | jq -r '.file_visible // false')
147+
148+
# Decide whether to show the diff
149+
SHOULD_SHOW="1"
150+
if [[ "$VISIBLE_ONLY" == "true" && "$FILE_VISIBLE" != "true" ]]; then
151+
SHOULD_SHOW="0"
152+
fi
153+
141154
# Determine change status for neo-tree indicator
142155
# Check if the actual file exists on disk (not the temp copy, which is always created)
143156
if [[ -f "$FILE_PATH" ]]; then
@@ -146,35 +159,38 @@ if [[ "$HAS_NVIM" == "true" ]]; then
146159
CHANGE_STATUS="created"
147160
fi
148161

149-
nvim_send "require('claude-preview.changes').set('$FILE_PATH_ESC', '$CHANGE_STATUS')" || true
150-
nvim_send "pcall(function() require('claude-preview.neo_tree').refresh() end)" || true
151-
# Reveal the file in neo-tree: for modified files reveal the file itself,
152-
# for created files reveal the nearest existing parent directory
153-
if [[ "$CHANGE_STATUS" == "modified" ]]; then
154-
nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$FILE_PATH_ESC') end) end, 300)" || true
155-
else
156-
# Walk up to find the nearest existing parent directory
157-
REVEAL_DIR="$(dirname "$FILE_PATH")"
158-
while [[ ! -d "$REVEAL_DIR" && "$REVEAL_DIR" != "/" ]]; do
159-
REVEAL_DIR="$(dirname "$REVEAL_DIR")"
160-
done
161-
# Reveal a file inside the parent dir to force neo-tree to expand it
162-
REVEAL_TARGET="$(find "$REVEAL_DIR" -maxdepth 1 -type f | head -1)"
163-
if [[ -z "$REVEAL_TARGET" ]]; then
164-
REVEAL_TARGET="$REVEAL_DIR"
162+
if [[ "$SHOULD_SHOW" == "1" ]]; then
163+
nvim_send "require('claude-preview.changes').set('$FILE_PATH_ESC', '$CHANGE_STATUS')" || true
164+
165+
# Neo-tree integration (gated by config)
166+
if [[ "$NEO_TREE_REVEAL" == "true" ]]; then
167+
# Resolve the directory neo-tree should root from
168+
REVEAL_DIR=""
169+
if [[ "$NEO_TREE_REVEAL_ROOT" == "git" ]]; then
170+
REVEAL_DIR=$(git -C "$(dirname "$FILE_PATH")" rev-parse --show-toplevel 2>/dev/null || echo "")
171+
REVEAL_DIR="${REVEAL_DIR:-$(dirname "$FILE_PATH")}"
172+
fi
173+
174+
nvim_send "pcall(function() require('claude-preview.neo_tree').refresh() end)" || true
175+
176+
if [[ -n "$REVEAL_DIR" ]]; then
177+
REVEAL_DIR_ESC="$(escape_lua "$REVEAL_DIR")"
178+
nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$FILE_PATH_ESC', '$REVEAL_DIR_ESC') end) end, 300)" || true
179+
else
180+
nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$FILE_PATH_ESC') end) end, 300)" || true
181+
fi
165182
fi
166-
REVEAL_TARGET_ESC="$(escape_lua "$REVEAL_TARGET")"
167-
nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$REVEAL_TARGET_ESC') end) end, 300)" || true
183+
184+
nvim_send "require('claude-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC')" || true
168185
fi
169-
nvim_send "require('claude-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC')" || true
170186
fi
171187

172-
# --- Always ask for user confirmation ---
188+
# --- Permission decision ---
173189

174-
if [[ "$HAS_NVIM" == "true" ]]; then
190+
if [[ "$HAS_NVIM" == "true" && "$SHOULD_SHOW" == "1" ]]; then
175191
REASON="Diff preview sent to Neovim. Review before accepting."
192+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"%s"}}\n' "$REASON"
176193
else
177-
REASON="Neovim not running. Review the diff in CLI before accepting."
194+
# File not visible or no nvim — auto-approve
195+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"Auto-approved"}}\n'
178196
fi
179-
180-
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"%s"}}\n' "$REASON"

lua/claude-preview/init.lua

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ local default_config = {
1010
auto_close = true,
1111
equalize = true,
1212
full_file = true,
13+
visible_only = false, -- only show diffs for files open in a visible nvim window
1314
},
1415
neo_tree = {
1516
enabled = true,
17+
reveal = true, -- reveal edited files in neo-tree
18+
reveal_root = "cwd", -- "cwd" (default), "git" (nearest git root), or false (skip reveal)
1619
refresh_on_change = true,
1720
position = "right",
1821
symbols = {
@@ -87,6 +90,15 @@ function M.setup(user_config)
8790
M.status()
8891
end, { desc = "Show claude-preview status" })
8992

93+
vim.api.nvim_create_user_command("ClaudePreviewToggleVisibleOnly", function()
94+
M.config.diff.visible_only = not M.config.diff.visible_only
95+
vim.notify(
96+
"claude-preview: visible_only = " .. tostring(M.config.diff.visible_only),
97+
vim.log.levels.INFO,
98+
{ title = "claude-preview" }
99+
)
100+
end, { desc = "Toggle visible_only — show diffs only for open buffers vs all files" })
101+
90102
-- Neo-tree integration (soft dependency)
91103
if M.config.neo_tree.enabled then
92104
require("claude-preview.neo_tree").setup(M.config)
@@ -97,6 +109,43 @@ function M.setup(user_config)
97109
end, { desc = "Close claude-preview diff" })
98110
end
99111

112+
--- Query hook context for the PreToolUse shell script.
113+
--- Returns a JSON string with config + file visibility in a single RPC call.
114+
--- @param file_path string absolute path of the file being edited
115+
--- @return string JSON: { visible_only, neo_tree_reveal, reveal_root, file_visible }
116+
function M.hook_context(file_path)
117+
local cfg = M.config
118+
local visible_only = cfg.diff.visible_only and true or false
119+
local neo_tree_reveal = (cfg.neo_tree.enabled and cfg.neo_tree.reveal) and true or false
120+
local reveal_root = cfg.neo_tree.reveal_root or "cwd"
121+
122+
local file_visible = false
123+
if visible_only and file_path ~= "" then
124+
-- Resolve to canonical path; use case-insensitive compare on macOS
125+
local is_mac = vim.fn.has("mac") == 1
126+
local target = vim.uv.fs_realpath(file_path) or vim.fn.fnamemodify(file_path, ":p")
127+
if is_mac then target = target:lower() end
128+
129+
for _, w in ipairs(vim.api.nvim_list_wins()) do
130+
local b = vim.api.nvim_win_get_buf(w)
131+
local name = vim.uv.fs_realpath(vim.api.nvim_buf_get_name(b))
132+
or vim.fn.fnamemodify(vim.api.nvim_buf_get_name(b), ":p")
133+
if is_mac then name = name:lower() end
134+
if name == target then
135+
file_visible = true
136+
break
137+
end
138+
end
139+
end
140+
141+
return vim.json.encode({
142+
visible_only = visible_only,
143+
neo_tree_reveal = neo_tree_reveal,
144+
reveal_root = reveal_root,
145+
file_visible = file_visible,
146+
})
147+
end
148+
100149
function M.status()
101150
local lines = { "claude-preview.nvim status", string.rep("", 40) }
102151

lua/claude-preview/neo_tree.lua

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,20 +345,24 @@ function M.refresh()
345345
end)
346346
end
347347

348-
function M.reveal(filepath)
348+
function M.reveal(filepath, dir)
349349
if not has_neo_tree then
350350
return
351351
end
352352
pcall(function()
353353
local cfg = require("claude-preview").config
354354
local position = cfg.neo_tree.position or "right"
355-
require("neo-tree.command").execute({
355+
local opts = {
356356
action = "show",
357357
source = "filesystem",
358358
reveal_file = filepath,
359359
position = position,
360360
toggle = false,
361-
})
361+
}
362+
if dir then
363+
opts.dir = dir
364+
end
365+
require("neo-tree.command").execute(opts)
362366
end)
363367
end
364368

0 commit comments

Comments
 (0)