diff options
| author | Mike Vink <mike@pionative.com> | 2025-01-19 13:52:52 +0100 |
|---|---|---|
| committer | Mike Vink <mike@pionative.com> | 2025-01-19 13:52:52 +0100 |
| commit | b77413ff8f59f380612074f0c9bd49093d8db695 (patch) | |
| tree | 32c39a811ba96ed4ab0a1c81cce9f8d518ed7e31 /lua/blink/cmp/lib/buffer_events.lua | |
Squashed 'mut/neovim/pack/plugins/start/blink.cmp/' content from commit 1cc3b1a
git-subtree-dir: mut/neovim/pack/plugins/start/blink.cmp
git-subtree-split: 1cc3b1a908fbcfd15451c4772759549724f38524
Diffstat (limited to 'lua/blink/cmp/lib/buffer_events.lua')
| -rw-r--r-- | lua/blink/cmp/lib/buffer_events.lua | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/lua/blink/cmp/lib/buffer_events.lua b/lua/blink/cmp/lib/buffer_events.lua new file mode 100644 index 0000000..dcca8b8 --- /dev/null +++ b/lua/blink/cmp/lib/buffer_events.lua @@ -0,0 +1,148 @@ +--- Exposes three events (cursor moved, char added, insert leave) for triggers to use. +--- Notably, when "char added" is fired, the "cursor moved" event will not be fired. +--- Unlike in regular neovim, ctrl + c and buffer switching will trigger "insert leave" + +--- @class blink.cmp.BufferEvents +--- @field has_context fun(): boolean +--- @field show_in_snippet boolean +--- @field ignore_next_text_changed boolean +--- @field ignore_next_cursor_moved boolean +--- +--- @field new fun(opts: blink.cmp.BufferEventsOptions): blink.cmp.BufferEvents +--- @field listen fun(self: blink.cmp.BufferEvents, opts: blink.cmp.BufferEventsListener) +--- @field suppress_events_for_callback fun(self: blink.cmp.BufferEvents, cb: fun()) + +--- @class blink.cmp.BufferEventsOptions +--- @field has_context fun(): boolean +--- @field show_in_snippet boolean + +--- @class blink.cmp.BufferEventsListener +--- @field on_char_added fun(char: string, is_ignored: boolean) +--- @field on_cursor_moved fun(event: 'CursorMoved' | 'InsertEnter', is_ignored: boolean) +--- @field on_insert_leave fun() + +--- @type blink.cmp.BufferEvents +--- @diagnostic disable-next-line: missing-fields +local buffer_events = {} + +function buffer_events.new(opts) + return setmetatable({ + has_context = opts.has_context, + show_in_snippet = opts.show_in_snippet, + ignore_next_text_changed = false, + ignore_next_cursor_moved = false, + }, { __index = buffer_events }) +end + +--- Normalizes the autocmds + ctrl+c into a common api and handles ignored events +function buffer_events:listen(opts) + local snippet = require('blink.cmp.config').snippets + + local last_char = '' + vim.api.nvim_create_autocmd('InsertCharPre', { + callback = function() + if snippet.active() and not self.show_in_snippet and not self.has_context() then return end + last_char = vim.v.char + end, + }) + + vim.api.nvim_create_autocmd('TextChangedI', { + callback = function() + if not require('blink.cmp.config').enabled() then return end + if snippet.active() and not self.show_in_snippet and not self.has_context() then return end + + local is_ignored = self.ignore_next_text_changed + self.ignore_next_text_changed = false + + -- no characters added so let cursormoved handle it + if last_char == '' then return end + + opts.on_char_added(last_char, is_ignored) + + last_char = '' + end, + }) + + vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'InsertEnter' }, { + callback = function(ev) + -- only fire a CursorMoved event (notable not CursorMovedI) + -- when jumping between tab stops in a snippet while showing the menu + if + ev.event == 'CursorMoved' + and (vim.api.nvim_get_mode().mode ~= 'v' or not self.has_context() or not snippet.active()) + then + return + end + + local is_cursor_moved = ev.event == 'CursorMoved' or ev.event == 'CursorMovedI' + + local is_ignored = is_cursor_moved and self.ignore_next_cursor_moved + if is_cursor_moved then self.ignore_next_cursor_moved = false end + + -- characters added so let textchanged handle it + if last_char ~= '' then return end + + if not require('blink.cmp.config').enabled() then return end + if not self.show_in_snippet and not self.has_context() and snippet.active() then return end + + opts.on_cursor_moved(is_cursor_moved and 'CursorMoved' or ev.event, is_ignored) + end, + }) + + -- definitely leaving the context + vim.api.nvim_create_autocmd({ 'ModeChanged', 'BufLeave' }, { + callback = function() + last_char = '' + -- HACK: when using vim.snippet.expand, the mode switches from insert -> normal -> visual -> select + -- so we schedule to ignore the intermediary modes + -- TODO: deduplicate requests + vim.schedule(function() + if not vim.tbl_contains({ 'i', 's' }, vim.api.nvim_get_mode().mode) then opts.on_insert_leave() end + end) + end, + }) + + -- ctrl+c doesn't trigger InsertLeave so handle it separately + local ctrl_c = vim.api.nvim_replace_termcodes('<C-c>', true, true, true) + vim.on_key(function(key) + if key == ctrl_c then + vim.schedule(function() + local mode = vim.api.nvim_get_mode().mode + if mode ~= 'i' then + last_char = '' + opts.on_insert_leave() + end + end) + end + end) +end + +--- Suppresses autocmd events for the duration of the callback +--- 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 buffer_events:suppress_events_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' + + self.ignore_next_text_changed = changed_tick_after ~= changed_tick_before and is_insert_mode + + -- HACK: the cursor may move from position (1, 1) to (1, 0) and back to (1, 1) during the callback + -- This will trigger a CursorMovedI event, but we can't detect it simply by checking the cursor position + -- since they're equal before vs after the callback. So instead, we always mark the cursor as ignored in + -- insert mode, but if the cursor was equal, we undo the ignore after a small delay, which practically guarantees + -- that the CursorMovedI event will fire + -- TODO: It could make sense to override the nvim_win_set_cursor function and mark as ignored if it's called + -- on the current buffer + local cursor_moved = cursor_after[1] ~= cursor_before[1] or cursor_after[2] ~= cursor_before[2] + self.ignore_next_cursor_moved = is_insert_mode + if not cursor_moved then vim.defer_fn(function() self.ignore_next_cursor_moved = false end, 10) end +end + +return buffer_events |
