Skip to content

Commit 66c2720

Browse files
Cannon07claude
andcommitted
feat: add configurable debug logging
Add opt-in debug logging following Neovim plugin conventions. Lua and shell scripts write to the same log file for unified debugging. - Create lua/code-preview/log.lua (DEBUG/INFO to file, WARN/ERROR also via vim.notify) - Add `debug = false` config option, log file at stdpath("log")/code-preview.log - Wire logging into diff.lua, neo_tree.lua, and both shell hook scripts - Expose debug flag and log path in hook_context() for shell scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 175f231 commit 66c2720

6 files changed

Lines changed: 129 additions & 2 deletions

File tree

bin/core-post-tool.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)"
2222
source "$SCRIPT_DIR/nvim-socket.sh" "$CWD" 2>/dev/null
2323
source "$SCRIPT_DIR/nvim-send.sh"
2424

25+
# Set up logging — query debug config from nvim
26+
log_post() { :; }
27+
if [[ -n "${NVIM_SOCKET:-}" ]]; then
28+
_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 '{}')
29+
_POST_DEBUG=$(echo "$_POST_CTX" | jq -r '.debug // false')
30+
_POST_LOG_FILE=$(echo "$_POST_CTX" | jq -r '.log_file // ""')
31+
if [[ "$_POST_DEBUG" == "true" && -n "$_POST_LOG_FILE" ]]; then
32+
log_post() { printf '[%s] [INFO] core-post-tool.sh: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$_POST_LOG_FILE"; }
33+
fi
34+
fi
35+
36+
log_post "tool=$TOOL_NAME"
37+
2538
# For Bash tool (rm detection), only clear deletion markers — don't touch edit markers or diff tab
2639
if [[ "$TOOL_NAME" == "Bash" ]]; then
2740
nvim_send "require('code-preview.changes').clear_by_status('deleted')" || true
@@ -36,6 +49,7 @@ FILE_PATH_ESC="$(escape_lua "${FILE_PATH:-}")"
3649
# Tell Lua to handle this file's close — tolerates out-of-order post-hooks
3750
# (OpenCode may fire them in a different order than pre-hooks).
3851
if [[ -n "$FILE_PATH" ]]; then
52+
log_post "closing diff for file=$FILE_PATH"
3953
nvim_send "require('code-preview.diff').close_for_file('$FILE_PATH_ESC')" || true
4054
# neo_tree.refresh() is handled inside close_for_file() via vim.schedule()
4155
fi

bin/core-pre-tool.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ if [[ -z "${NVIM_SOCKET:-}" ]]; then
3131
HAS_NVIM=false
3232
fi
3333

34+
# Set up logging early so all code paths can use it
35+
log_pre() { :; }
36+
if [[ "$HAS_NVIM" == "true" ]]; then
37+
_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 '{}')
38+
_PRE_DEBUG=$(echo "$_PRE_CTX" | jq -r '.debug // false')
39+
_PRE_LOG_FILE=$(echo "$_PRE_CTX" | jq -r '.log_file // ""')
40+
if [[ "$_PRE_DEBUG" == "true" && -n "$_PRE_LOG_FILE" ]]; then
41+
log_pre() { printf '[%s] [INFO] core-pre-tool.sh: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$_PRE_LOG_FILE"; }
42+
fi
43+
fi
44+
45+
log_pre "tool=$TOOL_NAME has_nvim=$HAS_NVIM"
46+
3447
TMPDIR="${TMPDIR:-/tmp}"
3548
# Use unique temp files per hook invocation so rapid-fire pre-hooks
3649
# (OpenCode fires all before-hooks before any after-hooks) don't clobber
@@ -164,14 +177,18 @@ if [[ "$HAS_NVIM" == "true" ]]; then
164177
FILE_VISIBLE=$(echo "$HOOK_CTX" | jq -r '.file_visible // false')
165178
DEFER_PERMISSIONS=$(echo "$HOOK_CTX" | jq -r 'if .defer_claude_permissions == true then "true" else "false" end')
166179

180+
log_pre "file=$FILE_PATH visible_only=$VISIBLE_ONLY file_visible=$FILE_VISIBLE"
181+
167182
# Decide whether to show the diff — skip nvim UI entirely when visible_only
168183
# is on and the file isn't in any visible window.
169184
SHOULD_SHOW="1"
170185
if [[ "$VISIBLE_ONLY" == "true" && "$FILE_VISIBLE" != "true" ]]; then
171186
SHOULD_SHOW="0"
187+
log_pre "skipping diff: visible_only=true, file not visible"
172188
fi
173189

174190
if [[ "$SHOULD_SHOW" == "1" ]]; then
191+
log_pre "sending diff to nvim (layout via config)"
175192
nvim_send "require('code-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC', '$FILE_PATH_ESC')" || true
176193
fi
177194
fi

lua/code-preview/diff.lua

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
local M = {}
22

3+
local log = require("code-preview.log")
4+
35
-- Active diffs keyed by absolute file path.
46
-- Each entry: { tab, bufs, augroup, inline_win }
57
local active_diffs = {}
@@ -32,6 +34,7 @@ local function mark_change_and_reveal(abs_file_path)
3234
end
3335

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

@@ -371,16 +374,21 @@ end
371374

372375
function M.show_diff(original_path, proposed_path, real_file_path, abs_file_path)
373376
local file_key = abs_file_path or real_file_path
377+
local cfg = require("code-preview").config
378+
log.info(log.fmt("show_diff: file=%s layout=%s active=%d",
379+
file_key or "nil",
380+
(cfg.diff and cfg.diff.layout) or "tab",
381+
active_count()))
382+
374383
-- If a diff for this SAME file is already open, close it first (re-edit)
375384
if file_key and active_diffs[file_key] then
385+
log.debug(log.fmt("show_diff: re-edit detected, closing existing diff for %s", file_key))
376386
M.close_for_file(file_key)
377387
end
378388

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

382-
local cfg = require("code-preview").config
383-
384392
-- Inline layout
385393
if cfg.diff.layout == "inline" then
386394
local result = show_inline_diff(original_path, proposed_path, real_file_path, cfg)
@@ -478,9 +486,12 @@ end
478486
function M.close_for_file(file_path)
479487
local entry = active_diffs[file_path]
480488
if not entry then
489+
log.debug(log.fmt("close_for_file: no active diff for %s, skipping", file_path))
481490
return
482491
end
483492

493+
log.info(log.fmt("close_for_file: closing diff for %s (remaining=%d)", file_path, active_count() - 1))
494+
484495
-- Clear neo-tree indicator (refresh is deferred until after the tab is closed
485496
-- to avoid neo-tree walking a stale tabpage id)
486497
pcall(function() require("code-preview.changes").clear(file_path) end)
@@ -545,6 +556,7 @@ end
545556

546557
-- Close ALL diffs and clear neo-tree indicators (for manual close via <leader>dq)
547558
function M.close_diff_and_clear()
559+
log.info(log.fmt("close_diff_and_clear: closing all diffs (count=%d)", active_count()))
548560
-- Collect keys first to avoid modifying table during iteration
549561
local files = {}
550562
for file_path, _ in pairs(active_diffs) do

lua/code-preview/init.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local M = {}
44
M.config = {}
55

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

84+
-- Initialise logging
85+
require("code-preview.log").init({ debug = M.config.debug })
86+
8387
-- ── New commands ──────────────────────────────────────────────
8488

8589
vim.api.nvim_create_user_command("CodePreviewInstallClaudeCodeHooks", function()
@@ -162,12 +166,16 @@ function M.hook_context(file_path)
162166
end
163167
end
164168

169+
local log = require("code-preview.log")
170+
165171
return vim.json.encode({
166172
neo_tree_reveal = neo_tree_reveal,
167173
reveal_root = reveal_root,
168174
visible_only = visible_only,
169175
file_visible = file_visible,
170176
defer_claude_permissions = defer_claude_permissions,
177+
debug = log.is_enabled(),
178+
log_file = log.get_log_path() or "",
171179
})
172180
end
173181

lua/code-preview/log.lua

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
--- code-preview.nvim — Logging module
2+
---
3+
--- Opt-in debug logging following Neovim plugin conventions.
4+
--- - WARN/ERROR: shown to user via vim.notify()
5+
--- - DEBUG/INFO: written to log file only (when enabled)
6+
--- - Log file: vim.fn.stdpath("log") .. "/code-preview.log"
7+
8+
local M = {}
9+
10+
local log_file_path = nil
11+
local enabled = false
12+
13+
--- Initialise logging. Called once from setup().
14+
--- @param opts { debug: boolean }
15+
function M.init(opts)
16+
enabled = opts and opts.debug or false
17+
if enabled then
18+
log_file_path = vim.fn.stdpath("log") .. "/code-preview.log"
19+
end
20+
end
21+
22+
--- Write a line to the log file. No-op when debug is disabled.
23+
--- @param level string "DEBUG"|"INFO"|"WARN"|"ERROR"
24+
--- @param msg string
25+
local function write(level, msg)
26+
if not enabled or not log_file_path then
27+
return
28+
end
29+
local f = io.open(log_file_path, "a")
30+
if not f then
31+
return
32+
end
33+
f:write(string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), level, msg))
34+
f:close()
35+
end
36+
37+
function M.debug(msg) write("DEBUG", msg) end
38+
function M.info(msg) write("INFO", msg) end
39+
40+
function M.warn(msg)
41+
write("WARN", msg)
42+
vim.notify("[code-preview] " .. msg, vim.log.levels.WARN)
43+
end
44+
45+
function M.error(msg)
46+
write("ERROR", msg)
47+
vim.notify("[code-preview] " .. msg, vim.log.levels.ERROR)
48+
end
49+
50+
--- Format helper for structured messages.
51+
--- @param template string format string
52+
--- @param ... any format arguments
53+
--- @return string
54+
function M.fmt(template, ...)
55+
return string.format(template, ...)
56+
end
57+
58+
--- Check whether debug logging is enabled.
59+
--- @return boolean
60+
function M.is_enabled()
61+
return enabled
62+
end
63+
64+
--- Return the log file path (for shell scripts via hook_context).
65+
--- @return string|nil
66+
function M.get_log_path()
67+
return log_file_path
68+
end
69+
70+
return M

