Skip to content

Commit

Permalink
feat: ignore autocmds wrapper for trigger
Browse files Browse the repository at this point in the history
  • Loading branch information
Saghen committed Oct 11, 2024
1 parent 58ffe1e commit 19e1312
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 75 deletions.
12 changes: 0 additions & 12 deletions lua/blink/cmp/accept/init.lua
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
local text_edits_lib = require('blink.cmp.accept.text-edits')
local brackets_lib = require('blink.cmp.accept.brackets')
local config = require('blink.cmp.config')

--- Applies a completion item to the current buffer
--- @param item blink.cmp.CompletionItem
local function accept(item)
local has_original_text_edit = item.textEdit ~= nil
item = vim.deepcopy(item)
item.textEdit = text_edits_lib.get_from_item(item)

-- As `text_edits.guess_text_edit`'s way of detecting the start position of the edit is a bit
-- naive for now, if selection mode is `auto_insert` and the LSP didn't provide a textEdit, then
-- we need to manually set the correct position here.
if config.windows.autocomplete.selection == 'auto_insert' and not has_original_text_edit then
local word = item.insertText or item.label
if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then word = item.label end
local current_col = vim.api.nvim_win_get_cursor(0)[2]
item.textEdit.range.start.character = current_col - #word
end

-- Add brackets to the text edit if needed
local brackets_status, text_edit_with_brackets, offset = brackets_lib.add_brackets(vim.bo.filetype, item)
item.textEdit = text_edit_with_brackets
Expand Down
105 changes: 63 additions & 42 deletions lua/blink/cmp/trigger/completion.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
-- (provided by the sources) or anything matching the `keyword_regex`, we create a new `context`.
-- This can be used downstream to determine if we should make new requests to the sources or not.

local config = require('blink.cmp.config')
local config_trigger = config.trigger.completion
local config = require('blink.cmp.config').trigger.completion
local sources = require('blink.cmp.sources.lib')
local utils = require('blink.cmp.utils')
local autocomplete = require('blink.cmp.windows.autocomplete')
local text_edits_lib = require('blink.cmp.accept.text-edits')

