Skip to content

Commit ad347a1

Browse files
committed
feat: WIP sources rework
1 parent e837718 commit ad347a1

File tree

5 files changed

+248
-18
lines changed

5 files changed

+248
-18
lines changed

lua/blink/cmp/sources/lib/async.lua

Whitespace-only changes.

lua/blink/cmp/sources/lib/init.lua

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
local config = require('blink.cmp.config')
2+
local sources = {
3+
registered = {
4+
lsp = require('blink.cmp.sources.lsp'),
5+
buffer = require('blink.cmp.sources.buffer'),
6+
snippets = require('blink.cmp.sources.snippets'),
7+
},
8+
9+
-- hack: sweet mother of all hacks
10+
last_in_flight_id = -1,
11+
in_flight_id = {
12+
lsp = -1,
13+
buffer = -1,
14+
snippets = -1,
15+
},
16+
17+
sources_responses = {},
18+
current_context_id = -1,
19+
on_completions_callback = function(_) end,
20+
}
21+
22+
--- @return string[]
23+
function sources.get_trigger_characters()
24+
local blocked_trigger_characters = {}
25+
for _, char in ipairs(config.trigger.blocked_trigger_characters) do
26+
blocked_trigger_characters[char] = true
27+
end
28+
29+
local trigger_characters = {}
30+
for _, source in pairs(sources.registered) do
31+
if source.get_trigger_characters ~= nil then
32+
local source_trigger_characters = source.get_trigger_characters()
33+
for _, char in ipairs(source_trigger_characters) do
34+
if not blocked_trigger_characters[char] then table.insert(trigger_characters, char) end
35+
end
36+
end
37+
end
38+
return trigger_characters
39+
end
40+
41+
function sources.listen_on_completions(callback) sources.on_completions_callback = callback end
42+
43+
--- @param context blink.cmp.ShowContext
44+
function sources.completions(context)
45+
-- a new context means we should refetch everything
46+
local is_new_context = context.id ~= sources.current_context_id
47+
sources.current_context_id = context.id
48+
49+
if is_new_context then sources.sources_responses = {} end
50+
51+
for source_name, source in pairs(sources.registered) do
52+
-- the source indicates we should refetch when this character is typed
53+
local trigger_characters = source.get_trigger_characters ~= nil and source.get_trigger_characters() or {}
54+
local trigger_character = context.trigger_character
55+
and vim.tbl_contains(trigger_characters, context.trigger_character)
56+
-- the source indicates the previous results were incomplete and should be refetched on the next trigger
57+
local previous_incomplete = sources.sources_responses[source_name] ~= nil
58+
and sources.sources_responses[source_name].isIncomplete
59+
-- check if we have no data and no calls are in flight
60+
local no_data = sources.sources_responses[source_name] == nil and sources.in_flight_id[source_name] == -1
61+
62+
-- if none of these are true, we can use the existing cached results
63+
if is_new_context or trigger_character or previous_incomplete or no_data then
64+
if source.cancel_completions ~= nil then source.cancel_completions() end
65+
66+
-- register the call
67+
sources.last_in_flight_id = sources.last_in_flight_id + 1
68+
local in_flight_id = sources.last_in_flight_id
69+
sources.in_flight_id[source_name] = in_flight_id
70+
71+
-- get the reason for the trigger
72+
local trigger_context = trigger_character
73+
and { kind = vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter, character = context.trigger_character }
74+
or previous_incomplete and { kind = vim.lsp.protocol.CompletionTriggerKind.TriggerForIncompleteCompletions }
75+
or { kind = vim.lsp.protocol.CompletionTriggerKind.Invoked }
76+
77+
-- fetch them completions
78+
-- fixme: what if we refetch due to incomplete items or a trigger_character? the context trigger id wouldnt change
79+
-- change so stale data would be returned if the source doesn't support cancellation
80+
local cursor_column = vim.api.nvim_win_get_cursor(0)[2]
81+
local source_context = vim.fn.deepcopy(context)
82+
source_context.trigger = trigger_context
83+
source.completions(source_context, function(items)
84+
-- a new call was made or this one was cancelled
85+
if sources.in_flight_id[source_name] ~= in_flight_id then return end
86+
sources.in_flight_id[source_name] = -1
87+
88+
sources.add_source_completions(source_name, items, cursor_column)
89+
if not sources.some_in_flight() then sources.send_completions(context) end
90+
end)
91+
end
92+
end
93+
94+
-- no completions will be in flight if none of them ran,
95+
-- so we send the completions
96+
if not sources.some_in_flight() then sources.send_completions(context) end
97+
end
98+
99+
--- @param source_name string
100+
--- @param source_response blink.cmp.CompletionResponse
101+
--- @param cursor_column number
102+
function sources.add_source_completions(source_name, source_response, cursor_column)
103+
for _, item in ipairs(source_response.items) do
104+
item.source = source_name
105+
item.cursor_column = cursor_column
106+
end
107+
108+
sources.sources_responses[source_name] = source_response
109+
end
110+
111+
--- @return boolean
112+
function sources.some_in_flight()
113+
for _, in_flight in pairs(sources.in_flight_id) do
114+
if in_flight ~= -1 then return true end
115+
end
116+
return false
117+
end
118+
119+
--- @param context blink.cmp.ShowContext
120+
function sources.send_completions(context)
121+
local sources_responses = sources.sources_responses
122+
-- apply source filters
123+
for _, source in pairs(sources.registered) do
124+
if source.filter_completions ~= nil then
125+
sources_responses = source.filter_completions(context, sources_responses)
126+
end
127+
end
128+
129+
-- flatten the items
130+
local flattened_items = {}
131+
for source_name, response in pairs(sources.sources_responses) do
132+
local source = sources.registered[source_name]
133+
if source.should_show_completions == nil or source.should_show_completions(context, sources_responses) then
134+
vim.list_extend(flattened_items, response.items)
135+
end
136+
end
137+
138+
sources.on_completions_callback(context, flattened_items)
139+
end
140+
141+
function sources.cancel_completions()
142+
for source_name, source in pairs(sources.registered) do
143+
sources.in_flight_id[source_name] = -1
144+
if source.cancel_completions ~= nil then source.cancel_completions() end
145+
end
146+
end
147+
148+
--- @param item blink.cmp.CompletionItem
149+
--- @param callback fun(resolved_item: blink.cmp.CompletionItem | nil)
150+
--- @return fun(): nil Cancelation function
151+
function sources.resolve(item, callback)
152+
local item_source = sources.registered[item.source]
153+
if item_source == nil or item_source.resolve == nil then
154+
callback(nil)
155+
return function() end
156+
end
157+
return item_source.resolve(item, callback) or function() end
158+
end
159+
160+
return sources

