1
+ local utils = require (' blink.cmp.sources.lib.utils' )
1
2
local async = require (' blink.cmp.sources.lib.async' )
2
3
local sources_context = {}
3
4
4
5
--- @param context blink.cmp.Context
5
- --- @param sources_groups blink.cmp.Source[] []
6
+ --- @param sources blink.cmp.SourceProvider []
6
7
--- @param on_completions_callback fun ( context : blink.cmp.Context , items : blink.cmp.CompletionItem[] )
7
- function sources_context .new (context , sources_groups , on_completions_callback )
8
+ function sources_context .new (context , sources , on_completions_callback )
8
9
local self = setmetatable ({}, { __index = sources_context })
9
10
self .id = context .id
10
- self .sources_groups = sources_groups
11
+ self .sources = sources
11
12
12
13
self .active_request = nil
13
14
self .queued_request_context = nil
14
- self .last_sources_group_idx = nil
15
15
--- @type fun ( context : blink.cmp.Context , items : blink.cmp.CompletionItem[] )
16
16
self .on_completions_callback = on_completions_callback
17
17
@@ -27,26 +27,12 @@ function sources_context:get_completions(context)
27
27
return
28
28
end
29
29
30
- -- Create a task to get the completions for the first sources group,
31
- -- falling back to the next sources group iteratively if there are no items
32
- local request = self :get_completions_for_group (1 , self .sources_groups [1 ], context )
33
- for idx , sources_group in ipairs (self .sources_groups ) do
34
- if idx > 1 then
35
- request = request :map (function (res )
36
- if # res .items > 0 then return res end
37
- return self :get_completions_for_group (idx , sources_group , context )
38
- end )
39
- end
40
- end
41
-
42
- -- Send response upstream and run the queued request, if it exists
43
- self .active_request = request :map (function (response )
30
+ -- Create a task to get the completions, send responses upstream
31
+ -- and run the queued request, if it exists
32
+ self .active_request = self :get_completions_for_sources (self .sources , context ):map (function (response )
44
33
self .active_request = nil
45
34
-- only send upstream if the response contains something new
46
- if not response .is_cached or response .sources_group_idx ~= self .last_sources_group_idx then
47
- self .on_completions_callback (context , response .items )
48
- end
49
- self .last_sources_group_idx = response .sources_group_idx
35
+ if not response .is_cached then self .on_completions_callback (context , response .items ) end
50
36
51
37
-- run the queued request, if it exists
52
38
if self .queued_request_context ~= nil then
@@ -57,28 +43,26 @@ function sources_context:get_completions(context)
57
43
end )
58
44
end
59
45
60
- --- @param sources_group_idx number
61
- --- @param sources_group blink.cmp.Source[]
46
+ --- @param sources blink.cmp.SourceProvider[]
62
47
--- @param context blink.cmp.Context
63
48
--- @return blink.cmp.Task
64
- function sources_context :get_completions_for_group (sources_group_idx , sources_group , context )
65
- -- get completions for each source in the group
49
+ function sources_context :get_completions_for_sources (sources , context )
50
+ local non_fallback_sources = vim .tbl_filter (function (source ) return source .config .fallback_for == nil end , sources )
51
+
52
+ -- get completions for each non-fallback source
66
53
local tasks = vim .tbl_map (function (source )
67
54
-- the source indicates we should refetch when this character is typed
68
55
local trigger_character = context .trigger .character
69
56
and vim .tbl_contains (source :get_trigger_characters (), context .trigger .character )
70
57
71
- -- The TriggerForIncompleteCompletions kind is handled by the source itself
58
+ -- The TriggerForIncompleteCompletions kind is handled by the source provider itself
72
59
local source_context = require (' blink.cmp.utils' ).shallow_copy (context )
73
60
source_context .trigger = trigger_character
74
61
and { kind = vim .lsp .protocol .CompletionTriggerKind .TriggerCharacter , character = context .trigger .character }
75
62
or { kind = vim .lsp .protocol .CompletionTriggerKind .Invoked }
76
63
77
- return source :get_completions (source_context ):catch (function (err )
78
- vim .print (source .name .. ' : failed to get completions with error: ' .. err )
79
- return { is_incomplete_forward = false , is_incomplete_backward = false , items = {} }
80
- end )
81
- end , sources_group )
64
+ return self :get_completions_with_fallbacks (source_context , source , sources )
65
+ end , non_fallback_sources )
82
66
83
67
-- wait for all the tasks to complete
84
68
return async .task
@@ -91,21 +75,59 @@ function sources_context:get_completions_for_group(sources_group_idx, sources_gr
91
75
for idx , task_result in ipairs (tasks_results ) do
92
76
if task_result .status == async .STATUS .COMPLETED then
93
77
is_cached = is_cached and (task_result .result .is_cached or false )
94
- local source = sources_group [idx ]
78
+ local source = sources [idx ]
95
79
--- @type blink.cmp.CompletionResponse
96
80
local response = task_result .result
97
81
response .items = source :filter_completions (response )
98
82
if source :should_show_completions (context , response ) then vim .list_extend (items , response .items ) end
99
83
end
100
84
end
101
- return { sources_group_idx = sources_group_idx , is_cached = is_cached , items = items }
85
+ return { is_cached = is_cached , items = items }
102
86
end )
103
87
:catch (function (err )
104
- vim .print (' failed to get completions for group with error: ' .. err )
105
- return { sources_group_idx = sources_group_idx , is_cached = false , items = {} }
88
+ vim .print (' failed to get completions for sources with error: ' .. err )
89
+ return { is_cached = false , items = {} }
106
90
end )
107
91
end
108
92
93
+ --- Runs the source's get_completions function, falling back to other sources
94
+ --- with fallback_for = { source.name } if the source returns no completion items
95
+ --- @param context blink.cmp.Context
96
+ --- @param source blink.cmp.SourceProvider
97
+ --- @param sources blink.cmp.SourceProvider[]
98
+ --- @return blink.cmp.Task
99
+ --- TODO: When a source has multiple fallbacks, we may end up with duplicate completion items
100
+ function sources_context :get_completions_with_fallbacks (context , source , sources )
101
+ local fallback_sources = vim .tbl_filter (
102
+ function (fallback_source )
103
+ return fallback_source .name ~= source .name
104
+ and fallback_source .config .fallback_for ~= nil
105
+ and vim .tbl_contains (fallback_source .config .fallback_for , source .name )
106
+ end ,
107
+ sources
108
+ )
109
+
110
+ return source :get_completions (context ):map (function (response )
111
+ -- source returned completions, no need to fallback
112
+ if # response .items > 0 or # fallback_sources == 0 then return response end
113
+
114
+ -- run fallbacks
115
+ return async .task
116
+ .await_all (vim .tbl_map (function (fallback ) return fallback :get_completions (context ) end , fallback_sources ))
117
+ :map (function (task_results )
118
+ local successful_task_results = vim .tbl_filter (
119
+ function (task_result ) return task_result .status == async .STATUS .COMPLETED end ,
120
+ task_results
121
+ )
122
+ local fallback_responses = vim .tbl_map (
123
+ function (task_result ) return task_result .result end ,
124
+ successful_task_results
125
+ )
126
+ return utils .concat_responses (fallback_responses )
127
+ end )
128
+ end )
129
+ end
130
+
109
131
function sources_context :destroy ()
110
132
self .on_completions_callback = function () end
111
133
if self .active_request ~= nil then self .active_request :cancel () end
0 commit comments