-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathfiles.lua
More file actions
183 lines (160 loc) · 5.96 KB
/
files.lua
File metadata and controls
183 lines (160 loc) · 5.96 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
local config = require('opencode.config')
local icons = require('opencode.ui.icons')
local Promise = require('opencode.promise')
local M = {}
local last_successful_tool = nil
local function should_keep(ignore_patterns)
return function(path)
for _, pattern in ipairs(ignore_patterns) do
if path:match(pattern) then
return false
end
end
return true
end
end
local function run_systemlist(cmd)
local ok, result = pcall(vim.fn.systemlist, cmd)
return ok and vim.v.shell_error == 0 and result or nil
end
local function try_tool(tool, args, pattern, max, ignore_patterns)
if type(args) == 'function' then
local promise = args(pattern, max)
local result = promise and promise.and_then and promise:wait()
if result and type(result) == 'table' then
return vim.tbl_filter(should_keep(ignore_patterns), result)
end
end
if vim.fn.executable(tool) then
pattern = vim.fn.shellescape(pattern) or '.'
local result = run_systemlist(tool .. string.format(args, pattern, max))
if result then
return vim.tbl_filter(should_keep(ignore_patterns), result)
end
end
return nil
end
---@param pattern string
---@return string[]
local function find_files_fast(pattern)
local file_config = config.ui.completion.file_sources
local cli_tool = last_successful_tool or file_config.preferred_cli_tool or 'server'
local max = file_config.max_files or 10
local ignore_patterns = file_config.ignore_patterns or {}
local tools_order = { 'server', 'fd', 'fdfind', 'rg', 'git' }
local commands = {
fd = ' --type f --type l --full-path --color=never -E .git -E node_modules -i %s --max-results %d 2>/dev/null',
fdfind = ' --type f --type l --color=never -E .git -E node_modules --full-path -i %s --max-results %d 2>/dev/null',
rg = ' --files --no-messages --color=never | grep -i %s 2>/dev/null | head -%d',
git = ' ls-files --cached --others --exclude-standard | grep -i %s | head -%d',
server = function(pattern)
return require('opencode.state').api_client:find_files(pattern)
end,
}
if cli_tool and commands[cli_tool] then
tools_order = vim.tbl_filter(function(t)
return t ~= cli_tool
end, tools_order)
table.insert(tools_order, 1, cli_tool)
end
for _, tool in ipairs(tools_order) do
local result = try_tool(tool, commands[tool], pattern, max, ignore_patterns)
if result then
last_successful_tool = tool
return result
end
end
vim.notify('No suitable file search tool found. Please install fd, rg, or git.', vim.log.levels.WARN)
return {}
end
---@param file string
---@return CompletionItem
local function create_file_item(file, suffix, priority)
local filename = vim.fn.fnamemodify(file, ':t')
local dir = vim.fn.fnamemodify(file, ':h')
local file_path = dir == '.' and filename or dir .. '/' .. filename
local detail = dir == '.' and filename or dir .. '/' .. filename
-- 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)
local display_label = file_path
local file_config = config.ui.completion.file_sources
local max_display_len = file_config.max_display_length or 50
if #display_label > max_display_len then
display_label = '...' .. display_label:sub(-(math.floor(max_display_len) - 3))
end
local kind = vim.endswith(file, '/') and 'folder' or 'file'
return {
label = display_label .. (suffix or ''),
kind = kind,
kind_icon = icons.get(kind),
detail = detail,
documentation = 'Path: ' .. detail,
insert_text = file_path,
source_name = 'files',
priority = priority,
data = { name = filename, full_path = full_path },
}
end
local custom_kind = require('opencode.ui.completion.kind')
---@type CompletionSource
local file_source = {
name = 'files',
priority = 5,
is_incomplete = true,
custom_kind = custom_kind.register('files', icons.get('file')),
complete = Promise.async(function(context)
local sort_util = require('opencode.ui.completion.sort')
local file_config = config.ui.completion.file_sources
local input = context.input or ''
local expected_trigger = config.get_key_for_function('input_window', 'mention')
if not file_config.enabled or context.trigger_char ~= expected_trigger then
return {}
end
local recent_files = #input < 1 and M.get_recent_files():await() or {}
if #recent_files >= 5 then
return recent_files
end
local files_and_dirs = find_files_fast(input)
local items = vim.tbl_map(function(file)
return create_file_item(file, nil, 10)
end, files_and_dirs)
sort_util.sort_by_relevance(items, input, function(item)
return vim.fn.fnamemodify(item.label, ':t')
end, function(a, b)
return #a.label < #b.label
end)
return vim.list_extend(recent_files, items)
end),
on_complete = function(item)
local state = require('opencode.state')
local context = require('opencode.context')
local mention = require('opencode.ui.mention')
mention.highlight_all_mentions(state.windows.input_buf)
context.add_file(item.data.full_path)
end,
get_trigger_character = function()
local config = require('opencode.config')
return config.get_key_for_function('input_window', 'mention') or '@'
end,
}
---Get the list of recent files
---@return CompletionItem[]
M.get_recent_files = Promise.async(function()
local api_client = require('opencode.state').api_client
local result = api_client:get_file_status():await()
local recent_files = {}
if result then
for _, file in ipairs(result) do
local suffix = table.concat({ file.added and '+' .. file.added, file.removed and '-' .. file.removed }, ' ')
table.insert(recent_files, create_file_item(file.path, ' ' .. suffix, 20))
end
end
return recent_files
end)
---Get the file completion source
---@return CompletionSource
function M.get_source()
return file_source
end
return M