lua/blink/cmp/sources/lib/source.lua

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
local source = {}
2+
3+
--- @param config blink.cmp.SourceProviderConfig
4+
--- @return blink.cmp.SourceProvider
5+
function source.new(config)
6+
local self = setmetatable({}, { __index = source })
7+
self.module = require(config[1]).new(config.opts or {})
8+
9+
self.fallback_for = config.fallback_for
10+
self.keyword_length = config.keyword_length
11+
self.score_offset = config.score_offset
12+
self.deduplicate = config.deduplicate
13+
self.override = config.override or {}
14+
15+
return self
16+
end
17+
18+
function source:get_trigger_characters()
19+
if self.override.get_trigger_characters ~= nil then
20+
return self.override.get_trigger_characters(self.module.get_trigger_characters)
21+
end
22+
if self.module.get_trigger_characters == nil then return {} end
23+
return self.module.get_trigger_characters()
24+
end
25+
26+
function source:completions(context, callback)
27+
if self.override.completions ~= nil then
28+
return self.override.completions(context, callback, self.module.completions)
29+
end
30+
self.module.completions(context, callback)
31+
end
32+
33+
function source:filter_completions(context, source_responses)
34+
if self.override.filter_completions ~= nil then
35+
return self.override.filter_completions(context, source_responses, self.module.filter_completions)
36+
end
37+
if self.module.filter_completions == nil then return source_responses end
38+
return self.module.filter_completions(context, source_responses)
39+
end
40+
41+
function source:resolve(item, callback)
42+
if self.override.resolve ~= nil then return self.override.resolve(item, callback, self.module.resolve) end
43+
if self.module.resolve == nil then return callback(item) end
44+
self.module.resolve(item, callback)
45+
end
46+
47+
function source:cancel_completions()
48+
if self.override.cancel_completions ~= nil then
49+
return self.override.cancel_completions(self.module.cancel_completions)
50+
end
51+
if self.module.cancel_completions == nil then return end
52+
self.module.cancel_completions()
53+
end
54+
55+
return source

lua/blink/cmp/sources/lib/types.lua

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--- @class blink.cmp.CompletionTriggerContext
2+
--- @field kind number
3+
--- @field character string
4+
---
5+
--- @class blink.cmp.CompletionContext : blink.cmp.ShowContext
6+
--- @field trigger blink.cmp.CompletionTriggerContext | nil
7+
---
8+
--- @class blink.cmp.CompletionResponse
9+
--- @field isIncomplete boolean
10+
--- @field items blink.cmp.CompletionItem[]
11+
---
12+
--- @class blink.cmp.Source
13+
--- @field get_trigger_characters (fun(): string[]) | nil
14+
--- @field completions fun(context: blink.cmp.CompletionContext, callback: fun(response: blink.cmp.CompletionResponse))
15+
--- @field filter_completions (fun(context: blink.cmp.CompletionContext, source_responses: table<string, blink.cmp.CompletionResponse>): blink.cmp.CompletionItem[]) | nil
16+
--- @field cancel_completions fun() | nil
17+
--- @field should_show_completions (fun(context: blink.cmp.CompletionContext, source_responses: table<string, blink.cmp.CompletionResponse>): boolean) | nil
18+
--- @field resolve (fun(item: blink.cmp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): ((fun(): nil) | nil)) | nil
19+
---
20+
--- @class blink.cmp.SourceProvider
21+
--- @field module blink.cmp.Source
22+
--- @field fallback_for string[]
23+
--- @field keyword_length number
24+
--- @field score_offset number
25+
--- @field deduplicate blink.cmp.DeduplicateConfig
26+
--- @field override blink.cmp.OverrideConfig
27+
---
28+
--- @field get_trigger_characters fun(self: blink.cmp.SourceProvider): string[]
29+
--- @field completions fun(self: blink.cmp.SourceProvider, context: blink.cmp.CompletionContext, callback: fun(response: blink.cmp.CompletionResponse))
30+
--- @field filter_completions fun(self: blink.cmp.SourceProvider, context: blink.cmp.CompletionContext, source_responses: table<string, blink.cmp.CompletionResponse>): blink.cmp.CompletionItem[]
31+
--- @field cancel_completions fun(self: blink.cmp.SourceProvider)
32+
--- @field should_show_completions fun(self: blink.cmp.SourceProvider, context: blink.cmp.CompletionContext, source_responses: table<string, blink.cmp.CompletionResponse>): boolean
33+
--- @field resolve fun(self: blink.cmp.SourceProvider, item: blink.cmp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): ((fun(): nil) | nil)

lua/blink/cmp/sources/types.lua

-18
This file was deleted.

0 commit comments

Comments
 (0)