1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
-- Handles hiding and showing the signature help window. When a user types a trigger character
-- (provided by the sources), we create a new `context`. This can be used downstream to determine
-- if we should make new requests to the sources or not. When a user types a re-trigger character,
-- we update the context's re-trigger counter.
-- TODO: ensure this always calls *after* the completion trigger to avoid increasing latency
--- @class blink.cmp.SignatureHelpContext
--- @field id number
--- @field bufnr number
--- @field cursor number[]
--- @field line string
--- @field is_retrigger boolean
--- @field active_signature_help lsp.SignatureHelp | nil
--- @field trigger { kind: lsp.SignatureHelpTriggerKind, character?: string }
--- @class blink.cmp.SignatureTrigger
--- @field current_context_id number
--- @field context? blink.cmp.SignatureHelpContext
--- @field show_emitter blink.cmp.EventEmitter<{ context: blink.cmp.SignatureHelpContext }>
--- @field hide_emitter blink.cmp.EventEmitter<{}>
--- @field buffer_events? blink.cmp.BufferEvents
---
--- @field activate fun()
--- @field is_trigger_character fun(char: string, is_retrigger?: boolean): boolean
--- @field show_if_on_trigger_character fun()
--- @field show fun(opts?: { trigger_character: string })
--- @field hide fun()
--- @field set_active_signature_help fun(signature_help: lsp.SignatureHelp)
local config = require('blink.cmp.config').signature.trigger
--- @type blink.cmp.SignatureTrigger
--- @diagnostic disable-next-line: missing-fields
local trigger = {
current_context_id = -1,
--- @type blink.cmp.SignatureHelpContext | nil
context = nil,
show_emitter = require('blink.cmp.lib.event_emitter').new('signature_help_show'),
hide_emitter = require('blink.cmp.lib.event_emitter').new('signature_help_hide'),
}
function trigger.activate()
trigger.buffer_events = require('blink.cmp.lib.buffer_events').new({
show_in_snippet = true,
has_context = function() return trigger.context ~= nil end,
})
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)
-- ignore if disabled
if not require('blink.cmp.config').enabled() then
return trigger.hide()
-- character forces a trigger according to the sources, refresh the existing context if it exists
elseif 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
return trigger.show()
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 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()
end
end,
on_insert_leave = function() trigger.hide() end,
})
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)
local trigger_characters = is_retrigger and res.retrigger_characters or res.trigger_characters
local is_trigger = vim.tbl_contains(trigger_characters, char)
local blocked_trigger_characters = is_retrigger and config.blocked_retrigger_characters
or config.blocked_trigger_characters
local is_blocked = vim.tbl_contains(blocked_trigger_characters, char)
return is_trigger and not is_blocked
end
function trigger.show_if_on_trigger_character()
if require('blink.cmp.completion.trigger.context').get_mode() ~= 'default' then return end
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)
if trigger.is_trigger_character(char_under_cursor) then trigger.show({ trigger_character = char_under_cursor }) end
end
function trigger.show(opts)
opts = opts or {}
-- update context
local cursor = vim.api.nvim_win_get_cursor(0)
if trigger.context == nil then trigger.current_context_id = trigger.current_context_id + 1 end
trigger.context = {
id = trigger.current_context_id,
bufnr = vim.api.nvim_get_current_buf(),
cursor = cursor,
line = vim.api.nvim_buf_get_lines(0, cursor[1] - 1, cursor[1], false)[1],
trigger = {
kind = opts.trigger_character and vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter
or vim.lsp.protocol.CompletionTriggerKind.Invoked,
character = opts.trigger_character,
},
is_retrigger = trigger.context ~= nil,
active_signature_help = trigger.context and trigger.context.active_signature_help or nil,
}
trigger.show_emitter:emit({ context = trigger.context })
end
function trigger.hide()
if not trigger.context then return end
trigger.context = nil
trigger.hide_emitter:emit()
end
function trigger.set_active_signature_help(signature_help)
if not trigger.context then return end
trigger.context.active_signature_help = signature_help
end
return trigger
|