diff --git a/bin/claude-preview-diff.sh b/bin/claude-preview-diff.sh index 62b28a5..ac12e47 100755 --- a/bin/claude-preview-diff.sh +++ b/bin/claude-preview-diff.sh @@ -138,43 +138,40 @@ if [[ "$HAS_NVIM" == "true" ]]; then DISPLAY_ESC="$(escape_lua "$DISPLAY_NAME")" FILE_PATH_ESC="$(escape_lua "$FILE_PATH")" + # Query config + file visibility from nvim in a single RPC call + HOOK_CTX=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"require('claude-preview').hook_context('${FILE_PATH_ESC}')\")" 2>/dev/null || echo '{}') + VISIBLE_ONLY=$(echo "$HOOK_CTX" | jq -r '.visible_only // false') + FILE_VISIBLE=$(echo "$HOOK_CTX" | jq -r '.file_visible // false') + + # Decide whether to show the diff + SHOULD_SHOW="1" + if [[ "$VISIBLE_ONLY" == "true" && "$FILE_VISIBLE" != "true" ]]; then + SHOULD_SHOW="0" + fi + # Determine change status for neo-tree indicator - # Check if the actual file exists on disk (not the temp copy, which is always created) if [[ -f "$FILE_PATH" ]]; then CHANGE_STATUS="modified" else CHANGE_STATUS="created" fi - nvim_send "require('claude-preview.changes').set('$FILE_PATH_ESC', '$CHANGE_STATUS')" || true - nvim_send "pcall(function() require('claude-preview.neo_tree').refresh() end)" || true - # Reveal the file in neo-tree: for modified files reveal the file itself, - # for created files reveal the nearest existing parent directory - if [[ "$CHANGE_STATUS" == "modified" ]]; then + if [[ "$SHOULD_SHOW" == "1" ]]; then + nvim_send "require('claude-preview.changes').set('$FILE_PATH_ESC', '$CHANGE_STATUS')" || true + nvim_send "pcall(function() require('claude-preview.neo_tree').refresh() end)" || true nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$FILE_PATH_ESC') end) end, 300)" || true - else - # Walk up to find the nearest existing parent directory - REVEAL_DIR="$(dirname "$FILE_PATH")" - while [[ ! -d "$REVEAL_DIR" && "$REVEAL_DIR" != "/" ]]; do - REVEAL_DIR="$(dirname "$REVEAL_DIR")" - done - # Reveal a file inside the parent dir to force neo-tree to expand it - REVEAL_TARGET="$(find "$REVEAL_DIR" -maxdepth 1 -type f | head -1)" - if [[ -z "$REVEAL_TARGET" ]]; then - REVEAL_TARGET="$REVEAL_DIR" - fi - REVEAL_TARGET_ESC="$(escape_lua "$REVEAL_TARGET")" - nvim_send "vim.defer_fn(function() pcall(function() require('claude-preview.neo_tree').reveal('$REVEAL_TARGET_ESC') end) end, 300)" || true + nvim_send "require('claude-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC')" || true fi - nvim_send "require('claude-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC')" || true fi -# --- Always ask for user confirmation --- +# --- Permission decision --- -if [[ "$HAS_NVIM" == "true" ]]; then +if [[ "$HAS_NVIM" == "true" && "$SHOULD_SHOW" == "0" ]]; then + printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"File not visible in Neovim, auto-approved"}}\n' +elif [[ "$HAS_NVIM" == "true" ]]; then REASON="Diff preview sent to Neovim. Review before accepting." + printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"%s"}}\n' "$REASON" else REASON="Neovim not running. Review the diff in CLI before accepting." + printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"%s"}}\n' "$REASON" fi - -printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"%s"}}\n' "$REASON" diff --git a/lua/claude-preview/init.lua b/lua/claude-preview/init.lua index fc292e9..e64a112 100644 --- a/lua/claude-preview/init.lua +++ b/lua/claude-preview/init.lua @@ -10,6 +10,7 @@ local default_config = { auto_close = true, equalize = true, full_file = true, + visible_only = false, -- only show diffs for files open in a visible nvim window }, neo_tree = { enabled = true, @@ -87,6 +88,15 @@ function M.setup(user_config) M.status() end, { desc = "Show claude-preview status" }) + vim.api.nvim_create_user_command("ClaudePreviewToggleVisibleOnly", function() + M.config.diff.visible_only = not M.config.diff.visible_only + vim.notify( + "claude-preview: visible_only = " .. tostring(M.config.diff.visible_only), + vim.log.levels.INFO, + { title = "claude-preview" } + ) + end, { desc = "Toggle visible_only — show diffs only for open buffers vs all files" }) + -- Neo-tree integration (soft dependency) if M.config.neo_tree.enabled then require("claude-preview.neo_tree").setup(M.config) @@ -97,6 +107,38 @@ function M.setup(user_config) end, { desc = "Close claude-preview diff" }) end +--- Query hook context for the PreToolUse shell script. +--- Returns a JSON string with config + file visibility in a single RPC call. +--- @param file_path string absolute path of the file being edited +--- @return string JSON: { visible_only, file_visible } +function M.hook_context(file_path) + local cfg = M.config + local visible_only = cfg.diff.visible_only and true or false + + local file_visible = false + if visible_only and file_path ~= "" then + local is_mac = vim.fn.has("mac") == 1 + local target = vim.uv.fs_realpath(file_path) or vim.fn.fnamemodify(file_path, ":p") + if is_mac then target = target:lower() end + + for _, w in ipairs(vim.api.nvim_list_wins()) do + local b = vim.api.nvim_win_get_buf(w) + local name = vim.uv.fs_realpath(vim.api.nvim_buf_get_name(b)) + or vim.fn.fnamemodify(vim.api.nvim_buf_get_name(b), ":p") + if is_mac then name = name:lower() end + if name == target then + file_visible = true + break + end + end + end + + return vim.json.encode({ + visible_only = visible_only, + file_visible = file_visible, + }) +end + function M.status() local lines = { "claude-preview.nvim status", string.rep("─", 40) }