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/keymap | |
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/keymap')
| -rw-r--r-- | lua/blink/cmp/keymap/apply.lua | 132 | ||||
| -rw-r--r-- | lua/blink/cmp/keymap/fallback.lua | 91 | ||||
| -rw-r--r-- | lua/blink/cmp/keymap/init.lua | 74 | ||||
| -rw-r--r-- | lua/blink/cmp/keymap/presets.lua | 72 |
4 files changed, 369 insertions, 0 deletions
diff --git a/lua/blink/cmp/keymap/apply.lua b/lua/blink/cmp/keymap/apply.lua new file mode 100644 index 0000000..b10bd0f --- /dev/null +++ b/lua/blink/cmp/keymap/apply.lua @@ -0,0 +1,132 @@ +local apply = {} + +local snippet_commands = { 'snippet_forward', 'snippet_backward' } + +--- Applies the keymaps to the current buffer +--- @param keys_to_commands table<string, blink.cmp.KeymapCommand[]> +function apply.keymap_to_current_buffer(keys_to_commands) + -- skip if we've already applied the keymaps + for _, mapping in ipairs(vim.api.nvim_buf_get_keymap(0, 'i')) do + if mapping.desc == 'blink.cmp' then return end + end + + -- insert mode: uses both snippet and insert commands + for key, commands in pairs(keys_to_commands) do + if #commands == 0 then goto continue end + + local fallback = require('blink.cmp.keymap.fallback').wrap('i', key) + apply.set('i', key, function() + if not require('blink.cmp.config').enabled() then return fallback() end + + for _, command in ipairs(commands) do + -- special case for fallback + if command == 'fallback' then + return fallback() + + -- run user defined functions + elseif type(command) == 'function' then + if command(require('blink.cmp')) then return end + + -- otherwise, run the built-in command + elseif require('blink.cmp')[command]() then + return + end + end + end) + + ::continue:: + end + + -- snippet mode: uses only snippet commands + for key, commands in pairs(keys_to_commands) do + local has_snippet_command = false + for _, command in ipairs(commands) do + if vim.tbl_contains(snippet_commands, command) or type(command) == 'function' then has_snippet_command = true end + end + if not has_snippet_command or #commands == 0 then goto continue end + + local fallback = require('blink.cmp.keymap.fallback').wrap('s', key) + apply.set('s', key, function() + if not require('blink.cmp.config').enabled() then return fallback() end + + for _, command in ipairs(keys_to_commands[key] or {}) do + -- special case for fallback + if command == 'fallback' then + return fallback() + + -- run user defined functions + elseif type(command) == 'function' then + if command(require('blink.cmp')) then return end + + -- only run snippet commands + elseif vim.tbl_contains(snippet_commands, command) then + local did_run = require('blink.cmp')[command]() + if did_run then return end + end + end + end) + + ::continue:: + end +end + +function apply.cmdline_keymaps(keys_to_commands) + -- cmdline mode: uses only insert commands + for key, commands in pairs(keys_to_commands) do + local has_insert_command = false + for _, command in ipairs(commands) do + has_insert_command = has_insert_command or not vim.tbl_contains(snippet_commands, command) + end + if not has_insert_command or #commands == 0 then goto continue end + + local fallback = require('blink.cmp.keymap.fallback').wrap('c', key) + apply.set('c', key, function() + for _, command in ipairs(commands) do + -- special case for fallback + if command == 'fallback' then + return fallback() + + -- run user defined functions + elseif type(command) == 'function' then + if command(require('blink.cmp')) then return end + + -- otherwise, run the built-in command + elseif not vim.tbl_contains(snippet_commands, command) then + local did_run = require('blink.cmp')[command]() + if did_run then return end + end + end + end) + + ::continue:: + end +end + +--- @param mode string +--- @param key string +--- @param callback fun(): string | nil +function apply.set(mode, key, callback) + if mode == 'c' then + vim.api.nvim_set_keymap(mode, key, '', { + callback = callback, + expr = true, + -- silent must be false for fallback to work + -- otherwise, you get very weird behavior + silent = false, + noremap = true, + replace_keycodes = false, + desc = 'blink.cmp', + }) + else + vim.api.nvim_buf_set_keymap(0, mode, key, '', { + callback = callback, + expr = true, + silent = true, + noremap = true, + replace_keycodes = false, + desc = 'blink.cmp', + }) + end +end + +return apply diff --git a/lua/blink/cmp/keymap/fallback.lua b/lua/blink/cmp/keymap/fallback.lua new file mode 100644 index 0000000..a73d69e --- /dev/null +++ b/lua/blink/cmp/keymap/fallback.lua @@ -0,0 +1,91 @@ +local fallback = {} + +--- Add missing types. Remove when fixed upstream +---@class blink.cmp.Fallback : vim.api.keyset.keymap +---@field lhs string +---@field mode string +---@field rhs? string +---@field lhsraw? string +---@field buffer? number + +--- Gets the non blink.cmp global keymap for the given mode and key +--- @param mode string +--- @param key string +--- @return blink.cmp.Fallback | nil +function fallback.get_non_blink_global_mapping_for_key(mode, key) + local normalized_key = vim.api.nvim_replace_termcodes(key, true, true, true) + + -- get global mappings + local mappings = vim.api.nvim_get_keymap(mode) + + for _, mapping in ipairs(mappings) do + --- @cast mapping blink.cmp.Fallback + local mapping_key = vim.api.nvim_replace_termcodes(mapping.lhs, true, true, true) + if mapping_key == normalized_key and mapping.desc ~= 'blink.cmp' then return mapping end + end +end + +--- Gets the non blink.cmp buffer keymap for the given mode and key +--- @param mode string +--- @param key string +--- @return blink.cmp.Fallback? +function fallback.get_non_blink_buffer_mapping_for_key(mode, key) + local normalized_key = vim.api.nvim_replace_termcodes(key, true, true, true) + + local buffer_mappings = vim.api.nvim_buf_get_keymap(0, mode) + + for _, mapping in ipairs(buffer_mappings) do + --- @cast mapping blink.cmp.Fallback + local mapping_key = vim.api.nvim_replace_termcodes(mapping.lhs, true, true, true) + if mapping_key == normalized_key and mapping.desc ~= 'blink.cmp' then return mapping end + end +end + +--- Returns a function that will run the first non blink.cmp keymap for the given mode and key +--- @param mode string +--- @param key string +--- @return fun(): string? +function fallback.wrap(mode, key) + -- In default mode, there can't be multiple mappings on a single key for buffer local mappings + -- In cmdline mode, there can't be multiple mappings on a single key for global mappings + local buffer_mapping = mode ~= 'c' and fallback.get_non_blink_buffer_mapping_for_key(mode, key) + or fallback.get_non_blink_global_mapping_for_key(mode, key) + return function() + local mapping = buffer_mapping or fallback.get_non_blink_global_mapping_for_key(mode, key) + if mapping then return fallback.run_non_blink_keymap(mapping, key) end + return vim.api.nvim_replace_termcodes(key, true, true, true) + end +end + +--- Runs the first non blink.cmp keymap for the given mode and key +--- @param mapping blink.cmp.Fallback +--- @param key string +--- @return string | nil +function fallback.run_non_blink_keymap(mapping, key) + -- TODO: there's likely many edge cases here. the nvim-cmp version is lacking documentation + -- and is quite complex. we should look to see if we can simplify their logic + -- https://github.com/hrsh7th/nvim-cmp/blob/ae644feb7b67bf1ce4260c231d1d4300b19c6f30/lua/cmp/utils/keymap.lua + if type(mapping.callback) == 'function' then + -- with expr = true, which we use, we can't modify the buffer without scheduling + -- so if the keymap does not use expr, we must schedule it + if mapping.expr ~= 1 then + vim.schedule(mapping.callback) + return + end + + local expr = mapping.callback() + if type(expr) == 'string' and mapping.replace_keycodes == 1 then + expr = vim.api.nvim_replace_termcodes(expr, true, true, true) + end + return expr + elseif mapping.rhs then + local rhs = vim.api.nvim_replace_termcodes(mapping.rhs, true, true, true) + if mapping.expr == 1 then rhs = vim.api.nvim_eval(rhs) end + return rhs + end + + -- pass the key along as usual + return vim.api.nvim_replace_termcodes(key, true, true, true) +end + +return fallback diff --git a/lua/blink/cmp/keymap/init.lua b/lua/blink/cmp/keymap/init.lua new file mode 100644 index 0000000..a5e7009 --- /dev/null +++ b/lua/blink/cmp/keymap/init.lua @@ -0,0 +1,74 @@ +local keymap = {} + +--- Lowercases all keys in the mappings table +--- @param existing_mappings table<string, blink.cmp.KeymapCommand[]> +--- @param new_mappings table<string, blink.cmp.KeymapCommand[]> +--- @return table<string, blink.cmp.KeymapCommand[]> +function keymap.merge_mappings(existing_mappings, new_mappings) + local merged_mappings = vim.deepcopy(existing_mappings) + for new_key, new_mapping in pairs(new_mappings) do + -- normalize the keys and replace, since naively merging would not handle <C-a> == <c-a> + for existing_key, _ in pairs(existing_mappings) do + if + vim.api.nvim_replace_termcodes(existing_key, true, true, true) + == vim.api.nvim_replace_termcodes(new_key, true, true, true) + then + merged_mappings[existing_key] = new_mapping + goto continue + end + end + + -- key wasn't found, add it as per usual + merged_mappings[new_key] = new_mapping + + ::continue:: + end + return merged_mappings +end + +---@param keymap_config blink.cmp.BaseKeymapConfig +function keymap.get_mappings(keymap_config) + local mappings = vim.deepcopy(keymap_config) + + -- Handle preset + if mappings.preset then + local preset_keymap = require('blink.cmp.keymap.presets').get(mappings.preset) + + -- Remove 'preset' key from opts to prevent it from being treated as a keymap + mappings.preset = nil + + -- Merge the preset keymap with the user-defined keymaps + -- User-defined keymaps overwrite the preset keymaps + mappings = keymap.merge_mappings(preset_keymap, mappings) + end + return mappings +end + +function keymap.setup() + local config = require('blink.cmp.config') + local mappings = keymap.get_mappings(config.keymap) + -- We set on the buffer directly to avoid buffer-local keymaps (such as from autopairs) + -- from overriding our mappings. We also use InsertEnter to avoid conflicts with keymaps + -- applied on other autocmds, such as LspAttach used by nvim-lspconfig and most configs + vim.api.nvim_create_autocmd('InsertEnter', { + callback = function() + if not require('blink.cmp.config').enabled() then return end + require('blink.cmp.keymap.apply').keymap_to_current_buffer(mappings) + end, + }) + + -- This is not called when the plugin loads since it first checks if the binary is + -- installed. As a result, when lazy-loaded on InsertEnter, the event may be missed + if vim.api.nvim_get_mode().mode == 'i' and require('blink.cmp.config').enabled() then + require('blink.cmp.keymap.apply').keymap_to_current_buffer(mappings) + end + + -- Apply cmdline keymaps since they're global, if any sources are defined + local cmdline_sources = require('blink.cmp.config').sources.cmdline + if type(cmdline_sources) ~= 'table' or #cmdline_sources > 0 then + local cmdline_mappings = keymap.get_mappings(config.keymap.cmdline or config.keymap) + require('blink.cmp.keymap.apply').cmdline_keymaps(cmdline_mappings) + end +end + +return keymap diff --git a/lua/blink/cmp/keymap/presets.lua b/lua/blink/cmp/keymap/presets.lua new file mode 100644 index 0000000..1e7de16 --- /dev/null +++ b/lua/blink/cmp/keymap/presets.lua @@ -0,0 +1,72 @@ +local presets = { + none = {}, + + default = { + ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, + ['<C-e>'] = { 'cancel', 'fallback' }, + ['<C-y>'] = { 'select_and_accept' }, + + ['<C-p>'] = { 'select_prev', 'fallback' }, + ['<C-n>'] = { 'select_next', 'fallback' }, + + ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, + ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + + ['<Tab>'] = { 'snippet_forward', 'fallback' }, + ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + }, + + ['super-tab'] = { + ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, + ['<C-e>'] = { 'cancel', 'fallback' }, + + ['<Tab>'] = { + function(cmp) + if cmp.snippet_active() then + return cmp.accept() + else + return cmp.select_and_accept() + end + end, + 'snippet_forward', + 'fallback', + }, + ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + + ['<Up>'] = { 'select_prev', 'fallback' }, + ['<Down>'] = { 'select_next', 'fallback' }, + ['<C-p>'] = { 'select_prev', 'fallback' }, + ['<C-n>'] = { 'select_next', 'fallback' }, + + ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, + ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + }, + + enter = { + ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' }, + ['<C-e>'] = { 'cancel', 'fallback' }, + ['<CR>'] = { 'accept', 'fallback' }, + + ['<Tab>'] = { 'snippet_forward', 'fallback' }, + ['<S-Tab>'] = { 'snippet_backward', 'fallback' }, + + ['<Up>'] = { 'select_prev', 'fallback' }, + ['<Down>'] = { 'select_next', 'fallback' }, + ['<C-p>'] = { 'select_prev', 'fallback' }, + ['<C-n>'] = { 'select_next', 'fallback' }, + + ['<C-b>'] = { 'scroll_documentation_up', 'fallback' }, + ['<C-f>'] = { 'scroll_documentation_down', 'fallback' }, + }, +} + +--- Gets the preset keymap for the given preset name +--- @param name string +--- @return table<string, blink.cmp.KeymapCommand[]> +function presets.get(name) + local preset = presets[name] + if preset == nil then error('Invalid blink.cmp keymap preset: ' .. name) end + return preset +end + +return presets |
