Skip to content

Commit 13d12e2

Browse files
committed
refactor: replace close_buffer_by_name with close_tab tool and apply code formatting
Change-Id: I6c0ef979b3c5ac8328e398d827d47dfd642c8274 Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent 6eb14dc commit 13d12e2

File tree

11 files changed

+549
-174
lines changed

11 files changed

+549
-174
lines changed

findings.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,40 @@ The lock file contains a JSON object with the following structure:
237237

238238
The extension registers various tools with the MCP server:
239239

240-
1. **openDiff**: Open a diff view between two files
240+
1. **openDiff**: Open a diff view between two files and wait for user action
241241

242242
- Parameters:
243243
- `old_file_path`: Path to the original file (REQUIRED)
244244
- `new_file_path`: Path to the new file (REQUIRED)
245245
- `new_file_contents`: Contents of the new file (REQUIRED)
246246
- `tab_name`: Name for the diff tab (REQUIRED)
247-
- Returns: Information about diff acceptance or rejection
247+
- Behavior: This tool is **blocking** - it opens the diff view and waits for user interaction before returning
248+
- Returns: One of three possible responses based on user action:
249+
250+
```json
251+
// User saved the file (accepted changes)
252+
{
253+
"content": [
254+
{ "type": "text", "text": "FILE_SAVED" },
255+
{ "type": "text", "text": "final file contents" }
256+
]
257+
}
258+
259+
// User closed the diff tab or explicitly rejected
260+
{
261+
"content": [
262+
{ "type": "text", "text": "DIFF_REJECTED" },
263+
{ "type": "text", "text": "tab_name" }
264+
]
265+
}
266+
```
267+
268+
- Implementation Notes:
269+
- Creates temporary file providers for both old and new files
270+
- Monitors for tab close events, file save events, and diff acceptance/rejection
271+
- Automatically closes any existing Claude Code diff tabs with the same name
272+
- Uses `Promise.race()` to wait for the first of: tab closed, diff accepted, or file saved
273+
- If autoSave is disabled, also waits for manual save events
248274

249275
2. **getDiagnostics**: Get language diagnostics from VS Code
250276

lua/claudecode/diff.lua

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,27 +120,39 @@ function M._cleanup_temp_file(tmp_file)
120120
if vim.fs and type(vim.fs.remove) == "function" then
121121
local ok_file, err_file = pcall(vim.fs.remove, tmp_file)
122122
if not ok_file then
123-
vim.notify("ClaudeCode: Error removing temp file " .. tmp_file .. ": " .. tostring(err_file), vim.log.levels.WARN)
123+
vim.notify(
124+
"ClaudeCode: Error removing temp file " .. tmp_file .. ": " .. tostring(err_file),
125+
vim.log.levels.WARN
126+
)
124127
end
125128

126129
local ok_dir, err_dir = pcall(vim.fs.remove, tmp_dir)
127130
if not ok_dir then
128-
vim.notify("ClaudeCode: Error removing temp directory " .. tmp_dir .. ": " .. tostring(err_dir), vim.log.levels.INFO)
131+
vim.notify(
132+
"ClaudeCode: Error removing temp directory " .. tmp_dir .. ": " .. tostring(err_dir),
133+
vim.log.levels.INFO
134+
)
129135
end
130136
else
131137
local reason = "vim.fs.remove is not a function"
132138
if not vim.fs then
133139
reason = "vim.fs is nil"
134140
end
135141
vim.notify(
136-
"ClaudeCode: Cannot perform standard cleanup: " .. reason .. ". Affected file: " .. tmp_file ..
137-
". Please check your Neovim setup or report this issue.",
142+
"ClaudeCode: Cannot perform standard cleanup: "
143+
.. reason
144+
.. ". Affected file: "
145+
.. tmp_file
146+
.. ". Please check your Neovim setup or report this issue.",
138147
vim.log.levels.ERROR
139148
)
140149
-- Fallback to os.remove for the file.
141150
local os_ok, os_err = pcall(os.remove, tmp_file)
142151
if not os_ok then
143-
vim.notify("ClaudeCode: Fallback os.remove also failed for file " .. tmp_file .. ": " .. tostring(os_err), vim.log.levels.ERROR)
152+
vim.notify(
153+
"ClaudeCode: Fallback os.remove also failed for file " .. tmp_file .. ": " .. tostring(os_err),
154+
vim.log.levels.ERROR
155+
)
144156
end
145157
end
146158
end

