From 6da1023bb30b615c1f82f11d3faf56a80f1dcb38 Mon Sep 17 00:00:00 2001 From: Liam Dyer Date: Wed, 22 Jan 2025 17:42:35 -0500 Subject: [PATCH] feat: signature commands and more events Closes #816 Closes #89 --- docs/configuration/keymap.md | 8 ++++++ docs/configuration/reference.md | 10 ++++++- docs/configuration/signature.md | 1 + lua/blink/cmp/completion/brackets/config.lua | 2 +- lua/blink/cmp/completion/trigger/context.lua | 2 +- lua/blink/cmp/completion/trigger/init.lua | 17 +++-------- lua/blink/cmp/completion/trigger/utils.lua | 30 -------------------- lua/blink/cmp/config/keymap.lua | 2 ++ lua/blink/cmp/config/signature.lua | 14 +++++++-- lua/blink/cmp/fuzzy/init.lua | 8 ++++++ lua/blink/cmp/init.lua | 18 ++++++++++++ lua/blink/cmp/keymap/presets.lua | 6 ++++ lua/blink/cmp/lib/utils.lua | 28 ++++++++++++++++++ lua/blink/cmp/signature/init.lua | 6 ++-- lua/blink/cmp/signature/trigger.lua | 15 ++++++---- lua/blink/cmp/sources/lib/init.lua | 8 +++--- lua/blink/cmp/sources/lsp/init.lua | 2 +- 17 files changed, 116 insertions(+), 61 deletions(-) delete mode 100644 lua/blink/cmp/completion/trigger/utils.lua diff --git a/docs/configuration/keymap.md b/docs/configuration/keymap.md index f3d241ad..2bfecb21 100644 --- a/docs/configuration/keymap.md +++ b/docs/configuration/keymap.md @@ -56,6 +56,8 @@ keymap = { - Optionally use `function(cmp) cmp.scroll_documentation_up(4) end` to scroll by a specific number of lines - `scroll_documentation_down`: Scrolls the documentation down by 4 lines - Optionally use `function(cmp) cmp.scroll_documentation_down(4) end` to scroll by a specific number of lines +- `show_signature`: Shows the signature help window +- `hide_signature`: Hides the signature help window - `snippet_forward`: Jumps to the next snippet placeholder - `snippet_backward`: Jumps to the previous snippet placeholder - `fallback`: Runs the next non-blink keymap, or runs the built-in neovim binding @@ -96,6 +98,8 @@ Set the preset to `none` to disable the presets [''] = { 'snippet_forward', 'fallback' }, [''] = { 'snippet_backward', 'fallback' }, + +[''] = { 'show_signature', 'hide_signature' }, ``` ### `super-tab` @@ -123,6 +127,8 @@ You may want to set `completion.trigger.show_in_snippet = false` or use `complet [''] = { 'scroll_documentation_up', 'fallback' }, [''] = { 'scroll_documentation_down', 'fallback' }, + +[''] = { 'show_signature', 'hide_signature' }, ``` ### `enter` @@ -144,4 +150,6 @@ You may want to set `completion.list.selection.preselect = false`. See more info [''] = { 'scroll_documentation_up', 'fallback' }, [''] = { 'scroll_documentation_down', 'fallback' }, + +[''] = { 'show_signature', 'hide_signature' }, ``` diff --git a/docs/configuration/reference.md b/docs/configuration/reference.md index a9a3ca46..4d211538 100644 --- a/docs/configuration/reference.md +++ b/docs/configuration/reference.md @@ -307,9 +307,17 @@ completion.ghost_text = { signature = { enabled = false, trigger = { + -- Show the signature help automatically + enabled = true, + -- Show the signature help window after typing any of alphanumerics, `-` or `_` + show_on_keyword = false, blocked_trigger_characters = {}, blocked_retrigger_characters = {}, - -- When true, will show the signature help window when the cursor comes after a trigger character when entering insert mode + -- Show the signature help window after typing a trigger character + show_on_trigger_character = true, + -- Show the signature help window when entering insert mode + show_on_insert = false, + -- Show the signature help window when the cursor comes after a trigger character when entering insert mode show_on_insert_on_trigger_character = true, }, window = { diff --git a/docs/configuration/signature.md b/docs/configuration/signature.md index 701d2557..b4e8765f 100644 --- a/docs/configuration/signature.md +++ b/docs/configuration/signature.md @@ -14,3 +14,4 @@ signature = { enabled = true } +You may want to set `signature.window.show_documentation = false` to only show the signature, and not the documentation. diff --git a/lua/blink/cmp/completion/brackets/config.lua b/lua/blink/cmp/completion/brackets/config.lua index 0bc23e3a..d8fdbe2e 100644 --- a/lua/blink/cmp/completion/brackets/config.lua +++ b/lua/blink/cmp/completion/brackets/config.lua @@ -38,7 +38,7 @@ return { by_filetype = { -- ignore `use` imports rust = function(ctx) return ctx.line:find('^%s*use%s') == nil end, - -- ignore import statements + -- ignore `from` and `import` statements python = function(ctx) return ctx.line:find('^%s*import%s') == nil and ctx.line:find('^%s*from%s') == nil end, }, }, diff --git a/lua/blink/cmp/completion/trigger/context.lua b/lua/blink/cmp/completion/trigger/context.lua index ea3df9b9..60d10660 100644 --- a/lua/blink/cmp/completion/trigger/context.lua +++ b/lua/blink/cmp/completion/trigger/context.lua @@ -1,4 +1,4 @@ --- TODO: remove the end_col field from ContextBounds +-- TODO: move the get_line, get_cursor, etc.. to a separate lib --- @class blink.cmp.ContextBounds --- @field line string diff --git a/lua/blink/cmp/completion/trigger/init.lua b/lua/blink/cmp/completion/trigger/init.lua index 1f7addbf..1844922f 100644 --- a/lua/blink/cmp/completion/trigger/init.lua +++ b/lua/blink/cmp/completion/trigger/init.lua @@ -12,7 +12,6 @@ --- @field hide_emitter blink.cmp.EventEmitter<{}> --- --- @field activate fun() ---- @field is_keyword_character fun(char: string): boolean --- @field is_trigger_character fun(char: string, is_show_on_x?: boolean): boolean --- @field suppress_events_for_callback fun(cb: fun()) --- @field show_if_on_trigger_character fun(opts?: { is_accept?: boolean }) @@ -31,7 +30,8 @@ local config = require('blink.cmp.config').completion.trigger local context = require('blink.cmp.completion.trigger.context') -local utils = require('blink.cmp.completion.trigger.utils') +local utils = require('blink.cmp.lib.utils') +local fuzzy = require('blink.cmp.fuzzy') --- @type blink.cmp.CompletionTrigger --- @diagnostic disable-next-line: missing-fields @@ -61,7 +61,7 @@ function trigger.activate() trigger.show({ trigger_kind = 'trigger_character', trigger_character = char }) -- character is part of a keyword - elseif trigger.is_keyword_character(char) and (config.show_on_keyword or trigger.context ~= nil) then + elseif fuzzy.is_keyword_character(char) and (config.show_on_keyword or trigger.context ~= nil) then trigger.show({ trigger_kind = 'keyword' }) -- nothing matches so hide @@ -75,7 +75,7 @@ function trigger.activate() local cursor_col = cursor[2] local char_under_cursor = utils.get_char_at_cursor() - local is_keyword = trigger.is_keyword_character(char_under_cursor) + local is_keyword = fuzzy.is_keyword_character(char_under_cursor) -- we were told to ignore the cursor moved event, so we update the context -- but don't send an on_show event upstream @@ -141,15 +141,6 @@ function trigger.activate() }) end -function trigger.is_keyword_character(char) - -- special case for hyphen, since we don't consider a lone hyphen to be a keyword - if char == '-' then return true end - - local success, keyword_start_col, keyword_end_col = - pcall(require('blink.cmp.fuzzy').get_keyword_range, char, #char, 'prefix') - return success and keyword_start_col ~= keyword_end_col -end - function trigger.is_trigger_character(char, is_show_on_x) local sources = require('blink.cmp.sources.lib') local is_trigger = vim.tbl_contains(sources.get_trigger_characters(context.get_mode()), char) diff --git a/lua/blink/cmp/completion/trigger/utils.lua b/lua/blink/cmp/completion/trigger/utils.lua deleted file mode 100644 index b2878c25..00000000 --- a/lua/blink/cmp/completion/trigger/utils.lua +++ /dev/null @@ -1,30 +0,0 @@ -local context = require('blink.cmp.completion.trigger.context') -local utils = {} - ---- Gets the full Unicode character at cursor position ---- @return string -function utils.get_char_at_cursor() - local line = context.get_line() - if line == '' then return '' end - local cursor_col = context.get_cursor()[2] - - -- Find the start of the UTF-8 character - local start_col = cursor_col - while start_col > 1 do - local char = string.byte(line:sub(start_col, start_col)) - if char < 0x80 or char > 0xBF then break end - start_col = start_col - 1 - end - - -- Find the end of the UTF-8 character - local end_col = cursor_col - while end_col < #line do - local char = string.byte(line:sub(end_col + 1, end_col + 1)) - if char < 0x80 or char > 0xBF then break end - end_col = end_col + 1 - end - - return line:sub(start_col, end_col) -end - -return utils diff --git a/lua/blink/cmp/config/keymap.lua b/lua/blink/cmp/config/keymap.lua index 8607c750..8022589a 100644 --- a/lua/blink/cmp/config/keymap.lua +++ b/lua/blink/cmp/config/keymap.lua @@ -139,6 +139,8 @@ function keymap.validate(config) 'hide_documentation', 'scroll_documentation_up', 'scroll_documentation_down', + 'show_signature', + 'hide_signature', 'snippet_forward', 'snippet_backward', } diff --git a/lua/blink/cmp/config/signature.lua b/lua/blink/cmp/config/signature.lua index 7cd9190a..23e5d377 100644 --- a/lua/blink/cmp/config/signature.lua +++ b/lua/blink/cmp/config/signature.lua @@ -4,9 +4,13 @@ --- @field window blink.cmp.SignatureWindowConfig --- @class (exact) blink.cmp.SignatureTriggerConfig +--- @field enabled boolean Show the signature help automatically +--- @field show_on_keyword boolean Show the signature help window after typing any of alphanumerics, `-` or `_` --- @field blocked_trigger_characters string[] --- @field blocked_retrigger_characters string[] ---- @field show_on_insert_on_trigger_character boolean When true, will show the signature help window when the cursor comes after a trigger character when entering insert mode +--- @field show_on_trigger_character boolean Show the signature help window after typing a trigger character +--- @field show_on_insert boolean Show the signature help window when entering insert mode +--- @field show_on_insert_on_trigger_character boolean Show the signature help window when the cursor comes after a trigger character when entering insert mode --- @class (exact) blink.cmp.SignatureWindowConfig --- @field min_width number @@ -27,8 +31,11 @@ local signature = { enabled = false, trigger = { enabled = true, + show_on_keyword = false, blocked_trigger_characters = {}, blocked_retrigger_characters = {}, + show_on_trigger_character = true, + show_on_insert = false, show_on_insert_on_trigger_character = true, }, window = { @@ -41,7 +48,7 @@ local signature = { scrollbar = false, direction_priority = { 'n', 's' }, treesitter_highlighting = true, - show_documentation = true, + show_documentation = false, }, }, } @@ -54,8 +61,11 @@ function signature.validate(config) }, config) validate('signature.trigger', { enabled = { config.trigger.enabled, 'boolean' }, + show_on_keyword = { config.trigger.show_on_keyword, 'boolean' }, blocked_trigger_characters = { config.trigger.blocked_trigger_characters, 'table' }, blocked_retrigger_characters = { config.trigger.blocked_retrigger_characters, 'table' }, + show_on_trigger_character = { config.trigger.show_on_trigger_character, 'boolean' }, + show_on_insert = { config.trigger.show_on_insert, 'boolean' }, show_on_insert_on_trigger_character = { config.trigger.show_on_insert_on_trigger_character, 'boolean' }, }, config.trigger) validate('signature.window', { diff --git a/lua/blink/cmp/fuzzy/init.lua b/lua/blink/cmp/fuzzy/init.lua index ad4db037..ee5d8b51 100644 --- a/lua/blink/cmp/fuzzy/init.lua +++ b/lua/blink/cmp/fuzzy/init.lua @@ -106,6 +106,14 @@ function fuzzy.get_keyword_range(line, col, range) return require('blink.cmp.fuzzy.rust').get_keyword_range(line, col, range == 'full') end +function fuzzy.is_keyword_character(char) + -- special case for hyphen, since we don't consider a lone hyphen to be a keyword + if char == '-' then return true end + + local keyword_start_col, keyword_end_col = fuzzy.get_keyword_range(char, #char, 'prefix') + return keyword_start_col ~= keyword_end_col +end + --- @param item blink.cmp.CompletionItem --- @param line string --- @param col number diff --git a/lua/blink/cmp/init.lua b/lua/blink/cmp/init.lua index 2f396850..47c4a17b 100644 --- a/lua/blink/cmp/init.lua +++ b/lua/blink/cmp/init.lua @@ -210,6 +210,24 @@ function cmp.scroll_documentation_down(count) return true end +--- Check if the signature help window is visible +--- @return boolean +function cmp.is_signature_visible() return require('blink.cmp.signature.window').win:is_open() end + +--- Show the signature help window +function cmp.show_signature() + if cmp.is_signature_visible() then return end + require('blink.cmp.signature.trigger').show() + return true +end + +--- Hide the signature help window +function cmp.hide_signature() + if not cmp.is_signature_visible() then return end + require('blink.cmp.signature.trigger').hide() + return true +end + --- Check if a snippet is active, optionally filtering by direction --- @param filter? { direction?: number } function cmp.snippet_active(filter) return require('blink.cmp.config').snippets.active(filter) end diff --git a/lua/blink/cmp/keymap/presets.lua b/lua/blink/cmp/keymap/presets.lua index 746a66a4..037afd96 100644 --- a/lua/blink/cmp/keymap/presets.lua +++ b/lua/blink/cmp/keymap/presets.lua @@ -16,6 +16,8 @@ local presets = { [''] = { 'snippet_forward', 'fallback' }, [''] = { 'snippet_backward', 'fallback' }, + + [''] = { 'show_signature', 'hide_signature' }, }, ['super-tab'] = { @@ -42,6 +44,8 @@ local presets = { [''] = { 'scroll_documentation_up', 'fallback' }, [''] = { 'scroll_documentation_down', 'fallback' }, + + [''] = { 'show_signature', 'hide_signature' }, }, enter = { @@ -59,6 +63,8 @@ local presets = { [''] = { 'scroll_documentation_up', 'fallback' }, [''] = { 'scroll_documentation_down', 'fallback' }, + + [''] = { 'show_signature', 'hide_signature' }, }, } diff --git a/lua/blink/cmp/lib/utils.lua b/lua/blink/cmp/lib/utils.lua index 84bcc3db..a5e992fb 100644 --- a/lua/blink/cmp/lib/utils.lua +++ b/lua/blink/cmp/lib/utils.lua @@ -109,4 +109,32 @@ function utils.slice(arr, start, finish) return sliced end +--- Gets the full Unicode character at cursor position +--- @return string +function utils.get_char_at_cursor() + local context = require('blink.cmp.completion.trigger.context') + + local line = context.get_line() + if line == '' then return '' end + local cursor_col = context.get_cursor()[2] + + -- Find the start of the UTF-8 character + local start_col = cursor_col + while start_col > 1 do + local char = string.byte(line:sub(start_col, start_col)) + if char < 0x80 or char > 0xBF then break end + start_col = start_col - 1 + end + + -- Find the end of the UTF-8 character + local end_col = cursor_col + while end_col < #line do + local char = string.byte(line:sub(end_col + 1, end_col + 1)) + if char < 0x80 or char > 0xBF then break end + end_col = end_col + 1 + end + + return line:sub(start_col, end_col) +end + return utils diff --git a/lua/blink/cmp/signature/init.lua b/lua/blink/cmp/signature/init.lua index 8c700327..21746fe9 100644 --- a/lua/blink/cmp/signature/init.lua +++ b/lua/blink/cmp/signature/init.lua @@ -3,14 +3,16 @@ local signature = {} function signature.setup() local trigger = require('blink.cmp.signature.trigger') trigger.activate() - local window = require('blink.cmp.signature.window') local sources = require('blink.cmp.sources.lib') + local window = require('blink.cmp.signature.window') trigger.show_emitter:on(function(event) local context = event.context sources.cancel_signature_help() - sources.get_signature_help(context, function(signature_help) + sources.get_signature_help(context):map(function(signature_helps) + -- TODO: pick intelligently + local signature_help = signature_helps[1] if signature_help ~= nil and trigger.context ~= nil and trigger.context.id == context.id then trigger.set_active_signature_help(signature_help) window.open_with_signature_help(context, signature_help) diff --git a/lua/blink/cmp/signature/trigger.lua b/lua/blink/cmp/signature/trigger.lua index 2d874c5f..d2a098a6 100644 --- a/lua/blink/cmp/signature/trigger.lua +++ b/lua/blink/cmp/signature/trigger.lua @@ -28,6 +28,8 @@ --- @field set_active_signature_help fun(signature_help: lsp.SignatureHelp) local config = require('blink.cmp.config').signature.trigger +local utils = require('blink.cmp.lib.utils') +local fuzzy = require('blink.cmp.fuzzy') --- @type blink.cmp.SignatureTrigger --- @diagnostic disable-next-line: missing-fields @@ -46,14 +48,15 @@ function trigger.activate() }) trigger.buffer_events:listen({ on_char_added = function() - 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 char_under_cursor = utils.get_char_at_cursor() -- ignore if disabled if not require('blink.cmp.config').enabled() then return trigger.hide() + elseif config.show_on_keyword and fuzzy.is_keyword_character(char_under_cursor) then + return trigger.show({ trigger_character = char_under_cursor }) -- character forces a trigger according to the sources, refresh the existing context if it exists - elseif trigger.is_trigger_character(char_under_cursor) then + elseif config.show_on_trigger_character and trigger.is_trigger_character(char_under_cursor) then return trigger.show({ trigger_character = char_under_cursor }) -- character forces a re-trigger according to the sources, show if we have a context elseif trigger.is_trigger_character(char_under_cursor, true) and trigger.context ~= nil then @@ -61,14 +64,15 @@ function trigger.activate() end end, on_cursor_moved = function(event) - 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 char_under_cursor = utils.get_char_at_cursor() local is_on_trigger = trigger.is_trigger_character(char_under_cursor) if config.show_on_insert_on_trigger_character and is_on_trigger and event == 'InsertEnter' then trigger.show({ trigger_character = char_under_cursor }) elseif event == 'CursorMoved' and trigger.context ~= nil then trigger.show() + elseif event == 'InsertEnter' and config.show_on_insert then + trigger.show() end end, on_insert_leave = function() trigger.hide() end, @@ -76,7 +80,6 @@ function trigger.activate() end function trigger.is_trigger_character(char, is_retrigger) - -- TODO: should the get_mode() be moved to sources or somewhere else? local mode = require('blink.cmp.completion.trigger.context').get_mode() local res = require('blink.cmp.sources.lib').get_signature_help_trigger_characters(mode) diff --git a/lua/blink/cmp/sources/lib/init.lua b/lua/blink/cmp/sources/lib/init.lua index 9b56cf5e..c491e58c 100644 --- a/lua/blink/cmp/sources/lib/init.lua +++ b/lua/blink/cmp/sources/lib/init.lua @@ -23,7 +23,7 @@ local config = require('blink.cmp.config') --- @field execute fun(context: blink.cmp.Context, item: blink.cmp.CompletionItem): blink.cmp.Task --- --- @field get_signature_help_trigger_characters fun(mode: blink.cmp.Mode): { trigger_characters: string[], retrigger_characters: string[] } ---- @field get_signature_help fun(context: blink.cmp.SignatureHelpContext, callback: fun(signature_help: lsp.SignatureHelp | nil)) +--- @field get_signature_help fun(context: blink.cmp.SignatureHelpContext): blink.cmp.Task --- @field cancel_signature_help fun() --- --- @field reload fun(provider?: string) @@ -218,16 +218,16 @@ function sources.get_signature_help_trigger_characters(mode) return { trigger_characters = trigger_characters, retrigger_characters = retrigger_characters } end -function sources.get_signature_help(context, callback) +function sources.get_signature_help(context) local tasks = {} for _, source in pairs(sources.providers) do table.insert(tasks, source:get_signature_help(context)) end sources.current_signature_help = async.task.await_all(tasks):map(function(signature_helps) - signature_helps = vim.tbl_filter(function(signature_help) return signature_help ~= nil end, signature_helps) - callback(signature_helps[1]) + return vim.tbl_filter(function(signature_help) return signature_help ~= nil end, signature_helps) end) + return sources.current_signature_help end function sources.cancel_signature_help() diff --git a/lua/blink/cmp/sources/lsp/init.lua b/lua/blink/cmp/sources/lsp/init.lua index 7a7457d0..ad2772ef 100644 --- a/lua/blink/cmp/sources/lsp/init.lua +++ b/lua/blink/cmp/sources/lsp/init.lua @@ -142,7 +142,7 @@ function lsp:get_signature_help(context, callback) table.insert(signature_helps, signature_help) end end - -- todo: pick intelligently + -- TODO: pick intelligently callback(signature_helps[1]) end) end