Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions bin/core-post-tool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)"
source "$SCRIPT_DIR/nvim-socket.sh" "$CWD" 2>/dev/null
source "$SCRIPT_DIR/nvim-send.sh"

# Set up logging — query debug config from nvim
log_post() { :; }
if [[ -n "${NVIM_SOCKET:-}" ]]; then
_POST_CTX=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"vim.json.encode({debug=require('code-preview.log').is_enabled(),log_file=require('code-preview.log').get_log_path() or ''})\")" 2>/dev/null || echo '{}')
_POST_DEBUG=$(echo "$_POST_CTX" | jq -r '.debug // false')
_POST_LOG_FILE=$(echo "$_POST_CTX" | jq -r '.log_file // ""')
if [[ "$_POST_DEBUG" == "true" && -n "$_POST_LOG_FILE" ]]; then
log_post() { printf '[%s] [INFO] core-post-tool.sh: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$_POST_LOG_FILE"; }
fi
fi

log_post "tool=$TOOL_NAME"

# For Bash tool (rm detection), only clear deletion markers — don't touch edit markers or diff tab
if [[ "$TOOL_NAME" == "Bash" ]]; then
nvim_send "require('code-preview.changes').clear_by_status('deleted')" || true
Expand All @@ -36,6 +49,7 @@ FILE_PATH_ESC="$(escape_lua "${FILE_PATH:-}")"
# Tell Lua to handle this file's close — tolerates out-of-order post-hooks
# (OpenCode may fire them in a different order than pre-hooks).
if [[ -n "$FILE_PATH" ]]; then
log_post "closing diff for file=$FILE_PATH"
nvim_send "require('code-preview.diff').close_for_file('$FILE_PATH_ESC')" || true
# neo_tree.refresh() is handled inside close_for_file() via vim.schedule()
fi
Expand Down
17 changes: 17 additions & 0 deletions bin/core-pre-tool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ if [[ -z "${NVIM_SOCKET:-}" ]]; then
HAS_NVIM=false
fi

# Set up logging early so all code paths can use it
log_pre() { :; }
if [[ "$HAS_NVIM" == "true" ]]; then
_PRE_CTX=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"vim.json.encode({debug=require('code-preview.log').is_enabled(),log_file=require('code-preview.log').get_log_path() or ''})\")" 2>/dev/null || echo '{}')
_PRE_DEBUG=$(echo "$_PRE_CTX" | jq -r '.debug // false')
_PRE_LOG_FILE=$(echo "$_PRE_CTX" | jq -r '.log_file // ""')
if [[ "$_PRE_DEBUG" == "true" && -n "$_PRE_LOG_FILE" ]]; then
log_pre() { printf '[%s] [INFO] core-pre-tool.sh: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$_PRE_LOG_FILE"; }
fi
fi

log_pre "tool=$TOOL_NAME has_nvim=$HAS_NVIM"

TMPDIR="${TMPDIR:-/tmp}"
# Use unique temp files per hook invocation so rapid-fire pre-hooks
# (OpenCode fires all before-hooks before any after-hooks) don't clobber
Expand Down Expand Up @@ -164,14 +177,18 @@ if [[ "$HAS_NVIM" == "true" ]]; then
FILE_VISIBLE=$(echo "$HOOK_CTX" | jq -r '.file_visible // false')
DEFER_PERMISSIONS=$(echo "$HOOK_CTX" | jq -r 'if .defer_claude_permissions == true then "true" else "false" end')

log_pre "file=$FILE_PATH visible_only=$VISIBLE_ONLY file_visible=$FILE_VISIBLE"

# Decide whether to show the diff — skip nvim UI entirely when visible_only
# is on and the file isn't in any visible window.
SHOULD_SHOW="1"
if [[ "$VISIBLE_ONLY" == "true" && "$FILE_VISIBLE" != "true" ]]; then
SHOULD_SHOW="0"
log_pre "skipping diff: visible_only=true, file not visible"
fi

if [[ "$SHOULD_SHOW" == "1" ]]; then
log_pre "sending diff to nvim (layout via config)"
nvim_send "require('code-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC', '$FILE_PATH_ESC')" || true
fi
fi
Expand Down
16 changes: 14 additions & 2 deletions lua/code-preview/diff.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
local M = {}

local log = require("code-preview.log")

-- Active diffs keyed by absolute file path.
-- Each entry: { tab, bufs, augroup, inline_win }
local active_diffs = {}
Expand Down Expand Up @@ -32,6 +34,7 @@ local function mark_change_and_reveal(abs_file_path)
end

local status = vim.loop.fs_stat(abs_file_path) and "modified" or "created"
log.debug(log.fmt("mark_change_and_reveal: %s → %s", abs_file_path, status))
pcall(function() require("code-preview.changes").set(abs_file_path, status) end)
pcall(function() require("code-preview.neo_tree").refresh() end)

Expand Down Expand Up @@ -371,16 +374,21 @@ end

function M.show_diff(original_path, proposed_path, real_file_path, abs_file_path)
local file_key = abs_file_path or real_file_path
local cfg = require("code-preview").config
log.info(log.fmt("show_diff: file=%s layout=%s active=%d",
file_key or "nil",
(cfg.diff and cfg.diff.layout) or "tab",
active_count()))

-- If a diff for this SAME file is already open, close it first (re-edit)
if file_key and active_diffs[file_key] then
log.debug(log.fmt("show_diff: re-edit detected, closing existing diff for %s", file_key))
M.close_for_file(file_key)
end

-- Set the neo-tree indicator + reveal
mark_change_and_reveal(abs_file_path)

local cfg = require("code-preview").config

-- Inline layout
if cfg.diff.layout == "inline" then
local result = show_inline_diff(original_path, proposed_path, real_file_path, cfg)
Expand Down Expand Up @@ -478,9 +486,12 @@ end
function M.close_for_file(file_path)
local entry = active_diffs[file_path]
if not entry then
log.debug(log.fmt("close_for_file: no active diff for %s, skipping", file_path))
return
end

log.info(log.fmt("close_for_file: closing diff for %s (remaining=%d)", file_path, active_count() - 1))

-- Clear neo-tree indicator (refresh is deferred until after the tab is closed
-- to avoid neo-tree walking a stale tabpage id)
pcall(function() require("code-preview.changes").clear(file_path) end)
Expand Down Expand Up @@ -545,6 +556,7 @@ end

-- Close ALL diffs and clear neo-tree indicators (for manual close via <leader>dq)
function M.close_diff_and_clear()
log.info(log.fmt("close_diff_and_clear: closing all diffs (count=%d)", active_count()))
-- Collect keys first to avoid modifying table during iteration
local files = {}
for file_path, _ in pairs(active_diffs) do
Expand Down
8 changes: 8 additions & 0 deletions lua/code-preview/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local M = {}
M.config = {}

local default_config = {
debug = false, -- enable debug logging to stdpath("log")/code-preview.log
diff = {
layout = "tab", -- "tab", "vsplit", or "inline"
labels = { current = "CURRENT", proposed = "PROPOSED" },
Expand Down Expand Up @@ -80,6 +81,9 @@ end
function M.setup(user_config)
M.config = deep_merge(default_config, user_config or {})

-- Initialise logging
require("code-preview.log").init({ debug = M.config.debug })

-- ── New commands ──────────────────────────────────────────────

vim.api.nvim_create_user_command("CodePreviewInstallClaudeCodeHooks", function()
Expand Down Expand Up @@ -162,12 +166,16 @@ function M.hook_context(file_path)
end
end

local log = require("code-preview.log")

return vim.json.encode({
neo_tree_reveal = neo_tree_reveal,
reveal_root = reveal_root,
visible_only = visible_only,
file_visible = file_visible,
defer_claude_permissions = defer_claude_permissions,
debug = log.is_enabled(),
log_file = log.get_log_path() or "",
})
end

Expand Down
70 changes: 70 additions & 0 deletions lua/code-preview/log.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
--- code-preview.nvim — Logging module
---
--- Opt-in debug logging following Neovim plugin conventions.
--- - WARN/ERROR: shown to user via vim.notify()
--- - DEBUG/INFO: written to log file only (when enabled)
--- - Log file: vim.fn.stdpath("log") .. "/code-preview.log"

local M = {}

local log_file_path = nil
local enabled = false

--- Initialise logging. Called once from setup().
--- @param opts { debug: boolean }
function M.init(opts)
enabled = opts and opts.debug or false
if enabled then
log_file_path = vim.fn.stdpath("log") .. "/code-preview.log"
end
end

--- Write a line to the log file. No-op when debug is disabled.
--- @param level string "DEBUG"|"INFO"|"WARN"|"ERROR"
--- @param msg string
local function write(level, msg)
if not enabled or not log_file_path then
return
end
local f = io.open(log_file_path, "a")
if not f then
return
end
f:write(string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), level, msg))
f:close()
end

function M.debug(msg) write("DEBUG", msg) end
function M.info(msg) write("INFO", msg) end

function M.warn(msg)
write("WARN", msg)
vim.notify("[code-preview] " .. msg, vim.log.levels.WARN)
end

function M.error(msg)
write("ERROR", msg)
vim.notify("[code-preview] " .. msg, vim.log.levels.ERROR)
end

--- Format helper for structured messages.
--- @param template string format string
--- @param ... any format arguments
--- @return string
function M.fmt(template, ...)
return string.format(template, ...)
end

--- Check whether debug logging is enabled.
--- @return boolean
function M.is_enabled()
return enabled
end

--- Return the log file path (for shell scripts via hook_context).
--- @return string|nil
function M.get_log_path()
return log_file_path
end

return M
6 changes: 6 additions & 0 deletions lua/code-preview/neo_tree.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local M = {}

local changes = require("code-preview.changes")
local log = require("code-preview.log")

-- Guard: all neo-tree interaction goes through pcall
local has_neo_tree = false
Expand Down Expand Up @@ -260,6 +261,7 @@ local function inject_virtual_nodes(state, pending)
pcall(function()
state.tree:add_node(file_node, parent_path)
virtual_nodes[filepath] = true
log.debug(log.fmt("neo_tree: injected virtual node for %s", filepath))
end)
changed = true

Expand All @@ -276,10 +278,12 @@ function M.setup(cfg)

local ok, neo_tree_events = pcall(require, "neo-tree.events")
if not ok then
log.debug("neo_tree.setup: neo-tree not found, skipping integration")
return
end
has_neo_tree = true
setup_done = true
log.info("neo_tree.setup: neo-tree integration enabled")

local symbols = cfg.neo_tree.symbols
local highlights = cfg.neo_tree.highlights
Expand Down Expand Up @@ -340,6 +344,7 @@ function M.refresh()
if not has_neo_tree then
return
end
log.debug("neo_tree.refresh: triggering filesystem refresh")
pcall(function()
require("neo-tree.sources.manager").refresh("filesystem")
end)
Expand All @@ -349,6 +354,7 @@ function M.reveal(filepath, dir)
if not has_neo_tree then
return
end
log.debug(log.fmt("neo_tree.reveal: %s (dir=%s)", filepath, dir or "nil"))
pcall(function()
local cfg = require("code-preview").config
local position = cfg.neo_tree.position or "right"
Expand Down
Loading