summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/keymap
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
committerMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
commitb77413ff8f59f380612074f0c9bd49093d8db695 (patch)
tree32c39a811ba96ed4ab0a1c81cce9f8d518ed7e31 /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.lua132
-rw-r--r--lua/blink/cmp/keymap/fallback.lua91
-rw-r--r--lua/blink/cmp/keymap/init.lua74
-rw-r--r--lua/blink/cmp/keymap/presets.lua72
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