Skip to content

Commit 7fea65c

Browse files
committed
feat!: rework sources system and configuration
Closes #144
1 parent 516190b commit 7fea65c

File tree

15 files changed

+261
-131
lines changed

15 files changed

+261
-131
lines changed

lua/blink/cmp/config.lua

+36-16
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,22 @@
5050
--- @field signature_help? blink.cmp.SignatureHelpTriggerConfig
5151

5252
--- @class blink.cmp.SourceConfig
53-
--- @field providers? blink.cmp.SourceProviderConfig[]
53+
--- @field completion? string[] | fun(ctx?: blink.cmp.Context): string[]
54+
--- @field providers? table<string, blink.cmp.SourceProviderConfig>
5455
---
5556
--- @class blink.cmp.SourceProviderConfig
56-
--- @field [1]? string
5757
--- @field name string
58-
--- @field fallback_for? string[] | nil
59-
--- @field keyword_length? number | nil
60-
--- @field score_offset? number | nil
61-
--- @field deduplicate? blink.cmp.DeduplicateConfig | nil
62-
--- @field trigger_characters? string[] | nil
63-
--- @field opts? table | nil
58+
--- @field module string
59+
--- @field enabled? boolean | fun(ctx?: blink.cmp.Context): boolean
60+
--- @field opts? table
61+
--- @field transform_items? fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[]
62+
--- @field should_show_items? boolean | number | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean
63+
--- @field max_items? number | fun(ctx: blink.cmp.Context, enabled_sources: string[], items: blink.cmp.CompletionItem[]): number
64+
--- @field min_keyword_length? number | fun(ctx: blink.cmp.Context, enabled_sources: string[]): number
65+
--- @field fallback_for? string[] | fun(ctx: blink.cmp.Context, enabled_sources: string[]): string[]
66+
--- @field score_offset? number | fun(ctx: blink.cmp.Context, enabled_sources: string[]): number
67+
--- @field deduplicate? blink.cmp.DeduplicateConfig
68+
--- @field override? blink.cmp.SourceOverride
6469
---
6570
--- @class blink.cmp.DeduplicateConfig
6671
--- @field enabled? boolean
@@ -244,15 +249,30 @@ local config = {
244249
},
245250

246251
sources = {
247-
-- similar to nvim-cmp's sources, but we point directly to the source's lua module
248-
-- multiple groups can be provided, where it'll fallback to the next group if the previous
249-
-- returns no completion items
250-
-- WARN: This API will have breaking changes during the beta
252+
-- list of enabled providers
253+
completion = { 'lsp', 'path', 'snippets', 'buffer' },
254+
255+
-- table of providers to configure
251256
providers = {
252-
{ 'blink.cmp.sources.lsp', name = 'LSP' },
253-
{ 'blink.cmp.sources.path', name = 'Path', score_offset = 3 },
254-
{ 'blink.cmp.sources.snippets', name = 'Snippets', score_offset = -3 },
255-
{ 'blink.cmp.sources.buffer', name = 'Buffer', fallback_for = { 'LSP' } },
257+
lsp = {
258+
name = 'LSP',
259+
module = 'blink.cmp.sources.lsp',
260+
},
261+
path = {
262+
name = 'Path',
263+
module = 'blink.cmp.sources.path',
264+
score_offset = 3,
265+
},
266+
snippets = {
267+
name = 'Snippets',
268+
module = 'blink.cmp.sources.snippets',
269+
score_offset = -3,
270+
},
271+
buffer = {
272+
name = 'Buffer',
273+
module = 'blink.cmp.sources.buffer',
274+
fallback_for = { 'lsp' },
275+
},
256276
},
257277
},
258278

lua/blink/cmp/fuzzy/frecency.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ use std::time::{SystemTime, UNIX_EPOCH};
1010
struct CompletionItemKey {
1111
label: String,
1212
kind: u32,
13-
source: String,
13+
source_id: String,
1414
}
1515

1616
impl From<&LspItem> for CompletionItemKey {
1717
fn from(item: &LspItem) -> Self {
1818
Self {
1919
label: item.label.clone(),
2020
kind: item.kind,
21-
source: item.source.clone(),
21+
source_id: item.source_id.clone(),
2222
}
2323
}
2424
}

