Skip to content

Commit 561660d

Browse files
authored
feat: multi-tab simultaneous diffs for multi-file edits (#37)
1 parent 0a38c34 commit 561660d

10 files changed

Lines changed: 267 additions & 234 deletions

File tree

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ require("code-preview").setup({
145145
diff = {
146146
layout = "tab", -- "tab" (new tab) | "vsplit" (current tab) | "inline" (GitHub-style)
147147
labels = { current = "CURRENT", proposed = "PROPOSED" },
148-
auto_close = true, -- close diff after accept
149148
equalize = true, -- 50/50 split widths (tab/vsplit only)
150149
full_file = true, -- show full file, not just diff hunks (tab/vsplit only)
151150
visible_only = false, -- skip diffs for files not open in any Neovim buffer

bin/apply-edit.lua

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ if replace_all then
2626
local result = {}
2727
local search_start = 1
2828
if old_string == "" then
29-
-- Nothing to replace; write content unchanged
30-
result = { content }
29+
-- Empty old_string: prepend new_string (handles "insert into empty file")
30+
result = { new_string, content }
3131
else
3232
while true do
3333
local s, e = string.find(content, old_string, search_start, true)
@@ -43,7 +43,10 @@ if replace_all then
4343
content = table.concat(result)
4444
else
4545
-- Replace first occurrence only
46-
if old_string ~= "" then
46+
if old_string == "" then
47+
-- Empty old_string: prepend new_string (handles "insert into empty file")
48+
content = new_string .. content
49+
else
4750
local s, e = string.find(content, old_string, 1, true)
4851
if s then
4952
content = content:sub(1, s - 1) .. new_string .. content:sub(e + 1)

bin/core-post-tool.sh

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,14 @@ fi
3333
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)"
3434
FILE_PATH_ESC="$(escape_lua "${FILE_PATH:-}")"
3535

36-
# Only clean up if a diff for THIS file is actually open.
37-
# OpenCode fires all before-hooks before any after-hooks, so the open diff
38-
# may belong to a different file — closing it would kill the wrong preview.
39-
DIFF_OPEN=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"require('code-preview.diff').is_open('${FILE_PATH_ESC}')\")" 2>/dev/null || echo "false")
40-
41-
if [[ "$DIFF_OPEN" == "true" ]]; then
42-
nvim_send "require('code-preview.changes').clear_all()" || true
43-
nvim_send "require('code-preview.diff').close_diff()" || true
44-
if [[ -n "$FILE_PATH" ]]; then
45-
nvim_send "vim.defer_fn(function() pcall(function() require('code-preview.neo_tree').refresh() end) vim.defer_fn(function() pcall(function() require('code-preview.neo_tree').reveal('$FILE_PATH_ESC') end) end, 200) end, 200)" || true
46-
else
47-
nvim_send "vim.defer_fn(function() pcall(function() require('code-preview.neo_tree').refresh() end) end, 200)" || true
48-
fi
36+
# Tell Lua to handle this file's close — tolerates out-of-order post-hooks
37+
# (OpenCode may fire them in a different order than pre-hooks).
38+
if [[ -n "$FILE_PATH" ]]; then
39+
nvim_send "require('code-preview.diff').close_for_file('$FILE_PATH_ESC')" || true
40+
# neo_tree.refresh() is handled inside close_for_file() via vim.schedule()
4941
fi
5042

51-
# Clean up temp files
52-
rm -f "${TMPDIR:-/tmp}/claude-diff-original" "${TMPDIR:-/tmp}/claude-diff-proposed"
43+
# Clean up temp files (both legacy shared paths and per-PID paths)
44+
rm -f "${TMPDIR:-/tmp}"/claude-diff-original* "${TMPDIR:-/tmp}"/claude-diff-proposed*
5345

5446
exit 0

bin/core-pre-tool.sh

Lines changed: 10 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ if [[ -z "${NVIM_SOCKET:-}" ]]; then
3232
fi
3333

3434
TMPDIR="${TMPDIR:-/tmp}"
35-
ORIG_FILE="$TMPDIR/claude-diff-original"
36-
PROP_FILE="$TMPDIR/claude-diff-proposed"
35+
# Use unique temp files per hook invocation so rapid-fire pre-hooks
36+
# (OpenCode fires all before-hooks before any after-hooks) don't clobber
37+
# each other's diff content.
38+
HOOK_ID="$$"
39+
ORIG_FILE="$TMPDIR/claude-diff-original-$HOOK_ID"
40+
PROP_FILE="$TMPDIR/claude-diff-proposed-$HOOK_ID"
3741

3842
# --- Compute original and proposed file content ---
3943

@@ -50,7 +54,7 @@ case "$TOOL_NAME" in
5054
> "$ORIG_FILE"
5155
fi
5256

53-
NVIM_LISTEN_ADDRESS= nvim --headless -l "$SCRIPT_DIR/apply-edit.lua" "$FILE_PATH" "$OLD_STRING" "$NEW_STRING" "$REPLACE_ALL" "$PROP_FILE"
57+
NVIM_LISTEN_ADDRESS= nvim --headless -l "$SCRIPT_DIR/apply-edit.lua" "$FILE_PATH" "$OLD_STRING" "$NEW_STRING" "$REPLACE_ALL" "$PROP_FILE" || true
5458
;;
5559

5660
Write)
@@ -152,12 +156,10 @@ if [[ "$HAS_NVIM" == "true" ]]; then
152156
DISPLAY_ESC="$(escape_lua "$DISPLAY_NAME")"
153157
FILE_PATH_ESC="$(escape_lua "$FILE_PATH")"
154158

155-
# Query config + file visibility from nvim in a single RPC call
159+
# Query config + file visibility from nvim in a single RPC call.
160+
# Neo-tree indicator/reveal is now driven from lua/code-preview/diff.lua
161+
# (inside show_diff), so we only need visibility + permission fields here.
156162
HOOK_CTX=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"require('code-preview').hook_context('${FILE_PATH_ESC}')\")" 2>/dev/null || echo '{}')
157-
# Use explicit conditional: jq's `//` operator treats boolean false like null,
158-
# which would silently convert `reveal = false` into `true`.
159-
NEO_TREE_REVEAL=$(echo "$HOOK_CTX" | jq -r 'if .neo_tree_reveal == false then "false" else "true" end')
160-
NEO_TREE_REVEAL_ROOT=$(echo "$HOOK_CTX" | jq -r '.reveal_root // "cwd"')
161163
VISIBLE_ONLY=$(echo "$HOOK_CTX" | jq -r '.visible_only // false')
162164
FILE_VISIBLE=$(echo "$HOOK_CTX" | jq -r '.file_visible // false')
163165
DEFER_PERMISSIONS=$(echo "$HOOK_CTX" | jq -r 'if .defer_claude_permissions == true then "true" else "false" end')
@@ -169,51 +171,7 @@ if [[ "$HAS_NVIM" == "true" ]]; then
169171
SHOULD_SHOW="0"
170172
fi
171173

