-
-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathinit.lua
More file actions
271 lines (236 loc) · 10 KB
/
init.lua
File metadata and controls
271 lines (236 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
local M = {}
-- Module-level config, populated by setup()
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" },
equalize = true,
full_file = true,
visible_only = false, -- only show diffs for files open in a visible nvim window
defer_claude_permissions = false, -- when true, skip permissionDecision and let Claude Code's own settings decide
},
neo_tree = {
enabled = true,
-- reveal = false disables scroll-to-file in the tree. Change indicators
-- (modified/created/deleted icons) still appear — to disable those too,
-- set neo_tree.enabled = false.
reveal = true, -- reveal edited files in neo-tree
reveal_root = "cwd", -- "cwd" (default) or "git" (nearest git root)
refresh_on_change = true,
position = "right",
symbols = {
modified = "",
created = "",
deleted = "",
},
highlights = {
modified = { fg = "#e8a838", bold = true },
created = { fg = "#56c8d8", bold = true },
deleted = { fg = "#e06c75", bold = true, strikethrough = true },
},
},
keys = {
-- Set any entry to false to skip that binding. Set `keys = false` to skip all.
-- <Plug>(CodePreviewCloseAll) is always defined so users can map it themselves.
next_change = "]c", -- buffer-local in inline diff buffers
prev_change = "[c", -- buffer-local in inline diff buffers
close_all = "<leader>dq", -- global; close diff and clear indicators
},
highlights = {
current = {
DiffAdd = { bg = "#4c2e2e" },
DiffDelete = { bg = "#4c2e2e" },
DiffChange = { bg = "#4c3a2e" },
DiffText = { bg = "#5c3030" },
},
proposed = {
DiffAdd = { bg = "#2e4c2e" },
DiffDelete = { bg = "#4c2e2e" },
DiffChange = { bg = "#2e3c4c" },
DiffText = { bg = "#3e5c3e" },
},
inline = {
added = { bg = "#2e4c2e" },
removed = { bg = "#4c2e2e" },
added_text = { bg = "#3a6e3a" },
removed_text = { bg = "#6e3a3a" },
},
},
}
local function deep_merge(base, override)
local result = vim.deepcopy(base)
for k, v in pairs(override) do
if type(v) == "table" and type(result[k]) == "table" then
result[k] = deep_merge(result[k], v)
else
result[k] = v
end
end
return result
end
-- Helper: create a deprecated alias that warns and delegates
local function deprecated_alias(old_name, new_name)
vim.api.nvim_create_user_command(old_name, function()
vim.notify(
"[code-preview] :" .. old_name .. " is deprecated, use :" .. new_name,
vim.log.levels.WARN
)
vim.cmd(new_name)
end, { desc = "(deprecated) Use :" .. new_name .. " instead" })
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()
require("code-preview.backends.claudecode").install()
end, { desc = "Install code-preview PreToolUse/PostToolUse hooks for Claude Code" })
vim.api.nvim_create_user_command("CodePreviewUninstallClaudeCodeHooks", function()
require("code-preview.backends.claudecode").uninstall()
end, { desc = "Uninstall code-preview hooks for Claude Code" })
vim.api.nvim_create_user_command("CodePreviewInstallOpenCodeHooks", function()
require("code-preview.backends.opencode").install()
end, { desc = "Install code-preview plugin for OpenCode" })
vim.api.nvim_create_user_command("CodePreviewUninstallOpenCodeHooks", function()
require("code-preview.backends.opencode").uninstall()
end, { desc = "Uninstall code-preview plugin from OpenCode" })
vim.api.nvim_create_user_command("CodePreviewInstallCopilotCliHooks", function()
require("code-preview.backends.copilot").install()
end, { desc = "Install code-preview hooks for GitHub Copilot CLI" })
vim.api.nvim_create_user_command("CodePreviewUninstallCopilotCliHooks", function()
require("code-preview.backends.copilot").uninstall()
end, { desc = "Uninstall code-preview hooks for GitHub Copilot CLI" })
vim.api.nvim_create_user_command("CodePreviewCloseDiff", function()
require("code-preview.diff").close_diff_and_clear()
end, { desc = "Manually close code-preview diff (use after rejecting a change)" })
vim.api.nvim_create_user_command("CodePreviewStatus", function()
M.status()
end, { desc = "Show code-preview status" })
vim.api.nvim_create_user_command("CodePreviewToggleVisibleOnly", function()
M.config.diff.visible_only = not M.config.diff.visible_only
vim.notify(
"code-preview: visible_only = " .. tostring(M.config.diff.visible_only),
vim.log.levels.INFO,
{ title = "code-preview" }
)
end, { desc = "Toggle visible_only — show diffs only for open buffers vs all files" })
-- ── Deprecated aliases (remove after one release cycle) ───────
deprecated_alias("ClaudePreviewInstallHooks", "CodePreviewInstallClaudeCodeHooks")
deprecated_alias("ClaudePreviewUninstallHooks", "CodePreviewUninstallClaudeCodeHooks")
deprecated_alias("ClaudePreviewCloseDiff", "CodePreviewCloseDiff")
deprecated_alias("ClaudePreviewStatus", "CodePreviewStatus")
deprecated_alias("ClaudePreviewToggleVisibleOnly", "CodePreviewToggleVisibleOnly")
-- Neo-tree integration (soft dependency)
if M.config.neo_tree.enabled then
require("code-preview.neo_tree").setup(M.config)
end
-- <Plug> mapping is always defined so users can bind it themselves
-- regardless of the `keys` config (e.g. `keys = false` to disable defaults).
vim.keymap.set("n", "<Plug>(CodePreviewCloseAll)", function()
require("code-preview.diff").close_diff_and_clear()
end, { desc = "Close code-preview diff" })
if M.config.keys ~= false then
local close_all = M.config.keys and M.config.keys.close_all
if close_all then
vim.keymap.set("n", close_all, "<Plug>(CodePreviewCloseAll)",
{ desc = "Close code-preview diff" })
end
end
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: { neo_tree_reveal, reveal_root, visible_only, file_visible }
function M.hook_context(file_path)
local cfg = M.config
local neo_tree_reveal = (cfg.neo_tree.enabled and cfg.neo_tree.reveal) and true or false
local reveal_root = cfg.neo_tree.reveal_root or "cwd"
local visible_only = cfg.diff.visible_only and true or false
local defer_claude_permissions = cfg.diff.defer_claude_permissions and true or false
local file_visible = false
if visible_only and file_path ~= "" then
-- fs_realpath returns the filesystem's canonical form, so case-insensitive
-- volumes (e.g. default APFS) normalize automatically without per-OS logic.
local target = vim.uv.fs_realpath(file_path) or vim.fn.fnamemodify(file_path, ":p")
for _, w in ipairs(vim.api.nvim_list_wins()) do
local b = vim.api.nvim_win_get_buf(w)
local raw = vim.api.nvim_buf_get_name(b)
if raw ~= "" then
local name = vim.uv.fs_realpath(raw) or vim.fn.fnamemodify(raw, ":p")
if name == target then
file_visible = true
break
end
end
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
function M.status()
local lines = { "code-preview.nvim status", string.rep("─", 40) }
-- Socket
local socket = vim.env.NVIM_LISTEN_ADDRESS or ""
if socket == "" then
socket = vim.v.servername or ""
end
if socket ~= "" then
table.insert(lines, "Neovim socket : " .. socket)
else
table.insert(lines, "Neovim socket : not found")
end
-- jq dependency
local jq_ok = vim.fn.executable("jq") == 1
table.insert(lines, "jq : " .. (jq_ok and "found" or "MISSING"))
-- Diff tab open?
local diff = require("code-preview.diff")
table.insert(lines, "Diff tab : " .. (diff.is_open() and "open" or "closed"))
-- Backends
table.insert(lines, "")
table.insert(lines, "Backends:")
-- Claude Code
local claude_ok = false
local settings_path = vim.fn.getcwd() .. "/.claude/settings.local.json"
local f = io.open(settings_path, "r")
if f then
local content = f:read("*a")
f:close()
claude_ok = content:find("code-preview", 1, true) ~= nil
or content:find("claude-preview", 1, true) ~= nil
end
if claude_ok then
table.insert(lines, " Claude Code : installed")
else
table.insert(lines, " Claude Code : not installed -> :CodePreviewInstallClaudeCodeHooks")
end
-- OpenCode
local opencode_ok = vim.fn.filereadable(vim.fn.getcwd() .. "/.opencode/plugins/index.ts") == 1
if opencode_ok then
table.insert(lines, " OpenCode : installed")
else
table.insert(lines, " OpenCode : not installed -> :CodePreviewInstallOpenCodeHooks")
end
-- Copilot CLI — check file contents, not just existence, so a user-authored
-- hook file that happens to share the name isn't reported as "installed".
local copilot_ok = require("code-preview.backends.copilot").is_our_config(
vim.fn.getcwd() .. "/.github/hooks/code-preview.json"
)
if copilot_ok then
table.insert(lines, " Copilot CLI : installed")
else
table.insert(lines, " Copilot CLI : not installed -> :CodePreviewInstallCopilotCliHooks")
end
vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO, { title = "code-preview" })
end
return M