lua/code-preview/neo_tree.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
local M = {}
22

33
local changes = require("code-preview.changes")
4+
local log = require("code-preview.log")
45

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

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

277279
local ok, neo_tree_events = pcall(require, "neo-tree.events")
278280
if not ok then
281+
log.debug("neo_tree.setup: neo-tree not found, skipping integration")
279282
return
280283
end
281284
has_neo_tree = true
282285
setup_done = true
286+
log.info("neo_tree.setup: neo-tree integration enabled")
283287

284288
local symbols = cfg.neo_tree.symbols
285289
local highlights = cfg.neo_tree.highlights
@@ -340,6 +344,7 @@ function M.refresh()
340344
if not has_neo_tree then
341345
return
342346
end
347+
log.debug("neo_tree.refresh: triggering filesystem refresh")
343348
pcall(function()
344349
require("neo-tree.sources.manager").refresh("filesystem")
345350
end)
@@ -349,6 +354,7 @@ function M.reveal(filepath, dir)
349354
if not has_neo_tree then
350355
return
351356
end
357+
log.debug(log.fmt("neo_tree.reveal: %s (dir=%s)", filepath, dir or "nil"))
352358
pcall(function()
353359
local cfg = require("code-preview").config
354360
local position = cfg.neo_tree.position or "right"

0 commit comments

Comments
 (0)