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
4 changes: 3 additions & 1 deletion lua/opencode/ui/completion/files.lua
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ local function create_file_item(file, suffix, priority)
local dir = vim.fn.fnamemodify(file, ':h')
local file_path = dir == '.' and filename or dir .. '/' .. filename
local detail = dir == '.' and filename or dir .. '/' .. filename
local full_path = vim.fn.fnamemodify(file, ':p')
-- Build absolute path without resolving symlinks so that files inside
-- symlinked directories within cwd pass the is_path_in_cwd check.
local full_path = file:sub(1, 1) == '/' and file or (vim.fn.getcwd() .. '/' .. file)
Comment thread
sudo-tee marked this conversation as resolved.
local display_label = file_path

local file_config = config.ui.completion.file_sources
Expand Down
14 changes: 11 additions & 3 deletions lua/opencode/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,17 @@ function M.pcall_trace(fn, ...)
end

function M.is_path_in_cwd(path)
local cwd = vim.fn.getcwd()
local abs_path = vim.fn.fnamemodify(path, ':p')
return abs_path:sub(1, #cwd) == cwd
local cwd = vim.fn.simplify(vim.fn.getcwd())
local cwd_prefix = cwd == '/' and cwd or (cwd .. '/')

local logical_path
if path:sub(1, 1) == '/' then
logical_path = vim.fn.simplify(path)
else
logical_path = vim.fn.simplify(cwd .. '/' .. path)
end

return logical_path == cwd or logical_path:sub(1, #cwd_prefix) == cwd_prefix
end

--- Check if a given path is in the system temporary directory.
Expand Down
46 changes: 46 additions & 0 deletions tests/unit/util_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,49 @@ describe('util.parse_quick_context_args', function()
end)
end
end)

describe('util.is_path_in_cwd', function()
local original_getcwd
local test_cwd = '/tmp/test_project'

before_each(function()
original_getcwd = vim.fn.getcwd
vim.fn.getcwd = function()
return test_cwd
end
end)

after_each(function()
vim.fn.getcwd = original_getcwd
end)

it('accepts a relative path inside cwd', function()
assert.is_true(util.is_path_in_cwd('src/foo.lua'))
end)

it('accepts an absolute path inside cwd', function()
assert.is_true(util.is_path_in_cwd(test_cwd .. '/src/foo.lua'))
end)

it('accepts an absolute path through a symlinked directory in cwd', function()
assert.is_true(util.is_path_in_cwd(test_cwd .. '/linked_folder/test.txt'))
end)

it('rejects an absolute path outside cwd', function()
assert.is_false(util.is_path_in_cwd('/tmp/outside/foo.lua'))
end)

it('rejects a path that only shares the cwd prefix', function()
assert.is_false(util.is_path_in_cwd('/tmp/test_project2/src/foo.lua'))
end)

it('rejects a relative path that escapes cwd via ..', function()
assert.is_false(util.is_path_in_cwd('../outside/foo.lua'))
end)

it('accepts a relative path through a symlinked directory in cwd', function()
-- Simulate the symlink case: relative path stays logical (not resolved)
-- e.g. linked_folder -> /tmp/external, linked_folder/test.txt is valid
assert.is_true(util.is_path_in_cwd('linked_folder/test.txt'))
end)
Comment thread
sudo-tee marked this conversation as resolved.
end)
Loading