Skip to content

Commit e3a811b

Browse files
committed
feat!: rework sources config structure and available options
Closes #144
1 parent 5cc63f0 commit e3a811b

File tree

10 files changed

+137
-107
lines changed

10 files changed

+137
-107
lines changed

README.md

+32-43
Original file line numberDiff line numberDiff line change
@@ -233,57 +233,47 @@ MiniDeps.add({
233233
},
234234

235235
sources = {
236-
-- similar to nvim-cmp's sources, but we point directly to the source's lua module
237-
-- multiple groups can be provided, where it'll fallback to the next group if the previous
238-
-- returns no completion items
239-
-- WARN: This API will have breaking changes during the beta
240-
providers = {
241-
{ 'blink.cmp.sources.lsp', name = 'LSP' },
242-
{ 'blink.cmp.sources.path', name = 'Path', score_offset = 3 },
243-
{ 'blink.cmp.sources.snippets', name = 'Snippets', score_offset = -3 },
244-
{ 'blink.cmp.sources.buffer', name = 'Buffer', fallback_for = { 'LSP' } },
245-
},
246-
-- WARN: **For reference only** to see what options are available. **See above for the default config**
236+
-- list of enabled providers
237+
completion = {
238+
enabled_providers = {'lsp', 'path', 'snippets', 'buffer' },
239+
}
240+
247241
providers = {
248-
-- all of these properties work on every source
249-
{
250-
'blink.cmp.sources.lsp',
251-
name = 'LSP',
252-
keyword_length = 0,
253-
score_offset = 0,
254-
trigger_characters = { 'f', 'o', 'o' },
242+
lsp = {
243+
name = 'LSP',
244+
module = 'blink.cmp.sources.lsp',
245+
246+
--- *All* of the providers have the following options available
247+
--- NOTE: All of these options may be functions to enable dynamic behavior
248+
--- See the type definitions for more information
249+
enabled = true, -- whether or not to enable the provider
250+
transform_items = nil, -- function to transform the items before they're returned
251+
should_show_items = true, -- whether or not to show the items
252+
max_items = nil, -- maximum number of items to return
253+
min_keyword_length = 0, -- minimum number of characters to trigger the provider
254+
fallback_for = {}, -- if any of these providers return 0 items, it will fallback to this provider
255+
score_offset = 0, -- boost/penalize the score of the items
256+
override = nil, -- override the source's functions
255257
},
256-
-- the following two sources have additional options
257-
{
258-
'blink.cmp.sources.path',
258+
path = {
259259
name = 'Path',
260+
module = 'blink.cmp.sources.path',
260261
score_offset = 3,
261-
opts = {
262-
trailing_slash = false,
263-
label_trailing_slash = true,
264-
get_cwd = function(context) return vim.fn.expand(('#%d:p:h'):format(context.bufnr)) end,
265-
show_hidden_files_by_default = true,
266-
}
267262
},
268-
{
269-
'blink.cmp.sources.snippets',
263+
snippets = {
270264
name = 'Snippets',
265+
module = 'blink.cmp.sources.snippets',
271266
score_offset = -3,
272-
-- similar to https://github.com/garymjr/nvim-snippets
273-
opts = {
274-
friendly_snippets = true,
275-
search_paths = { vim.fn.stdpath('config') .. '/snippets' },
276-
global_snippets = { 'all' },
277-
extended_filetypes = {},
278-
ignored_filetypes = {},
279-
},
267+
268+
--- Example usage for disabling the snippet provider after pressing trigger characters (i.e. ".")
269+
-- enabled = function(ctx) return ctx ~= nil and ctx.trigger.kind == vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter end,
280270
},
281-
{
282-
'blink.cmp.sources.buffer',
271+
buffer = {
283272
name = 'Buffer',
284-
fallback_for = { 'LSP' },
285-
}
286-
}
273+
module = 'blink.cmp.sources.buffer',
274+
fallback_for = { 'lsp' },
275+
},
276+
},
287277
},
288278

289279
windows = {
@@ -437,7 +427,6 @@ The plugin use a 4 stage pipeline: trigger -> sources -> fuzzy -> render
437427

438428
### Planned missing features
439429

440-
- Less customizable with regards to trigger, sources, sorting, filtering
441430
- Significantly less testing and documentation
442431
- Ghost text
443432
- Matched character highlighting

lua/blink/cmp/config.lua

+7-2
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@
5050
--- @field signature_help? blink.cmp.SignatureHelpTriggerConfig
5151

5252
--- @class blink.cmp.SourceConfig
53-
--- @field completion? string[] | fun(ctx?: blink.cmp.Context): string[]
53+
--- @field completion? blink.cmp.SourceModeConfig
5454
--- @field providers? table<string, blink.cmp.SourceProviderConfig>
5555
---
56+
--- @class blink.cmp.SourceModeConfig
57+
--- @field enabled_providers? string[] | fun(ctx?: blink.cmp.Context): string[]
58+
---
5659
--- @class blink.cmp.SourceProviderConfig
5760
--- @field name string
5861
--- @field module string
@@ -250,7 +253,9 @@ local config = {
250253

251254
sources = {
252255
-- list of enabled providers
253-
completion = { 'lsp', 'path', 'snippets', 'buffer' },
256+
completion = {
257+
enabled_providers = { 'lsp', 'path', 'snippets', 'buffer' },
258+
},
254259

255260
-- table of providers to configure
256261
providers = {

lua/blink/cmp/fuzzy/init.lua

-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
local config = require('blink.cmp.config')
22

33
local fuzzy = {
4-
---@type blink.cmp.Context?
5-
last_context = nil,
6-
---@type blink.cmp.CompletionItem[]?
7-
last_items = nil,
84
rust = require('blink.cmp.fuzzy.rust'),
95
}
106

@@ -63,20 +59,6 @@ function fuzzy.filter_items(needle, haystack)
6359
return filtered_items
6460
end
6561

66-
---@param needle string
67-
---@param context blink.cmp.Context
68-
---@param items blink.cmp.CompletionItem[]?
69-
function fuzzy.filter_items_with_cache(needle, context, items)
70-
if items == nil then
71-
if fuzzy.last_context == nil or fuzzy.last_context.id ~= context.id then return {} end
72-
items = fuzzy.last_items
73-
end
74-
fuzzy.last_context = context
75-
fuzzy.last_items = items
76-
77-
return fuzzy.filter_items(needle, items)
78-
end
79-
8062
--- Gets the text under the cursor to be used for fuzzy matching
8163
--- @return string
8264
function fuzzy.get_query()

lua/blink/cmp/init.lua

+13-18
Original file line numberDiff line numberDiff line change
@@ -32,36 +32,31 @@ cmp.setup = function(opts)
3232
documentation = require('blink.cmp.windows.documentation').setup(),
3333
}
3434

35-
-- fuzzy combines smith waterman with frecency
36-
-- and bonus from proximity words but I'm still working
37-
-- on tuning the weights
38-
--- @param context blink.cmp.Context
39-
--- @param items blink.cmp.CompletionItem[] | nil
40-
local function update_completions(context, items)
35+
cmp.trigger.listen_on_show(function(context) cmp.sources.request_completions(context) end)
36+
cmp.trigger.listen_on_hide(function()
37+
cmp.sources.cancel_completions()
38+
cmp.windows.autocomplete.close()
39+
end)
40+
cmp.sources.listen_on_completions(function(context, items)
41+
-- fuzzy combines smith waterman with frecency
42+
-- and bonus from proximity words but I'm still working
43+
-- on tuning the weights
4144
if not cmp.fuzzy then
4245
cmp.fuzzy = require('blink.cmp.fuzzy')
4346
cmp.fuzzy.init_db(vim.fn.stdpath('data') .. '/blink/cmp/fuzzy.db')
4447
end
45-
-- we avoid adding 1-4ms to insertion latency by scheduling for later
48+
49+
-- we avoid adding 0.5-4ms to insertion latency by scheduling for later
4650
vim.schedule(function()
47-
local filtered_items = cmp.fuzzy.filter_items_with_cache(cmp.fuzzy.get_query(), context, items)
51+
local filtered_items = cmp.fuzzy.filter_items(cmp.fuzzy.get_query(), items)
52+
filtered_items = cmp.sources.apply_max_items_for_completions(context, filtered_items)
4853
if #filtered_items > 0 then
4954
cmp.windows.autocomplete.open_with_items(context, filtered_items)
5055
else
5156
cmp.windows.autocomplete.close()
5257
end
5358
end)
54-
end
55-
56-
cmp.trigger.listen_on_show(function(context)
57-
update_completions(context) -- immediately update via cache on keystroke
58-
cmp.sources.request_completions(context)
59-
end)
60-
cmp.trigger.listen_on_hide(function()
61-
cmp.sources.cancel_completions()
62-
cmp.windows.autocomplete.close()
6359
end)
64-
cmp.sources.listen_on_completions(update_completions)
6560

6661
-- setup signature help if enabled
6762
if config.trigger.signature_help.enabled then cmp.setup_signature_help() end

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

+24-20
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ local async = require('blink.cmp.sources.lib.async')
66
--- @field sources table<string, blink.cmp.SourceProvider>
77
--- @field active_request blink.cmp.Task | nil
88
--- @field queued_request_context blink.cmp.Context | nil
9-
--- @field on_completions_callback fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
9+
--- @field cached_responses table<string, blink.cmp.CompletionResponse> | nil
10+
--- @field on_completions_callback fun(context: blink.cmp.Context, enabled_sources: string[], responses: table<string, blink.cmp.CompletionResponse>)
1011
---
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 new fun(context: blink.cmp.Context, sources: table<string, blink.cmp.SourceProvider>, on_completions_callback: fun(context: blink.cmp.Context, items: table<string, blink.cmp.CompletionResponse>)): blink.cmp.SourcesContext
13+
--- @field get_sources fun(self: blink.cmp.SourcesContext): string[]
14+
--- @field get_cached_completions fun(self: blink.cmp.SourcesContext): table<string, blink.cmp.CompletionResponse> | nil
1215
--- @field get_completions fun(self: blink.cmp.SourcesContext, context: blink.cmp.Context)
1316
--- @field get_completions_for_sources fun(self: blink.cmp.SourcesContext, sources: table<string, blink.cmp.SourceProvider>, context: blink.cmp.Context): blink.cmp.Task
1417
--- @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
@@ -25,12 +28,15 @@ function sources_context.new(context, sources, on_completions_callback)
2528

2629
self.active_request = nil
2730
self.queued_request_context = nil
28-
--- @type fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
2931
self.on_completions_callback = on_completions_callback
3032

3133
return self
3234
end
3335

36+
function sources_context:get_sources() return vim.tbl_keys(self.sources) end
37+
38+
function sources_context:get_cached_completions() return self.cached_responses end
39+
3440
function sources_context:get_completions(context)
3541
assert(context.id == self.id, 'Requested completions on a sources context with a different context ID')
3642

@@ -41,10 +47,17 @@ function sources_context:get_completions(context)
4147

4248
-- Create a task to get the completions, send responses upstream
4349
-- and run the queued request, if it exists
44-
self.active_request = self:get_completions_for_sources(self.sources, context):map(function(response)
50+
self.active_request = self:get_completions_for_sources(self.sources, context):map(function(responses)
51+
self.cached_responses = responses
52+
--- @cast responses table<string, blink.cmp.CompletionResponse>
4553
self.active_request = nil
46-
-- only send upstream if the response contains something new
47-
if not response.is_cached then self.on_completions_callback(context, response.items) end
54+
55+
-- only send upstream if the responses contain something new
56+
local is_cached = true
57+
for _, response in pairs(responses) do
58+
is_cached = is_cached and (response.is_cached or false)
59+
end
60+
if not is_cached then self.on_completions_callback(context, self:get_sources(), responses) end
4861

4962
-- run the queued request, if it exists
5063
if self.queued_request_context ~= nil then
@@ -84,28 +97,19 @@ function sources_context:get_completions_for_sources(sources, context)
8497
return async.task
8598
.await_all(tasks)
8699
:map(function(tasks_results)
87-
local is_cached = true
88-
local items = {}
89-
-- for each task, add them to the list if the source should show the items
100+
local responses = {}
90101
for idx, task_result in ipairs(tasks_results) do
91102
if task_result.status == async.STATUS.COMPLETED then
92103
--- @type blink.cmp.SourceProvider
93-
local source = vim.tbl_values(sources)[idx]
94-
--- @type blink.cmp.CompletionResponse
95-
local response = task_result.result
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
104+
local source = vim.tbl_values(non_fallback_sources)[idx]
105+
responses[source.id] = task_result.result
102106
end
103107
end
104-
return { is_cached = is_cached, items = items }
108+
return responses
105109
end)
106110
:catch(function(err)
107111
vim.print('failed to get completions for sources with error: ' .. err)
108-
return { is_cached = false, items = {} }
112+
return {}
109113
end)
110114
end
111115

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

+50-4
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ local config = require('blink.cmp.config')
66
--- @field current_signature_help blink.cmp.Task | nil
77
--- @field sources_registered boolean
88
--- @field providers table<string, blink.cmp.SourceProvider>
9-
--- @field on_completions_callback fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
9+
--- @field on_completions_callback fun(context: blink.cmp.Context, enabled_sources: table<string, blink.cmp.SourceProvider>, responses: table<string, blink.cmp.CompletionResponse>)
1010
---
1111
--- @field register fun()
1212
--- @field get_enabled_providers fun(context?: blink.cmp.Context): table<string, blink.cmp.SourceProvider>
1313
--- @field get_trigger_characters fun(): string[]
1414
--- @field request_completions fun(context: blink.cmp.Context)
1515
--- @field cancel_completions fun()
1616
--- @field listen_on_completions fun(callback: fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[]))
17+
--- @field apply_max_items_for_completions fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[]
1718
--- @field resolve fun(item: blink.cmp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): (fun(): nil) | nil
1819
--- @field get_signature_help_trigger_characters fun(): { trigger_characters: string[], retrigger_characters: string[] }
1920
--- @field get_signature_help fun(context: blink.cmp.SignatureHelpContext, callback: fun(signature_help: lsp.SignatureHelp | nil)): (fun(): nil) | nil
@@ -39,8 +40,9 @@ function sources.register()
3940
end
4041

4142
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
43+
local mode_providers = type(config.sources.completion.enabled_providers) == 'function'
44+
and config.sources.completion.enabled_providers(context)
45+
or config.sources.completion.enabled_providers
4446
--- @cast mode_providers string[]
4547

4648
--- @type table<string, blink.cmp.SourceProvider>
@@ -70,7 +72,17 @@ function sources.get_trigger_characters()
7072
return trigger_characters
7173
end
7274

73-
function sources.listen_on_completions(callback) sources.on_completions_callback = callback end
75+
function sources.listen_on_completions(callback)
76+
sources.on_completions_callback = function(context, enabled_sources, responses)
77+
local items = {}
78+
for id, response in pairs(responses) do
79+
if sources.providers[id]:should_show_items(context, enabled_sources, response.items) then
80+
vim.list_extend(items, response.items)
81+
end
82+
end
83+
callback(context, items)
84+
end
85+
end
7486

7587
function sources.request_completions(context)
7688
-- create a new context if the id changed or if we haven't created one yet
@@ -82,6 +94,13 @@ function sources.request_completions(context)
8294
sources.get_enabled_providers(context),
8395
sources.on_completions_callback
8496
)
97+
-- send cached completions if they exist to immediately trigger updates
98+
elseif sources.current_context:get_cached_completions() ~= nil then
99+
sources.on_completions_callback(
100+
context,
101+
sources.current_context:get_sources(),
102+
sources.current_context:get_cached_completions()
103+
)
85104
end
86105

87106
sources.current_context:get_completions(context)
@@ -94,6 +113,33 @@ function sources.cancel_completions()
94113
end
95114
end
96115

116+
--- Limits the number of items per source as configured
117+
function sources.apply_max_items_for_completions(context, items)
118+
local enabled_sources = sources.get_enabled_providers(context)
119+
120+
-- get the configured max items for each source
121+
local total_items_for_sources = {}
122+
local max_items_for_sources = {}
123+
for id, source in pairs(sources.providers) do
124+
max_items_for_sources[id] = source.config.max_items(context, enabled_sources, items)
125+
total_items_for_sources[id] = 0
126+
end
127+
128+
-- no max items configured, return as-is
129+
if #vim.tbl_keys(max_items_for_sources) == 0 then return items end
130+
131+
-- apply max items
132+
local filtered_items = {}
133+
for _, item in ipairs(items) do
134+
local max_items = max_items_for_sources[item.source_id]
135+
total_items_for_sources[item.source_id] = total_items_for_sources[item.source_id] + 1
136+
if max_items == nil or total_items_for_sources[item.source_id] <= max_items then
137+
table.insert(filtered_items, item)
138+
end
139+
end
140+
return filtered_items
141+
end
142+
97143
--- Resolve ---
98144

99145
function sources.resolve(item, callback)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ end
8787
function source:should_show_items(context, enabled_sources, response)
8888
-- if keyword length is configured, check if the context is long enough
8989
local min_keyword_length = self.config.min_keyword_length(context, enabled_sources)
90-
local current_keyword_length = context.bounds.end_col - context.bounds.start_col
90+
local current_keyword_length = context.bounds.length
9191
if current_keyword_length < min_keyword_length then return false end
9292

9393
if self.config.should_show_items == nil then return true end

0 commit comments

Comments
 (0)