172-
# Determine change status for neo-tree indicator
173-
if [[ -f "$FILE_PATH" ]]; then
174-
CHANGE_STATUS="modified"
175-
else
176-
CHANGE_STATUS="created"
177-
fi
178-
179174
if [[ "$SHOULD_SHOW" == "1" ]]; then
180-
nvim_send "require('code-preview.changes').set('$FILE_PATH_ESC', '$CHANGE_STATUS')" || true
181-
182-
# Neo-tree integration (gated by config)
183-
if [[ "$NEO_TREE_REVEAL" == "true" ]]; then
184-
# Resolve the directory neo-tree should root from
185-
REVEAL_DIR=""
186-
if [[ "$NEO_TREE_REVEAL_ROOT" == "git" ]]; then
187-
REVEAL_DIR=$(git -C "$(dirname "$FILE_PATH")" rev-parse --show-toplevel 2>/dev/null || echo "")
188-
REVEAL_DIR="${REVEAL_DIR:-$(dirname "$FILE_PATH")}"
189-
fi
190-
191-
# Resolve reveal target. For modified files the path exists; for created
192-
# files the path (and possibly its parents) don't exist yet, so walk up to
193-
# the nearest existing directory and reveal a sibling file inside it to
194-
# force neo-tree to expand the path before virtual nodes are injected.
195-
if [[ "$CHANGE_STATUS" == "modified" ]]; then
196-
REVEAL_TARGET="$FILE_PATH"
197-
else
198-
REVEAL_PARENT="$(dirname "$FILE_PATH")"
199-
while [[ ! -d "$REVEAL_PARENT" && "$REVEAL_PARENT" != "/" ]]; do
200-
REVEAL_PARENT="$(dirname "$REVEAL_PARENT")"
201-
done
202-
REVEAL_TARGET="$(find "$REVEAL_PARENT" -maxdepth 1 -type f 2>/dev/null | head -1)"
203-
REVEAL_TARGET="${REVEAL_TARGET:-$REVEAL_PARENT}"
204-
fi
205-
REVEAL_TARGET_ESC="$(escape_lua "$REVEAL_TARGET")"
206-
207-
nvim_send "pcall(function() require('code-preview.neo_tree').refresh() end)" || true
208-
209-
if [[ -n "$REVEAL_DIR" ]]; then
210-
REVEAL_DIR_ESC="$(escape_lua "$REVEAL_DIR")"
211-
nvim_send "vim.defer_fn(function() pcall(function() require('code-preview.neo_tree').reveal('$REVEAL_TARGET_ESC', '$REVEAL_DIR_ESC') end) end, 300)" || true
212-
else
213-
nvim_send "vim.defer_fn(function() pcall(function() require('code-preview.neo_tree').reveal('$REVEAL_TARGET_ESC') end) end, 300)" || true
214-
fi
215-
fi
216-
217175
nvim_send "require('code-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC', '$FILE_PATH_ESC')" || true
218176
fi
219177
fi

0 commit comments

Comments
 (0)