Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve auto_show flexibility #697

Merged
merged 2 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lua/blink/cmp/completion/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function completion.setup()
-- since this was performed asynchronously, we check if the context has changed
if trigger.context == nil or event.context.id ~= trigger.context.id then return end
-- don't show the list if prefetching results
if trigger.prefetch then return end
if event.context.trigger.kind == 'prefetch' then return end

-- don't show if all the sources that defined the trigger character returned no items
if event.context.trigger.character ~= nil then
Expand Down
16 changes: 13 additions & 3 deletions lua/blink/cmp/completion/trigger/context.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
--- @field cursor number[]
--- @field line string
--- @field bounds blink.cmp.ContextBounds
--- @field trigger { kind: number, character: string | nil }
--- @field trigger blink.cmp.ContextTrigger
--- @field providers string[]
---
--- @field new fun(opts: blink.cmp.ContextOpts): blink.cmp.Context
Expand All @@ -28,9 +28,18 @@
--- @field get_bounds fun(line: string, cursor: number[]): blink.cmp.ContextBounds
--- @field get_regex_around_cursor fun(range: string, regex_str: string, exclude_from_prefix_regex_str: string): { start_col: number, length: number }

--- @class blink.cmp.ContextTrigger
--- @field initial_kind blink.cmp.CompletionTriggerKind The trigger kind when the context was first created
--- @field initial_character? string The trigger character when initial_kind == 'trigger_character'
--- @field kind blink.cmp.CompletionTriggerKind The current trigger kind
--- @field character? string The trigger character when kind == 'trigger_character'

--- @class blink.cmp.ContextOpts
--- @field id number
--- @field providers string[]
--- @field initial_trigger_kind blink.cmp.CompletionTriggerKind
--- @field initial_trigger_character? string
--- @field trigger_kind blink.cmp.CompletionTriggerKind
--- @field trigger_character? string

local keyword_regex = vim.regex(require('blink.cmp.config').completion.keyword.regex)
Expand All @@ -51,8 +60,9 @@ function context.new(opts)
line = line,
bounds = context.get_bounds(line, cursor),
trigger = {
kind = opts.trigger_character and vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter
or vim.lsp.protocol.CompletionTriggerKind.Invoked,
initial_kind = opts.initial_trigger_kind,
initial_character = opts.initial_trigger_character,
kind = opts.trigger_kind,
character = opts.trigger_character,
},
providers = opts.providers,
Expand Down
41 changes: 23 additions & 18 deletions lua/blink/cmp/completion/trigger/init.lua
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
--- @alias blink.cmp.CompletionTriggerKind 'manual' | 'prefetch' | 'keyword' | 'trigger_character'
---
-- Handles hiding and showing the completion window. When a user types a trigger character
-- (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.

--- @class blink.cmp.CompletionTrigger
--- @field buffer_events blink.cmp.BufferEvents
--- @field cmdline_events blink.cmp.CmdlineEvents
--- @field current_context_id number
--- @field context? blink.cmp.Context
--- @field prefetch? boolean
--- @field show_emitter blink.cmp.EventEmitter<{ context: blink.cmp.Context }>
--- @field hide_emitter blink.cmp.EventEmitter<{}>
---
--- @field activate fun()
--- @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 })
--- @field show fun(opts?: { trigger_character?: string, force?: boolean, send_upstream?: boolean, providers?: string[], prefetch?: boolean })
--- @field show fun(opts?: { trigger_kind: blink.cmp.CompletionTriggerKind, trigger_character?: string, force?: boolean, send_upstream?: boolean, providers?: string[], prefetch?: boolean })
--- @field hide fun()
--- @field within_query_bounds fun(cursor: number[]): boolean
--- @field get_bounds fun(regex: vim.regex, line: string, cursor: number[]): blink.cmp.ContextBounds
Expand Down Expand Up @@ -45,18 +45,18 @@ function trigger.activate()
-- we were told to ignore the text changed event, so we update the context
-- but don't send an on_show event upstream
if is_ignored then
if trigger.context ~= nil then trigger.show({ send_upstream = false }) end
if trigger.context ~= nil then trigger.show({ send_upstream = false, trigger_kind = 'keyword' }) end

-- 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 trigger.is_trigger_character(char) and (config.show_on_trigger_character or trigger.context ~= nil) then
trigger.context = nil
trigger.show({ trigger_character = char })
trigger.show({ trigger_kind = 'trigger_character', trigger_character = char })

-- character is part of a keyword
-- character is part of a keyword
elseif keyword_regex:match_str(char) ~= nil and (config.show_on_keyword or trigger.context ~= nil) then
trigger.show()
trigger.show({ trigger_kind = 'keyword' })

-- nothing matches so hide
-- nothing matches so hide
else
trigger.hide()
end
Expand All @@ -66,7 +66,7 @@ function trigger.activate()
-- we were told to ignore the cursor moved event, so we update the context
-- but don't send an on_show event upstream
if is_ignored and event == 'CursorMoved' then
if trigger.context ~= nil then trigger.show({ send_upstream = false }) end
if trigger.context ~= nil then trigger.show({ send_upstream = false, trigger_kind = 'keyword' }) end
return
end

