Skip to content

Commit 285f6f4

Browse files
jdrouhardSaghen
andauthored
feat: extract word from completion item for auto-insert preview (#341)
* feat: extract word from completion item for auto-insert preview Use a modified version of nvim-cmp's word extraction algorithm from expanded snippets for use in auto-insert preview textEdits. For example, if a snippet expansion results in something like: ``` my_neat_function(int arg1, double arg2) ``` Then the floating window will show the above for that completion, but when selecting it, the buffer will only insert `my_neat_function`. This allows using the snippet completion item without actually _accepting_ it by just selecting it in the preview window and then continuing to type. So one could type "my_n", Ctrl-N/Tab (or whatever your keymap is) to select `my_neat_function`, and then just continue with typing the ( and args manually. Of course, accepting the completion will still work as expected by expanding the snippet. * refactor: word extraction for auto-insert preview --------- Co-authored-by: Liam Dyer <[email protected]>
1 parent 9ad8886 commit 285f6f4

File tree

2 files changed

+64
-12
lines changed

2 files changed

+64
-12
lines changed

lua/blink/cmp/accept/preview.lua

+7-12
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,17 @@ local function preview(item, previous_text_edit)
66
-- with auto_insert, we may have to undo the previous preview
77
if previous_text_edit ~= nil then text_edit.range = text_edits_lib.get_undo_range(previous_text_edit) end
88

9-
-- for snippets, expand them with the default property names
9+
if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
10+
local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_edit.newText)
11+
text_edit.newText = require('blink.cmp.utils').get_prefix_before_brackets_and_quotes(
12+
expanded_snippet and tostring(expanded_snippet) or text_edit.newText
13+
)
14+
end
15+
1016
local cursor_pos = {
1117
text_edit.range.start.line + 1,
1218
text_edit.range.start.character + #text_edit.newText,
1319
}
14-
if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
15-
local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_edit.newText)
16-
text_edit.newText = expanded_snippet and tostring(expanded_snippet) or text_edit.newText
17-
18-
-- place the cursor at the first tab stop
19-
local tabstops = require('blink.cmp.sources.snippets.utils').get_tab_stops(text_edit.newText)
20-
if tabstops and #tabstops > 0 then
21-
cursor_pos[1] = text_edit.range.start.line + tabstops[1].line
22-
cursor_pos[2] = text_edit.range.start.character + tabstops[1].character
23-
end
24-
end
2520

2621
text_edits_lib.apply({ text_edit })
2722
vim.api.nvim_win_set_cursor(0, cursor_pos)

lua/blink/cmp/utils.lua

+57
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,61 @@ function utils.get_tailwind_hl(ctx)
9797
end
9898
end
9999

100+
local PAIRS_AND_INVALID_CHARS = {}
101+
string.gsub('\'"=$()[]<>{} \t\n\r', '.', function(char) PAIRS_AND_INVALID_CHARS[string.byte(char)] = true end)
102+
103+
local CLOSING_PAIR = {
104+
[string.byte('<')] = string.byte('>'),
105+
[string.byte('[')] = string.byte(']'),
106+
[string.byte('(')] = string.byte(')'),
107+
[string.byte('{')] = string.byte('}'),
108+
[string.byte('"')] = string.byte('"'),
109+
[string.byte("'")] = string.byte("'"),
110+
}
111+
112+
local ALPHANUMERIC = {}
113+
string.gsub(
114+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
115+
'.',
116+
function(char) ALPHANUMERIC[string.byte(char)] = true end
117+
)
118+
119+
--- Gets the prefix of the given text, stopping at brackets and quotes
120+
--- @param text string
121+
--- @return string
122+
function utils.get_prefix_before_brackets_and_quotes(text)
123+
local closing_pairs_stack = {}
124+
local word = ''
125+
126+
local add = function(char)
127+
word = word .. string.char(char)
128+
129+
-- if we've seen the opening pair, and we've just received the closing pair,
130+
-- remove it from the closing pairs stack
131+
if closing_pairs_stack[#closing_pairs_stack] == char then
132+
table.remove(closing_pairs_stack, #closing_pairs_stack)
133+
-- if the character is an opening pair, add it to the closing pairs stack
134+
elseif CLOSING_PAIR[char] ~= nil then
135+
table.insert(closing_pairs_stack, CLOSING_PAIR[char])
136+
end
137+
end
138+
139+
local has_alphanumeric = false
140+
for i = 1, #text do
141+
local char = string.byte(text, i)
142+
if PAIRS_AND_INVALID_CHARS[char] == nil then
143+
add(char)
144+
has_alphanumeric = has_alphanumeric or ALPHANUMERIC[char]
145+
elseif not has_alphanumeric or #closing_pairs_stack ~= 0 then
146+
add(char)
147+
-- if we had an alphanumeric, and the closing pairs stuck *just* emptied,
148+
-- because the current character is a closing pair, we exit
149+
if has_alphanumeric and #closing_pairs_stack == 0 then break end
150+
else
151+
break
152+
end
153+
end
154+
return word
155+
end
156+
100157
return utils

0 commit comments

Comments
 (0)