Skip to content

Commit

Permalink
feat: add cancel command for use with auto_insert
Browse files Browse the repository at this point in the history
related to #215
  • Loading branch information
Saghen committed Nov 24, 2024
1 parent f346516 commit c58b3a8
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 21 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ MiniDeps.add({
-- When defining your own keymaps without a preset, no keybinds will be assigned automatically.
--
-- Available commands:
-- show, hide, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
-- show, hide, cancel, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
-- scroll_documentation_up, scroll_documentation_down, snippet_forward, snippet_backward, fallback
--
-- "default" keymap
Expand Down Expand Up @@ -428,6 +428,8 @@ MiniDeps.add({
-- 'preselect' will automatically select the first item in the completion list
-- 'manual' will not select any item by default
-- 'auto_insert' will not select any item by default, and insert the completion items automatically when selecting them
--
-- When using 'auto_insert', you may want to bind a key to the `cancel` command, which will undo the selection
selection = 'preselect',
-- Controls how the completion items are rendered on the popup window
draw = {
Expand Down
11 changes: 4 additions & 7 deletions lua/blink/cmp/accept/preview.lua
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
--- @param item blink.cmp.CompletionItem
local function preview(item, previous_text_edit)
local function preview(item)
local text_edits_lib = require('blink.cmp.accept.text-edits')
local text_edit = text_edits_lib.get_from_item(item)

-- with auto_insert, we may have to undo the previous preview
if previous_text_edit ~= nil then text_edit.range = text_edits_lib.get_undo_range(previous_text_edit) end

if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_edit.newText)
text_edit.newText = require('blink.cmp.utils').get_prefix_before_brackets_and_quotes(
expanded_snippet and tostring(expanded_snippet) or text_edit.newText
)
end

local undo_text_edit = text_edits_lib.get_undo_text_edit(text_edit)
local cursor_pos = {
text_edit.range.start.line + 1,
text_edit.range.start.character + #text_edit.newText,
}

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

-- return so that it can be undone in the future
return text_edit
vim.api.nvim_win_set_cursor(0, cursor_pos)
return undo_text_edit
end

return preview
38 changes: 31 additions & 7 deletions lua/blink/cmp/accept/text-edits.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ function text_edits.apply(edits) vim.lsp.util.apply_text_edits(edits, vim.api.nv

------- Undo -------

--- Gets the reverse of the text edit, must be called before applying
--- @param text_edit lsp.TextEdit
--- @return lsp.TextEdit
function text_edits.get_undo_text_edit(text_edit)
return {
range = text_edits.get_undo_range(text_edit),
newText = text_edits.get_text_to_replace(text_edit),
}
end

--- Gets the range for undoing an applied text edit
--- @param text_edit lsp.TextEdit
function text_edits.get_undo_range(text_edit)
Expand All @@ -21,14 +31,28 @@ function text_edits.get_undo_range(text_edit)
return range
end

--- Undoes a text edit
--- Gets the text the text edit will replace
--- @param text_edit lsp.TextEdit
function text_edits.undo(text_edit)
text_edit = vim.deepcopy(text_edit)
text_edit.range = text_edits.get_undo_range(text_edit)
text_edit.newText = ''

text_edits.apply({ text_edit })
--- @return string
function text_edits.get_text_to_replace(text_edit)
local bufnr = vim.api.nvim_get_current_buf()
local lines = {}
for line = text_edit.range.start.line, text_edit.range['end'].line do
local line_text = vim.api.nvim_buf_get_lines(bufnr, line, line + 1, false)[1]
local is_start_line = line == text_edit.range.start.line
local is_end_line = line == text_edit.range['end'].line

if is_start_line and is_end_line then
table.insert(lines, line_text:sub(text_edit.range.start.character + 1, text_edit.range['end'].character))
elseif is_start_line then
table.insert(lines, line_text:sub(text_edit.range.start.character + 1))
elseif is_end_line then
table.insert(lines, line_text:sub(1, text_edit.range['end'].character))
else
table.insert(lines, line_text)
end
end
return table.concat(lines, '\n')
end

------- Get -------
Expand Down
3 changes: 2 additions & 1 deletion lua/blink/cmp/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
--- | 'fallback' Fallback to the built-in behavior
--- | 'show' Show the completion window
--- | 'hide' Hide the completion window
--- | 'cancel' Cancel the current completion, undoing the preview from auto_insert
--- | 'accept' Accept the current completion item
--- | 'select_and_accept' Select the current completion item and accept it
--- | 'select_prev' Select the previous completion item
Expand Down Expand Up @@ -204,7 +205,7 @@ local config = {
-- When defining your own keymaps without a preset, no keybinds will be assigned automatically.
--
-- Available commands:
-- show, hide, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
-- show, hide, cancel, accept, select_and_accept, select_prev, select_next, show_documentation, hide_documentation,
-- scroll_documentation_up, scroll_documentation_down, snippet_forward, snippet_backward, fallback
--
-- "default" keymap
Expand Down
9 changes: 9 additions & 0 deletions lua/blink/cmp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ cmp.hide = function()
return true
end

cmp.cancel = function()
if not cmp.windows.autocomplete.win:is_open() then return end
vim.schedule(function()
cmp.windows.autocomplete.undo_preview()
cmp.trigger.hide()
end)
return true
end

--- @param callback fun(context: blink.cmp.Context)
cmp.on_open = function(callback) cmp.windows.autocomplete.listen_on_open(callback) end

Expand Down
1 change: 1 addition & 0 deletions lua/blink/cmp/keymap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function keymap.setup(opts)
local commands = {
'show',
'hide',
'cancel',
'accept',
'select_and_accept',
'select_prev',
Expand Down
26 changes: 21 additions & 5 deletions lua/blink/cmp/windows/autocomplete.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
--- @field auto_show boolean
--- @field context blink.cmp.Context?
--- @field event_targets blink.cmp.CompletionWindowEventTargets
--- @field preview_undo_text_edit? lsp.TextEdit
--- @field preview_context_id? number
---
--- @field setup fun(): blink.cmp.CompletionWindow
---
Expand All @@ -25,6 +27,7 @@
--- @field listen_on_position_update fun(callback: fun())
---
--- @field accept fun(): boolean?
--- @field undo_preview fun()
---
--- @field select fun(line: number, skip_auto_insert?: boolean)
--- @field select_next fun(opts?: { skip_auto_insert?: boolean })
Expand Down Expand Up @@ -200,15 +203,25 @@ function autocomplete.accept()
if selected_item == nil then return end

-- undo the preview if it exists
if autocomplete.preview_text_edit ~= nil and autocomplete.preview_context_id == autocomplete.context.id then
text_edits_lib.undo(autocomplete.preview_text_edit)
if autocomplete.preview_undo_text_edit ~= nil and autocomplete.preview_context_id == autocomplete.context.id then
text_edits_lib.apply({ autocomplete.preview_undo_text_edit })
autocomplete.preview_undo_text_edit = nil
autocomplete.preview_context_id = nil
end

-- apply
require('blink.cmp.accept')(context, selected_item)
return true
end

function autocomplete.undo_preview()
if autocomplete.preview_undo_text_edit ~= nil and autocomplete.preview_context_id == autocomplete.context.id then
text_edits_lib.apply({ autocomplete.preview_undo_text_edit })
autocomplete.preview_undo_text_edit = nil
autocomplete.preview_context_id = nil
end
end

function autocomplete.select(line, skip_auto_insert)
autocomplete.set_has_selected(true)
vim.api.nvim_win_set_cursor(autocomplete.win:get_win(), { line, 0 })
Expand All @@ -218,9 +231,12 @@ function autocomplete.select(line, skip_auto_insert)
-- when auto_insert is enabled, we immediately apply the text edit
if config.windows.autocomplete.selection == 'auto_insert' and selected_item ~= nil and not skip_auto_insert then
require('blink.cmp.trigger.completion').suppress_events_for_callback(function()
if autocomplete.preview_context_id ~= autocomplete.context.id then autocomplete.preview_text_edit = nil end
autocomplete.preview_text_edit =
require('blink.cmp.accept.preview')(selected_item, autocomplete.preview_text_edit)
-- undo the previous preview if it exists
if autocomplete.preview_context_id == autocomplete.context.id and autocomplete.preview_undo_text_edit ~= nil then
require('blink.cmp.accept.text-edits').apply({ autocomplete.preview_undo_text_edit })
end

autocomplete.preview_undo_text_edit = require('blink.cmp.accept.preview')(selected_item)
autocomplete.preview_context_id = autocomplete.context.id
end)
end
Expand Down

0 comments on commit c58b3a8

Please sign in to comment.