local trigger = {
current_context_id = -1,
Expand All @@ -32,31 +29,32 @@ function trigger.activate_autocmds()
-- decide if we should show the completion window
vim.api.nvim_create_autocmd('TextChangedI', {
callback = function()
-- we were told to ignore the text changed event, so we update the context
-- but don't send an on_show event upstream
if trigger.ignore_next_text_changed then
if trigger.context ~= nil then trigger.show({ send_upstream = false }) end
trigger.ignore_next_text_changed = false

-- no characters added so let cursormoved handle it
if last_char == '' then return end
elseif last_char == '' then
return

-- ignore if in a special buffer
if utils.is_special_buffer() then
elseif utils.is_special_buffer() then
trigger.hide()
-- character forces a trigger according to the sources, create a fresh context

-- character forces a trigger according to the sources, create a fresh context
elseif vim.tbl_contains(sources.get_trigger_characters(), last_char) then
trigger.context = nil
trigger.triggered_by = nil
trigger.show({ trigger_character = last_char })
-- character is part of the current context OR in an existing context
elseif last_char:match(config_trigger.keyword_regex) ~= nil then

-- character is part of the current context OR in an existing context
elseif last_char:match(config.keyword_regex) ~= nil then
trigger.show()
-- nothing matches so hide
else
local selected_item = autocomplete.get_selected_item()
if
config.windows.autocomplete.selection == 'auto_insert'
and autocomplete.has_selected
and selected_item ~= nil
then
text_edits_lib.apply_additional_text_edits(selected_item)
end

-- nothing matches so hide
else
trigger.hide()
end

Expand All @@ -66,45 +64,49 @@ function trigger.activate_autocmds()

vim.api.nvim_create_autocmd({ 'CursorMovedI', 'InsertEnter' }, {
callback = function(ev)
-- we were told to ignore the cursor moved event, so we update the context
-- but don't send an on_show event upstream
if trigger.ignore_next_cursor_moved and ev.event == 'CursorMovedI' then
if trigger.context ~= nil then trigger.show({ send_upstream = false }) end
trigger.ignore_next_cursor_moved = false
return
end

-- characters added so let textchanged handle it
if last_char ~= '' then return end

local cursor_col = vim.api.nvim_win_get_cursor(0)[2]
local char_under_cursor = vim.api.nvim_get_current_line():sub(cursor_col, cursor_col)
local is_on_trigger = vim.tbl_contains(sources.get_trigger_characters(), char_under_cursor)
local is_on_trigger_for_show_on_insert = is_on_trigger
and not vim.tbl_contains(config_trigger.show_on_insert_blocked_trigger_characters, char_under_cursor)
local is_on_context_char = char_under_cursor:match(config_trigger.keyword_regex) ~= nil
and not vim.tbl_contains(config.show_on_insert_blocked_trigger_characters, char_under_cursor)
local is_on_context_char = char_under_cursor:match(config.keyword_regex) ~= nil

local insert_enter_on_trigger_character = config_trigger.show_on_insert_on_trigger_character
local insert_enter_on_trigger_character = config.show_on_insert_on_trigger_character
and is_on_trigger_for_show_on_insert
and ev.event == 'InsertEnter'

if config.windows.autocomplete.selection ~= 'auto_insert' or trigger.triggered_by ~= 'select' then
-- check if we're still within the bounds of the query used for the context
if trigger.within_query_bounds(vim.api.nvim_win_get_cursor(0)) then
trigger.show()
-- check if we're still within the bounds of the query used for the context
if trigger.within_query_bounds(vim.api.nvim_win_get_cursor(0)) then
trigger.show()

-- check if we've entered insert mode on a trigger character
-- or if we've moved onto a trigger character
elseif insert_enter_on_trigger_character or (is_on_trigger and trigger.context ~= nil) then
trigger.context = nil
trigger.triggered_by = nil
trigger.show({ trigger_character = char_under_cursor })
elseif insert_enter_on_trigger_character or (is_on_trigger and trigger.context ~= nil) then
trigger.context = nil
trigger.triggered_by = nil
trigger.show({ trigger_character = char_under_cursor })

-- show if we currently have a context, and we've moved outside of it's bounds by 1 char
elseif is_on_context_char and trigger.context ~= nil and cursor_col == trigger.context.bounds.start_col - 1 then
trigger.context = nil
trigger.triggered_by = nil
trigger.show()
elseif is_on_context_char and trigger.context ~= nil and cursor_col == trigger.context.bounds.start_col - 1 then
trigger.context = nil
trigger.triggered_by = nil
trigger.show()

-- otherwise hide
else
trigger.hide()
end
else
trigger.hide()
end

trigger.triggered_by = nil
end,
})

Expand All @@ -125,7 +127,26 @@ function trigger.activate_autocmds()
return trigger
end

--- @param opts { trigger_character: string } | nil
-- todo: extract into an autocmd module
-- hack: there's likely edge cases with this since we can't know for sure
-- if the autocmds will fire for cursor_moved afaik
function trigger.ignore_autocmds_for_callback(cb)
local cursor_before = vim.api.nvim_win_get_cursor(0)
local changed_tick_before = vim.api.nvim_buf_get_changedtick(0)

cb()

local cursor_after = vim.api.nvim_win_get_cursor(0)
local changed_tick_after = vim.api.nvim_buf_get_changedtick(0)

local is_insert_mode = vim.api.nvim_get_mode().mode == 'i'
trigger.ignore_next_text_changed = changed_tick_after ~= changed_tick_before and is_insert_mode
-- todo: does this guarantee that the CursorMovedI event will fire?
trigger.ignore_next_cursor_moved = (cursor_after[1] ~= cursor_before[1] or cursor_after[2] ~= cursor_before[2])
and is_insert_mode
end

--- @param opts { trigger_character?: string, send_upstream?: boolean } | nil
function trigger.show(opts)
opts = opts or {}

Expand All @@ -142,15 +163,15 @@ function trigger.show(opts)
bufnr = vim.api.nvim_get_current_buf(),
cursor = cursor,
line = vim.api.nvim_buf_get_lines(0, cursor[1] - 1, cursor[1], false)[1],
bounds = trigger.get_context_bounds(config_trigger.keyword_regex),
bounds = trigger.get_context_bounds(config.keyword_regex),
trigger = {
kind = opts.trigger_character and vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter
or vim.lsp.protocol.CompletionTriggerKind.Invoked,
character = opts.trigger_character,
},
}

trigger.event_targets.on_show(trigger.context)
if opts.send_upstream ~= false then trigger.event_targets.on_show(trigger.context) end
end

--- @param callback fun(context: blink.cmp.Context)
Expand Down
44 changes: 23 additions & 21 deletions lua/blink/cmp/windows/autocomplete.lua
Original file line number Diff line number Diff line change
Expand Up @@ -160,34 +160,36 @@ end

--- @param line number
local function select(line)
local auto_insert = config.windows.autocomplete.selection == 'auto_insert'

local prev_selected_item = autocomplete.get_selected_item()

autocomplete.set_has_selected(true)
vim.api.nvim_win_set_cursor(autocomplete.win:get_win(), { line, 0 })

local selected_item = autocomplete.get_selected_item()

if auto_insert and selected_item ~= nil then
local text_edit = text_edits_lib.get_from_item(selected_item)

if selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
text_edit.newText = selected_item.label
end

if
prev_selected_item ~= nil and prev_selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet
then
local current_col = vim.api.nvim_win_get_cursor(0)[2]
text_edit.range.start.character = current_col - #prev_selected_item.label
end

text_edits_lib.apply_text_edits(selected_item.client_id, { text_edit })
vim.api.nvim_win_set_cursor(0, {
text_edit.range.start.line + 1,
text_edit.range.start.character + #text_edit.newText,
})
-- when auto_insert is enabled, we immediately apply the text edit
-- todo: move this to the accept module
if config.windows.autocomplete.selection == 'auto_insert' and selected_item ~= nil then
require('blink.cmp.trigger.completion').ignore_autocmds_for_callback(function()
local text_edit = text_edits_lib.get_from_item(selected_item)

if selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
text_edit.newText = selected_item.label
end

if
prev_selected_item ~= nil and prev_selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet
then
local current_col = vim.api.nvim_win_get_cursor(0)[2]
text_edit.range.start.character = current_col - #prev_selected_item.label
end

text_edits_lib.apply_text_edits(selected_item.client_id, { text_edit })
vim.api.nvim_win_set_cursor(0, {
text_edit.range.start.line + 1,
text_edit.range.start.character + #text_edit.newText,
})
end)
end

autocomplete.event_targets.on_select(selected_item, autocomplete.context)
Expand Down

0 comments on commit 19e1312

Please sign in to comment.