lua/blink/cmp/fuzzy/fuzzy.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub struct LspItem {
1717
pub filter_text: Option<String>,
1818
pub kind: u32,
1919
pub score_offset: Option<i32>,
20-
pub source: String,
20+
pub source_id: String,
2121
}
2222

2323
impl FromLua<'_> for LspItem {
@@ -28,15 +28,15 @@ impl FromLua<'_> for LspItem {
2828
let filter_text: Option<String> = tab.get("filterText").ok();
2929
let kind: u32 = tab.get("kind").unwrap_or_default();
3030
let score_offset: Option<i32> = tab.get("score_offset").ok();
31-
let source: String = tab.get("source").unwrap_or_default();
31+
let source_id: String = tab.get("source_id").unwrap_or_default();
3232

3333
Ok(LspItem {
3434
label,
3535
sort_text,
3636
filter_text,
3737
kind,
3838
score_offset,
39-
source,
39+
source_id,
4040
})
4141
} else {
4242
Err(mlua::Error::FromLuaConversionError {

lua/blink/cmp/sources/lib/context.lua

+36-23
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
local utils = require('blink.cmp.sources.lib.utils')
22
local async = require('blink.cmp.sources.lib.async')
3+
4+
--- @class blink.cmp.SourcesContext
5+
--- @field id number
6+
--- @field sources table<string, blink.cmp.SourceProvider>
7+
--- @field active_request blink.cmp.Task | nil
8+
--- @field queued_request_context blink.cmp.Context | nil
9+
--- @field on_completions_callback fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
10+
---
11+
--- @field new fun(context: blink.cmp.Context, sources: table<string, blink.cmp.SourceProvider>, on_completions_callback: fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])): blink.cmp.SourcesContext
12+
--- @field get_completions fun(self: blink.cmp.SourcesContext, context: blink.cmp.Context)
13+
--- @field get_completions_for_sources fun(self: blink.cmp.SourcesContext, sources: table<string, blink.cmp.SourceProvider>, context: blink.cmp.Context): blink.cmp.Task
14+
--- @field get_completions_with_fallbacks fun(self: blink.cmp.SourcesContext, context: blink.cmp.Context, source: blink.cmp.SourceProvider, sources: table<string, blink.cmp.SourceProvider>): blink.cmp.Task
15+
--- @field destroy fun(self: blink.cmp.SourcesContext)
16+
17+
--- @type blink.cmp.SourcesContext
18+
--- @diagnostic disable-next-line: missing-fields
319
local sources_context = {}
420

5-
--- @param context blink.cmp.Context
6-
--- @param sources blink.cmp.SourceProvider[]
7-
--- @param on_completions_callback fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
821
function sources_context.new(context, sources, on_completions_callback)
922
local self = setmetatable({}, { __index = sources_context })
1023
self.id = context.id
@@ -18,7 +31,6 @@ function sources_context.new(context, sources, on_completions_callback)
1831
return self
1932
end
2033

21-
--- @param context blink.cmp.Context
2234
function sources_context:get_completions(context)
2335
assert(context.id == self.id, 'Requested completions on a sources context with a different context ID')
2436

@@ -43,13 +55,14 @@ function sources_context:get_completions(context)
4355
end)
4456
end
4557

46-
--- @param sources blink.cmp.SourceProvider[]
47-
--- @param context blink.cmp.Context
48-
--- @return blink.cmp.Task
4958
function sources_context:get_completions_for_sources(sources, context)
59+
local enabled_sources = vim.tbl_keys(sources)
60+
--- @type blink.cmp.SourceProvider[]
5061
local non_fallback_sources = vim.tbl_filter(
51-
function(source) return source.config.fallback_for == nil or #source.config.fallback_for == 0 end,
52-
sources
62+
function(source)
63+
return source.config.fallback_for == nil or #source.config.fallback_for(context, enabled_sources) == 0
64+
end,
65+
vim.tbl_values(sources)
5366
)
5467