lua/claudecode/server/init.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ function M.register_handlers()
208208
end,
209209

210210
["tools/call"] = function(client, params)
211+
logger.debug(
212+
"server",
213+
"Received tools/call. Tool: ",
214+
params and params.name,
215+
" Arguments: ",
216+
vim.inspect(params and params.arguments)
217+
)
211218
local result_or_error_table = tools.handle_invoke(client, params)
212219

213220
if result_or_error_table.error then

lua/claudecode/tools/close_buffer_by_name.lua

Lines changed: 0 additions & 35 deletions
This file was deleted.

lua/claudecode/tools/close_tab.lua

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
--- Tool implementation for closing a buffer by its name.
2+
3+
local schema = {
4+
description = "Close a tab/buffer by its tab name",
5+
inputSchema = {
6+
type = "object",
7+
properties = {
8+
tab_name = {
9+
type = "string",
10+
description = "Name of the tab to close",
11+
},
12+
},
13+
required = { "tab_name" },
14+
additionalProperties = false,
15+
["$schema"] = "http://json-schema.org/draft-07/schema#",
16+
},
17+
}
18+
19+
--- Handles the close_tab tool invocation.
20+
-- Closes a tab/buffer by its tab name.
21+
-- @param params table The input parameters for the tool.
22+
-- @field params.tab_name string Name of the tab to close.
23+
-- @return table A result message indicating success.
24+
-- @error table A table with code, message, and data for JSON-RPC error if failed.
25+
local function handler(params)
26+
local log_module_ok, log = pcall(require, "claudecode.logger")
27+
if not log_module_ok then
28+
return {
29+
code = -32603, -- Internal error
30+
message = "Internal error",
31+
data = "Failed to load logger module",
32+
}
33+
end
34+
35+
log.debug("close_tab handler called with params: " .. vim.inspect(params))
36+
37+
if not params.tab_name then
38+
log.error("Missing required parameter: tab_name")
39+
return {
40+
code = -32602, -- Invalid params
41+
message = "Invalid params",
42+
data = "Missing required parameter: tab_name",
43+
}
44+
end
45+
46+
-- Extract the actual file name from the tab name
47+
-- Tab name format: "✻ [Claude Code] README.md (e18e1e) ⧉"
48+
-- We need to extract "README.md" or the full path
49+
local tab_name = params.tab_name
50+
log.debug("Attempting to close tab: " .. tab_name)
51+
52+
-- Try to find buffer by the tab name first
53+
local bufnr = vim.fn.bufnr(tab_name)
54+
55+
if bufnr == -1 then
56+
-- If not found, try to extract filename from the tab name
57+
-- Look for pattern like "filename.ext" in the tab name
58+
local filename = tab_name:match("([%w%.%-_]+%.[%w]+)")
59+
if filename then
60+
log.debug("Extracted filename from tab name: " .. filename)
61+
-- Try to find buffer by filename
62+
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
63+
local buf_name = vim.api.nvim_buf_get_name(buf)
64+
if buf_name:match(filename .. "$") then
65+
bufnr = buf
66+
log.debug("Found buffer by filename match: " .. buf_name)
67+
break
68+
end
69+
end
70+
end
71+
end
72+
73+
if bufnr == -1 then
74+
log.error("Buffer not found for tab: " .. tab_name)
75+
return {
76+
code = -32000,
77+
message = "Buffer operation error",
78+
data = "Buffer not found for tab: " .. tab_name,
79+
}
80+
end
81+
82+
local success, err = pcall(vim.api.nvim_buf_delete, bufnr, { force = false })
83+
84+
if not success then
85+
log.error("Failed to close buffer: " .. tostring(err))
86+
return {
87+
code = -32000,
88+
message = "Buffer operation error",
89+
data = "Failed to close buffer for tab " .. tab_name .. ": " .. tostring(err),
90+
}
91+
end
92+
93+
log.info("Successfully closed tab: " .. tab_name)
94+
return { message = "Tab closed: " .. tab_name }
95+
end
96+
97+
return {
98+
name = "close_tab",
99+
schema = schema,
100+
handler = handler,
101+
}

