Skip to content

Commit c58b3a8

Browse files
committed
feat: add cancel command for use with auto_insert
related to #215
1 parent f346516 commit c58b3a8

File tree

7 files changed

+71
-21
lines changed

7 files changed

+71
-21
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ MiniDeps.add({
192192
-- When defining your own keymaps without a preset, no keybinds will be assigned automatically.
193193
--
194194
-- Available commands:
195-
-- show, hide, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
195+
-- show, hide, cancel, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
196196
-- scroll_documentation_up, scroll_documentation_down, snippet_forward, snippet_backward, fallback
197197
--
198198
-- "default" keymap
@@ -428,6 +428,8 @@ MiniDeps.add({
428428
-- 'preselect' will automatically select the first item in the completion list
429429
-- 'manual' will not select any item by default
430430
-- 'auto_insert' will not select any item by default, and insert the completion items automatically when selecting them
431+
--
432+
-- When using 'auto_insert', you may want to bind a key to the `cancel` command, which will undo the selection
431433
selection = 'preselect',
432434
-- Controls how the completion items are rendered on the popup window
433435
draw = {

lua/blink/cmp/accept/preview.lua

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
--- @param item blink.cmp.CompletionItem
2-
local function preview(item, previous_text_edit)
2+
local function preview(item)
33
local text_edits_lib = require('blink.cmp.accept.text-edits')
44
local text_edit = text_edits_lib.get_from_item(item)
55

6-
-- with auto_insert, we may have to undo the previous preview
7-
if previous_text_edit ~= nil then text_edit.range = text_edits_lib.get_undo_range(previous_text_edit) end
8-
96
if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
107
local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_edit.newText)
118
text_edit.newText = require('blink.cmp.utils').get_prefix_before_brackets_and_quotes(
129
expanded_snippet and tostring(expanded_snippet) or text_edit.newText
1310
)
1411
end
1512

13+
local undo_text_edit = text_edits_lib.get_undo_text_edit(text_edit)
1614
local cursor_pos = {
1715
text_edit.range.start.line + 1,
1816
text_edit.range.start.character + #text_edit.newText,
1917
}
2018

2119
text_edits_lib.apply({ text_edit })
22-
vim.api.nvim_win_set_cursor(0, cursor_pos)
2320

24-
-- return so that it can be undone in the future
25-
return text_edit
21+
vim.api.nvim_win_set_cursor(0, cursor_pos)
22+
return undo_text_edit
2623
end
2724

2825
return preview

lua/blink/cmp/accept/text-edits.lua

+31-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ function text_edits.apply(edits) vim.lsp.util.apply_text_edits(edits, vim.api.nv
77

88
------- Undo -------
99

10+
--- Gets the reverse of the text edit, must be called before applying
11+
--- @param text_edit lsp.TextEdit
12+
--- @return lsp.TextEdit
13+
function text_edits.get_undo_text_edit(text_edit)
14+
return {
15+
range = text_edits.get_undo_range(text_edit),
16+
newText = text_edits.get_text_to_replace(text_edit),
17+
}
18+
end
19+
1020
--- Gets the range for undoing an applied text edit
1121
--- @param text_edit lsp.TextEdit
1222
function text_edits.get_undo_range(text_edit)
@@ -21,14 +31,28 @@ function text_edits.get_undo_range(text_edit)
2131
return range
2232
end
2333

24-
--- Undoes a text edit
34+
--- Gets the text the text edit will replace
2535
--- @param text_edit lsp.TextEdit
26-
function text_edits.undo(text_edit)
27-
text_edit = vim.deepcopy(text_edit)
28-
text_edit.range = text_edits.get_undo_range(text_edit)
29-
text_edit.newText = ''
30-
31-
text_edits.apply({ text_edit })
36+
--- @return string
37+
function text_edits.get_text_to_replace(text_edit)
38+
local bufnr = vim.api.nvim_get_current_buf()
39+
local lines = {}
40+
for line = text_edit.range.start.line, text_edit.range['end'].line do
41+
local line_text = vim.api.nvim_buf_get_lines(bufnr, line, line + 1, false)[1]
42+
local is_start_line = line == text_edit.range.start.line
43+
local is_end_line = line == text_edit.range['end'].line
44+
45+
if is_start_line and is_end_line then
46+
table.insert(lines, line_text:sub(text_edit.range.start.character + 1, text_edit.range['end'].character))
47+
elseif is_start_line then
48+
table.insert(lines, line_text:sub(text_edit.range.start.character + 1))
49+
elseif is_end_line then
50+
table.insert(lines, line_text:sub(1, text_edit.range['end'].character))
51+
else
52+
table.insert(lines, line_text)
53+
end
54+
end
55+
return table.concat(lines, '\n')
3256
end
3357

3458
------- Get -------

lua/blink/cmp/config.lua

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
--- | 'fallback' Fallback to the built-in behavior
33
--- | 'show' Show the completion window
44
--- | 'hide' Hide the completion window
5+
--- | 'cancel' Cancel the current completion, undoing the preview from auto_insert
56
--- | 'accept' Accept the current completion item
67
--- | 'select_and_accept' Select the current completion item and accept it
78
--- | 'select_prev' Select the previous completion item
@@ -204,7 +205,7 @@ local config = {
204205
-- When defining your own keymaps without a preset, no keybinds will be assigned automatically.
205206
--
206207
-- Available commands:
207-
-- show, hide, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
208+
-- show, hide, cancel, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
208209
-- scroll_documentation_up, scroll_documentation_down, snippet_forward, snippet_backward, fallback
209210
--
210211
-- "default" keymap

lua/blink/cmp/init.lua

+9
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ cmp.hide = function()
137137
return true
138138
end
139139

140+
cmp.cancel = function()
141+
if not cmp.windows.autocomplete.win:is_open() then return end
142+
vim.schedule(function()
143+
cmp.windows.autocomplete.undo_preview()
144+
cmp.trigger.hide()
145+
end)
146+
return true
147+
end
148+
140149
--- @param callback fun(context: blink.cmp.Context)
141150
cmp.on_open = function(callback) cmp.windows.autocomplete.listen_on_open(callback) end
142151

lua/blink/cmp/keymap.lua

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ function keymap.setup(opts)
7272
local commands = {
7373
'show',
7474
'hide',
75+
'cancel',
7576
'accept',
7677
'select_and_accept',
7778
'select_prev',

lua/blink/cmp/windows/autocomplete.lua

+21-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
--- @field auto_show boolean
1313
--- @field context blink.cmp.Context?
1414
--- @field event_targets blink.cmp.CompletionWindowEventTargets
15+
--- @field preview_undo_text_edit? lsp.TextEdit
16+
--- @field preview_context_id? number
1517
---
1618
--- @field setup fun(): blink.cmp.CompletionWindow
1719
---
@@ -25,6 +27,7 @@
2527
--- @field listen_on_position_update fun(callback: fun())
2628
---
2729
--- @field accept fun(): boolean?
30+
--- @field undo_preview fun()
2831
---
2932
--- @field select fun(line: number, skip_auto_insert?: boolean)
3033
--- @field select_next fun(opts?: { skip_auto_insert?: boolean })
@@ -200,15 +203,25 @@ function autocomplete.accept()
200203
if selected_item == nil then return end
201204

202205
-- undo the preview if it exists
203-
if autocomplete.preview_text_edit ~= nil and autocomplete.preview_context_id == autocomplete.context.id then
204-
text_edits_lib.undo(autocomplete.preview_text_edit)
206+
if autocomplete.preview_undo_text_edit ~= nil and autocomplete.preview_context_id == autocomplete.context.id then
207+
text_edits_lib.apply({ autocomplete.preview_undo_text_edit })
208+
autocomplete.preview_undo_text_edit = nil
209+
autocomplete.preview_context_id = nil
205210
end
206211

207212
-- apply
208213
require('blink.cmp.accept')(context, selected_item)
209214
return true
210215
end
211216

217+
function autocomplete.undo_preview()
218+
if autocomplete.preview_undo_text_edit ~= nil and autocomplete.preview_context_id == autocomplete.context.id then
219+
text_edits_lib.apply({ autocomplete.preview_undo_text_edit })
220+
autocomplete.preview_undo_text_edit = nil
221+
autocomplete.preview_context_id = nil
222+
end
223+
end
224+
212225
function autocomplete.select(line, skip_auto_insert)
213226
autocomplete.set_has_selected(true)
214227
vim.api.nvim_win_set_cursor(autocomplete.win:get_win(), { line, 0 })
@@ -218,9 +231,12 @@ function autocomplete.select(line, skip_auto_insert)
218231
-- when auto_insert is enabled, we immediately apply the text edit
219232
if config.windows.autocomplete.selection == 'auto_insert' and selected_item ~= nil and not skip_auto_insert then
220233
require('blink.cmp.trigger.completion').suppress_events_for_callback(function()
221-
if autocomplete.preview_context_id ~= autocomplete.context.id then autocomplete.preview_text_edit = nil end
222-
autocomplete.preview_text_edit =
223-
require('blink.cmp.accept.preview')(selected_item, autocomplete.preview_text_edit)
234+
-- undo the previous preview if it exists
235+
if autocomplete.preview_context_id == autocomplete.context.id and autocomplete.preview_undo_text_edit ~= nil then
236+
require('blink.cmp.accept.text-edits').apply({ autocomplete.preview_undo_text_edit })
237+
end
238+
239+
autocomplete.preview_undo_text_edit = require('blink.cmp.accept.preview')(selected_item)
224240
autocomplete.preview_context_id = autocomplete.context.id
225241
end)
226242
end

0 commit comments

Comments
 (0)