5568
-- get completions for each non-fallback source
@@ -73,16 +86,19 @@ function sources_context:get_completions_for_sources(sources, context)
7386
:map(function(tasks_results)
7487
local is_cached = true
7588
local items = {}
76-
-- for each task, filter the items and add them to the list
77-
-- if the source should show the completions
89+
-- for each task, add them to the list if the source should show the items
7890
for idx, task_result in ipairs(tasks_results) do
7991
if task_result.status == async.STATUS.COMPLETED then
80-
is_cached = is_cached and (task_result.result.is_cached or false)
81-
local source = sources[idx]
92+
--- @type blink.cmp.SourceProvider
93+
local source = vim.tbl_values(sources)[idx]
8294
--- @type blink.cmp.CompletionResponse
8395
local response = task_result.result
84-
response.items = source:filter_completions(response)
85-
if source:should_show_completions(context, response) then vim.list_extend(items, response.items) end
96+
97+
is_cached = is_cached and (response.is_cached or false)
98+
99+
if source:should_show_items(context, enabled_sources, response) then
100+
vim.list_extend(items, response.items)
101+
end
86102
end
87103
end
88104
return { is_cached = is_cached, items = items }
@@ -95,22 +111,19 @@ end
95111

96112
--- Runs the source's get_completions function, falling back to other sources
97113
--- with fallback_for = { source.name } if the source returns no completion items
98-
--- @param context blink.cmp.Context
99-
--- @param source blink.cmp.SourceProvider
100-
--- @param sources blink.cmp.SourceProvider[]
101-
--- @return blink.cmp.Task
102114
--- TODO: When a source has multiple fallbacks, we may end up with duplicate completion items
103115
function sources_context:get_completions_with_fallbacks(context, source, sources)
116+
local enabled_sources = vim.tbl_keys(sources)
104117
local fallback_sources = vim.tbl_filter(
105118
function(fallback_source)
106-
return fallback_source.name ~= source.name
119+
return fallback_source.id ~= source.id
107120
and fallback_source.config.fallback_for ~= nil
108-
and vim.tbl_contains(fallback_source.config.fallback_for, source.name)
121+
and vim.tbl_contains(fallback_source.config.fallback_for(context), source.id)
109122
end,
110-
sources
123+
vim.tbl_values(sources)
111124
)
112125

113-
return source:get_completions(context):map(function(response)
126+
return source:get_completions(context, enabled_sources):map(function(response)
114127
-- source returned completions, no need to fallback
115128
if #response.items > 0 or #fallback_sources == 0 then return response end
116129

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

+50-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
local async = require('blink.cmp.sources.lib.async')
22
local config = require('blink.cmp.config')
3+
4+
--- @class blink.cmp.Sources
5+
--- @field current_context blink.cmp.SourcesContext | nil
6+
--- @field current_signature_help blink.cmp.Task | nil
7+
--- @field sources_registered boolean
8+
--- @field providers table<string, blink.cmp.SourceProvider>
9+
--- @field on_completions_callback fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
10+
---
11+
--- @field register fun()
12+
--- @field get_enabled_providers fun(context?: blink.cmp.Context): table<string, blink.cmp.SourceProvider>
13+
--- @field get_trigger_characters fun(): string[]
14+
--- @field request_completions fun(context: blink.cmp.Context)
15+
--- @field cancel_completions fun()
16+
--- @field listen_on_completions fun(callback: fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[]))
17+
--- @field resolve fun(item: blink.cmp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): (fun(): nil) | nil
18+
--- @field get_signature_help_trigger_characters fun(): { trigger_characters: string[], retrigger_characters: string[] }
19+
--- @field get_signature_help fun(context: blink.cmp.SignatureHelpContext, callback: fun(signature_help: lsp.SignatureHelp | nil)): (fun(): nil) | nil
20+
--- @field cancel_signature_help fun()
21+
--- @field reload fun()
22+
23+
--- @type blink.cmp.Sources
24+
--- @diagnostic disable-next-line: missing-fields
325
local sources = {
426
current_context = nil,
527
sources_registered = false,
@@ -8,24 +30,38 @@ local sources = {
830
}
931

1032
function sources.register()
11-
assert(#sources.providers == 0, 'Sources have already been registered')
33+
assert(not sources.sources_registered, 'Sources have already been registered')
34+
sources.sources_registered = true
35+
36+
for key, source_config in pairs(config.sources.providers) do
37+
sources.providers[key] = require('blink.cmp.sources.lib.provider').new(key, source_config)
38+
end
39+
end
40+
41+
function sources.get_enabled_providers(context)
42+
local mode_providers = type(config.sources.completion) == 'function' and config.sources.completion(context)
43+
or config.sources.completion
44+
--- @cast mode_providers string[]
1245

13-
for _, source_config in ipairs(config.sources.providers) do
14-
table.insert(sources.providers, require('blink.cmp.sources.lib.provider').new(source_config))
46+
--- @type table<string, blink.cmp.SourceProvider>
47+
local providers = {}
48+
for key, provider in pairs(sources.providers) do
49+
if provider.config.enabled(context) and vim.tbl_contains(mode_providers, key) then providers[key] = provider end
1550
end
51+
return providers
1652
end
1753

1854
--- Completion ---
1955

20-
--- @return string[]
2156
function sources.get_trigger_characters()
57+
local providers = sources.get_enabled_providers()
2258
local blocked_trigger_characters = {}
2359
for _, char in ipairs(config.trigger.completion.blocked_trigger_characters) do
2460
blocked_trigger_characters[char] = true
2561
end
2662

2763
local trigger_characters = {}
28-
for _, source in pairs(sources.providers) do
64+
for _, source in pairs(providers) do
2965
local source_trigger_characters = source:get_trigger_characters()
3066
for _, char in ipairs(source_trigger_characters) do
3167
if not blocked_trigger_characters[char] then table.insert(trigger_characters, char) end
@@ -36,14 +72,16 @@ end
3672

3773
function sources.listen_on_completions(callback) sources.on_completions_callback = callback end
3874

39-
--- @param context blink.cmp.Context
4075
function sources.request_completions(context)
4176
-- create a new context if the id changed or if we haven't created one yet
4277
local is_new_context = sources.current_context == nil or context.id ~= sources.current_context.id
4378
if is_new_context then
4479
if sources.current_context ~= nil then sources.current_context:destroy() end
45-
sources.current_context =
46-
require('blink.cmp.sources.lib.context').new(context, sources.providers, sources.on_completions_callback)
80+
sources.current_context = require('blink.cmp.sources.lib.context').new(
81+
context,
82+
sources.get_enabled_providers(context),
83+
sources.on_completions_callback
84+
)
4785
end
4886

4987
sources.current_context:get_completions(context)
@@ -58,13 +96,10 @@ end
5896

5997
--- Resolve ---
6098

61-
--- @param item blink.cmp.CompletionItem
62-
--- @param callback fun(resolved_item: blink.cmp.CompletionItem | nil)
63-
--- @return fun(): nil Cancelation function
6499
function sources.resolve(item, callback)
65100
local item_source = nil
66-
for _, source in ipairs(sources.providers) do
67-
if source.name == item.source then
101+
for _, source in pairs(sources.providers) do
102+
if source.id == item.source_id then
68103
item_source = source
69104
break
70105
end
@@ -82,7 +117,6 @@ end
82117

83118
--- Signature help ---
84119

85-
--- @return { trigger_characters: string[], retrigger_characters: string[] }
86120
function sources.get_signature_help_trigger_characters()
87121
local blocked_trigger_characters = {}
88122
local blocked_retrigger_characters = {}
@@ -97,7 +131,7 @@ function sources.get_signature_help_trigger_characters()
97131
local retrigger_characters = {}
98132

99133
-- todo: should this be all source groups?
100-
for _, source in ipairs(sources.providers) do
134+
for _, source in pairs(sources.providers) do
101135
local res = source:get_signature_help_trigger_characters()
102136
for _, char in ipairs(res.trigger_characters) do
103137
if not blocked_trigger_characters[char] then table.insert(trigger_characters, char) end
@@ -109,11 +143,9 @@ function sources.get_signature_help_trigger_characters()
109143
return { trigger_characters = trigger_characters, retrigger_characters = retrigger_characters }
110144
end
111145

112-
--- @param context blink.cmp.SignatureHelpContext
113-
--- @param callback fun(signature_helps: lsp.SignatureHelp)
114146
function sources.get_signature_help(context, callback)
115147
local tasks = {}
116-
for _, source in ipairs(sources.providers) do
148+
for _, source in pairs(sources.providers) do
117149
table.insert(tasks, source:get_signature_help(context))
118150
end
119151
sources.current_signature_help = async.task.await_all(tasks):map(function(tasks_results)

0 commit comments

Comments
 (0)