Expand All @@ -82,21 +82,21 @@ function trigger.activate()

-- check if we're still within the bounds of the query used for the context
if trigger.context ~= nil and trigger.context:within_query_bounds(cursor) then
trigger.show()
trigger.show({ trigger_kind = 'keyword' })

-- check if we've entered insert mode on a trigger character
elseif insert_enter_on_trigger_character then
trigger.context = nil
trigger.show({ trigger_character = char_under_cursor })
trigger.show({ trigger_kind = 'trigger_character', 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_keyword_char and trigger.context ~= nil and cursor_col == trigger.context.bounds.start_col - 1 then
trigger.context = nil
trigger.show()
trigger.show({ trigger_kind = 'keyword' })

-- prefetch completions without opening window on InsertEnter
elseif event == 'InsertEnter' and config.prefetch_on_insert then
trigger.show({ prefetch = true })
trigger.show({ trigger_kind = 'prefetch' })

-- otherwise hide
else
Expand Down Expand Up @@ -157,7 +157,7 @@ function trigger.show_if_on_trigger_character(opts)
local char_under_cursor = context.get_line():sub(cursor_col, cursor_col)

if trigger.is_trigger_character(char_under_cursor, true) then
trigger.show({ trigger_character = char_under_cursor })
trigger.show({ trigger_kind = 'trigger_character', trigger_character = char_under_cursor })
end
end

Expand Down Expand Up @@ -186,9 +186,14 @@ function trigger.show(opts)
or (trigger.context and trigger.context.providers)
or require('blink.cmp.sources.lib').get_enabled_provider_ids(context.get_mode())

trigger.context =
context.new({ id = trigger.current_context_id, providers = providers, trigger_character = opts.trigger_character })
trigger.prefetch = opts.prefetch == true
trigger.context = context.new({
id = trigger.current_context_id,
providers = providers,
initial_trigger_kind = trigger.context and trigger.context.trigger.kind or opts.trigger_kind,
initial_trigger_character = trigger.context and trigger.context.trigger.character or opts.trigger_character,
trigger_kind = opts.trigger_kind,
trigger_character = opts.trigger_character,
})

if opts.send_upstream ~= false then trigger.show_emitter:emit({ context = trigger.context }) end
end
Expand Down
3 changes: 2 additions & 1 deletion lua/blink/cmp/completion/windows/menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ function menu.open_with_items(context, items)
if not menu.renderer then menu.renderer = require('blink.cmp.completion.windows.render').new(config.draw) end
menu.renderer:draw(context, menu.win:get_buf(), items)

if menu.auto_show then
local auto_show = type(menu.auto_show) == 'function' and menu.auto_show(context, items) or menu.auto_show
if auto_show then
menu.open()
menu.update_position()
end
Expand Down
4 changes: 2 additions & 2 deletions lua/blink/cmp/config/completion/menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ local validate = require('blink.cmp.config.utils').validate
--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none'
--- @field direction_priority ("n" | "s")[] Which directions to show the window, falling back to the next direction when there's not enough space
--- @field order blink.cmp.CompletionMenuOrderConfig TODO: implement
--- @field auto_show boolean Whether to automatically show the window when new completion items are available
--- @field auto_show boolean | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean Whether to automatically show the window when new completion items are available
--- @field cmdline_position fun(): number[] Screen coordinates (0-indexed) of the command line
--- @field draw blink.cmp.Draw Controls how the completion items are rendered on the popup window

Expand Down Expand Up @@ -151,7 +151,7 @@ function window.validate(config)
'one of: "n", "s"',
},
order = { config.order, 'table' },
auto_show = { config.auto_show, 'boolean' },
auto_show = { config.auto_show, { 'boolean', 'function' } },
cmdline_position = { config.cmdline_position, 'function' },
draw = { config.draw, 'table' },
}, config)
Expand Down
6 changes: 5 additions & 1 deletion lua/blink/cmp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ function cmp.show(opts)

vim.schedule(function()
require('blink.cmp.completion.windows.menu').auto_show = true
require('blink.cmp.completion.trigger').show({ force = true, providers = opts and opts.providers })
require('blink.cmp.completion.trigger').show({
force = true,
providers = opts and opts.providers,
trigger_kind = 'manual',
})
end)
return true
end
Expand Down
9 changes: 5 additions & 4 deletions lua/blink/cmp/sources/lsp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ function lsp:get_completions(context, callback)
local cancel_fns = {}
for _, client in pairs(clients) do
local params = vim.lsp.util.make_position_params(0, client.offset_encoding)
params.context = { triggerKind = context.trigger.kind }
if context.trigger.kind == CompletionTriggerKind.TriggerCharacter then
params.context.triggerCharacter = context.trigger.character
end
params.context = {
triggerKind = context.trigger.kind == 'trigger_character' and CompletionTriggerKind.TriggerCharacter
or CompletionTriggerKind.Invoked,
}
if context.trigger.kind == 'trigger_character' then params.context.triggerCharacter = context.trigger.character end

local _, request_id = client.request('textDocument/completion', params, function(err, result)
if err or result == nil then
Expand Down