Skip to content

Commit 8cb39fd

Browse files
committed
Handle completion of % and # completion correctly.
The current behavior if you try to complete :e %:p:h or something similar replaces only the h with the path, which isn't the desired behavior, because then nvim tries to open the wrong filename. Detect this case and produce edits that will replace the entire `%` chain with modifiers.
1 parent 8ee981b commit 8cb39fd

1 file changed

Lines changed: 41 additions & 6 deletions

File tree

lua/cmp_cmdline/init.lua

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ local definitions = {
7878
kind = cmp.lsp.CompletionItemKind.Variable,
7979
isIncomplete = true,
8080
exec = function(option, arglead, cmdline, force)
81+
-- Any edits we produce are relative to the whole command line.
82+
local cmdline_length = #cmdline
8183
-- Ignore range only cmdline. (e.g.: 4, '<,'>)
8284
if not force and ONLY_RANGE_REGEX:match_str(cmdline) then
8385
return {}
@@ -115,18 +117,21 @@ local definitions = {
115117
-- In this case, the `vim.fn.getcompletion` will return only `get_query` for `vim.treesitter.get_|`.
116118
-- We should detect `vim.treesitter.` and `get_query` separately.
117119
-- TODO: The `\h\w*` was choosed by huristic. We should consider more suitable detection.
118-
local fixed_input
119-
do
120-
local suffix_pos = vim.regex([[\h\w*$]]):match_str(arglead)
121-
fixed_input = string.sub(arglead, 1, suffix_pos or #arglead)
122-
end
120+
local fixed_input = arglead
123121

124122
-- The `vim.fn.getcompletion` does not return `*no*cursorline` option.
125123
-- cmp-cmdline corrects `no` prefix for option name.
126124
local is_option_name_completion = OPTION_NAME_COMPLETION_REGEX:match_str(cmdline) ~= nil
127125

128126
local items = {}
129127
local escaped = cmdline:gsub([[\\]], [[\\\\]]);
128+
local is_magic_file = false
129+
local input_start = string.sub(fixed_input, 1, 1)
130+
if input_start == '%' then
131+
is_magic_file = true
132+
elseif input_start == '#' then
133+
is_magic_file = true
134+
end
130135
for _, word_or_item in ipairs(vim.fn.getcompletion(escaped, 'cmdline')) do
131136
local word = type(word_or_item) == 'string' and word_or_item or word_or_item.word
132137
local item = { label = word }
@@ -139,9 +144,25 @@ local definitions = {
139144
end
140145
end
141146
for _, item in ipairs(items) do
142-
if not string.find(item.label, fixed_input, 1, true) then
147+
if not is_magic_file and not string.find(item.label, fixed_input, 1, true) then
143148
item.label = fixed_input .. item.label
144149
end
150+
if is_magic_file then
151+
local replace_range = {
152+
start = {
153+
character = cmdline_length - #fixed_input - 1
154+
},
155+
['end'] = {
156+
character = cmdline_length - 1
157+
}
158+
}
159+
item.textEdit = {
160+
replace = replace_range,
161+
range = replace_range,
162+
newText = item.label
163+
}
164+
item.label = fixed_input .. '' .. item.label
165+
end
145166
end
146167
return items
147168
end
@@ -196,6 +217,20 @@ source.complete = function(self, params, callback)
196217
for _, item in ipairs(items) do
197218
item.kind = kind
198219
labels[item.label] = true
220+
if item['textEdit'] ~= nil then
221+
local new_range = {
222+
start = {
223+
character = item.textEdit.range.start.character,
224+
line = params.context.cursor.line
225+
},
226+
['end'] = {
227+
character = item.textEdit.range['end'].character,
228+
line = params.context.cursor.line
229+
}
230+
}
231+
item.textEdit.replace = new_range
232+
item.textEdit.range = new_range
233+
end
199234
end
200235

201236
-- `vim.fn.getcompletion` does not handle fuzzy matches. So, we must return all items, including items that were matched in the previous input.

0 commit comments

Comments
 (0)