tests/config_test.lua

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,29 +82,75 @@ _G.vim = { ---@type vim_global_api
8282
cmd = function() end, ---@type fun(command: string):nil
8383
api = {}, ---@type table
8484
fn = { ---@type vim_fn_table
85-
mode = function() return "n" end,
86-
delete = function(_, _) return 0 end,
87-
filereadable = function(_) return 1 end,
88-
fnamemodify = function(fname, _) return fname end,
89-
expand = function(s, _) return s end,
90-
getcwd = function() return "/mock/cwd" end,
91-
mkdir = function(_, _, _) return 1 end,
92-
buflisted = function(_) return 1 end,
93-
bufname = function(_) return "mockbuffer" end,
94-
bufnr = function(_) return 1 end,
95-
win_getid = function() return 1 end,
96-
win_gotoid = function(_) return true end,
97-
line = function(_) return 1 end,
98-
col = function(_) return 1 end,
99-
virtcol = function(_) return 1 end,
100-
getpos = function(_) return {0,1,1,0} end,
101-
setpos = function(_,_) return true end,
102-
tempname = function() return "/tmp/mocktemp" end,
103-
globpath = function(_,_) return "" end,
104-
termopen = function(_, _) return 0 end,
105-
stdpath = function(_) return "/mock/stdpath" end,
106-
json_encode = function(_) return "{}" end,
107-
json_decode = function(_) return {} end,
85+
mode = function()
86+
return "n"
87+
end,
88+
delete = function(_, _)
89+
return 0
90+
end,
91+
filereadable = function(_)
92+
return 1
93+
end,
94+
fnamemodify = function(fname, _)
95+
return fname
96+
end,
97+
expand = function(s, _)
98+
return s
99+
end,
100+
getcwd = function()
101+
return "/mock/cwd"
102+
end,
103+
mkdir = function(_, _, _)
104+
return 1
105+
end,
106+
buflisted = function(_)
107+
return 1
108+
end,
109+
bufname = function(_)
110+
return "mockbuffer"
111+
end,
112+
bufnr = function(_)
113+
return 1
114+
end,
115+
win_getid = function()
116+
return 1
117+
end,
118+
win_gotoid = function(_)
119+
return true
120+
end,
121+
line = function(_)
122+
return 1
123+
end,
124+
col = function(_)
125+
return 1
126+
end,
127+
virtcol = function(_)
128+
return 1
129+
end,
130+
getpos = function(_)
131+
return { 0, 1, 1, 0 }
132+
end,
133+
setpos = function(_, _)
134+
return true
135+
end,
136+
tempname = function()
137+
return "/tmp/mocktemp"
138+
end,
139+
globpath = function(_, _)
140+
return ""
141+
end,
142+
termopen = function(_, _)
143+
return 0
144+
end,
145+
stdpath = function(_)
146+
return "/mock/stdpath"
147+
end,
148+
json_encode = function(_)
149+
return "{}"
150+
end,
151+
json_decode = function(_)
152+
return {}
153+
end,
108154
},
109155
fs = { remove = function() end }, ---@type vim_fs_module
110156
}

0 commit comments

Comments
 (0)