Skip to content
Open
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
42 changes: 41 additions & 1 deletion lua/claudecode/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,46 @@ function M._create_commands()
desc = "Add selected file(s) from tree explorer to Claude Code context (supports visual selection)",
})

--- Parse a command-line string respecting backslash-escaped spaces.
--- Treats `\ ` as a literal space within a token and unescaped whitespace as delimiters.
--- @param input string
--- @return string[]
local function parse_escaped_args(input)
local args = {}
local current = {}
local i = 1
local len = #input
while i <= len do
local ch = input:sub(i, i)
if ch == "\\" and i < len then
local next_ch = input:sub(i + 1, i + 1)
if next_ch == " " then
current[#current + 1] = " "
i = i + 2
elseif next_ch == "\\" then
current[#current + 1] = "\\"
i = i + 2
else
current[#current + 1] = ch
i = i + 1
end
elseif ch:match("%s") then
if #current > 0 then
args[#args + 1] = table.concat(current)
current = {}
end
i = i + 1
else
current[#current + 1] = ch
i = i + 1
end
end
if #current > 0 then
args[#args + 1] = table.concat(current)
end
return args
end

vim.api.nvim_create_user_command("ClaudeCodeAdd", function(opts)
if not M.state.server then
logger.error("command", "ClaudeCodeAdd: Claude Code integration is not running.")
Expand All @@ -911,7 +951,7 @@ function M._create_commands()
return
end

local args = vim.split(opts.args, "%s+")
local args = parse_escaped_args(opts.args)
local file_path = args[1]
local start_line = args[2] and tonumber(args[2]) or nil
local end_line = args[3] and tonumber(args[3]) or nil
Expand Down
80 changes: 80 additions & 0 deletions tests/unit/claudecode_add_command_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,86 @@ describe("ClaudeCodeAdd command", function()
end)
end)

describe("escaped path handling", function()
it("should parse backslash-escaped spaces in file paths", function()
vim.fn.filereadable = spy.new(function(path)
return path == "file name.lua" and 1 or 0
end)
vim.fn.expand = spy.new(function(path)
return path
end)

command_handler({ args = "file\\ name.lua" })

assert.spy(mock_server.broadcast).was_called_with("at_mentioned", {
filePath = "file name.lua",
lineStart = nil,
lineEnd = nil,
})
end)

it("should not treat 'file\\ 1' as path with line number", function()
vim.fn.filereadable = spy.new(function(path)
return path == "file 1" and 1 or 0
end)
vim.fn.expand = spy.new(function(path)
return path
end)

command_handler({ args = "file\\ 1" })

assert.spy(mock_server.broadcast).was_called_with("at_mentioned", {
filePath = "file 1",
lineStart = nil,
lineEnd = nil,
})
end)

it("should parse escaped path with line range", function()
vim.fn.filereadable = spy.new(function(path)
return path == "my test file.lua" and 1 or 0
end)
vim.fn.expand = spy.new(function(path)
return path
end)

command_handler({ args = "my\\ test\\ file.lua 10 20" })

assert.spy(mock_server.broadcast).was_called_with("at_mentioned", {
filePath = "my test file.lua",
lineStart = 9,
lineEnd = 19,
})
end)

it("should parse escaped path with start line only", function()
vim.fn.filereadable = spy.new(function(path)
return path == "file name.lua" and 1 or 0
end)
vim.fn.expand = spy.new(function(path)
return path
end)

command_handler({ args = "file\\ name.lua 5" })

assert.spy(mock_server.broadcast).was_called_with("at_mentioned", {
filePath = "file name.lua",
lineStart = 4,
lineEnd = nil,
})
end)

it("should still handle paths without spaces", function()
command_handler({ args = "/existing/file.lua 10" })

assert.spy(mock_server.broadcast).was_called_with("at_mentioned", {
filePath = "/existing/file.lua",
lineStart = 9,
lineEnd = nil,
})
end)
end)

describe("path handling", function()
it("should expand tilde paths", function()
command_handler({ args = "~/test.lua" })
Expand Down