-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathblink_cmp.lua
More file actions
209 lines (174 loc) · 6.05 KB
/
blink_cmp.lua
File metadata and controls
209 lines (174 loc) · 6.05 KB
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
local Promise = require('opencode.promise')
local state = require('opencode.state')
local CompletionEngine = require('opencode.ui.completion.engines.base')
---@class BlinkCmpEngine : CompletionEngine
local BlinkCmpEngine = setmetatable({}, { __index = CompletionEngine })
BlinkCmpEngine.__index = BlinkCmpEngine
---Create a new blink-cmp completion engine
---@return BlinkCmpEngine
function BlinkCmpEngine.new()
local self = CompletionEngine.new('blink_cmp')
return setmetatable(self, BlinkCmpEngine)
end
---Check if blink-cmp is available
---@return boolean
function BlinkCmpEngine:is_available()
return pcall(require, 'blink.cmp') and CompletionEngine.is_available()
end
---Setup blink-cmp completion engine
---@param completion_sources table[]
---@return boolean
function BlinkCmpEngine:setup(completion_sources)
local ok, _ = pcall(require, 'blink.cmp')
if not ok then
return false
end
local blink = require('blink.cmp')
CompletionEngine.setup(self, completion_sources)
blink.add_source_provider('opencode_mentions', {
module = 'opencode.ui.completion.engines.blink_cmp',
async = true,
})
vim.api.nvim_create_autocmd('User', {
group = vim.api.nvim_create_augroup('OpencodeBlinkCmp', { clear = true }),
pattern = 'BlinkCmpMenuOpen',
callback = function()
local current_buf = vim.api.nvim_get_current_buf()
local input_buf = vim.tbl_get(state, 'windows', 'input_buf')
if not state.windows or current_buf ~= input_buf then
return
end
local ctx = blink.get_context()
local triggers = CompletionEngine.get_trigger_characters()
-- blink has a tendency to show other providers even when we only want our own matching the trigger.
local should_override = (
ctx.trigger.initial_kind == 'trigger_character' and vim.tbl_contains(triggers, ctx.trigger.character)
) or (ctx.trigger.initial_kind == 'keyword' and vim.tbl_contains(triggers, ctx.line:sub(1, 1)))
if should_override then
blink.show({
providers = { 'opencode_mentions' },
trigger_character = ctx.trigger.character or ctx.line:sub(1, 1),
})
end
end,
})
state.subscribe('input_content', function(_, content)
if self:is_visible() then
return
end
vim.schedule(function()
local blink = require('blink.cmp')
local ctx = blink.get_context()
if not ctx then
return
end
--blink ctx.line is out of date here, so we get the line ourselves
local line = vim.api.nvim_get_current_line()
local col = ctx.cursor[2]
local before_cursor = line:sub(1, col)
local trigger_char, trigger_match = CompletionEngine.parse_trigger(self, before_cursor)
if trigger_match then
blink.show({
providers = { 'opencode_mentions' },
trigger_character = trigger_char,
})
end
end)
end)
return true
end
---Check if blink-cmp completion menu is visible
---@return boolean
function BlinkCmpEngine:is_visible()
local blink = require('blink.cmp')
return blink.is_visible()
end
---Trigger completion manually for blink-cmp
---@param trigger_char string
function BlinkCmpEngine:trigger(trigger_char)
local blink = require('blink.cmp')
vim.api.nvim_feedkeys(trigger_char, 'in', true)
if blink.is_visible() then
blink.hide()
end
blink.show({
providers = { 'opencode_mentions' },
trigger_character = trigger_char,
})
end
function BlinkCmpEngine:hide()
require('blink.cmp').hide()
end
-- Source implementation for blink-cmp provider (when this module is loaded by blink.cmp)
local Source = {}
Source.__index = Source
function Source.new()
local self = setmetatable({}, Source)
return self
end
function Source:get_trigger_characters()
return CompletionEngine.get_trigger_characters()
end
function Source:enabled()
return CompletionEngine.is_available()
end
function Source:get_completions(ctx, callback)
Promise.spawn(function()
local completion = require('opencode.ui.completion')
local completion_sources = completion.get_sources()
local line = ctx.line
local col = ctx.cursor[2] + 1
local before_cursor = line:sub(1, col - 1)
local trigger_char, trigger_match = CompletionEngine.parse_trigger(self, before_cursor)
if not trigger_match then
callback({ is_incomplete_forward = false, items = {} })
return
end
---@type CompletionContext
local context = {
input = trigger_match,
cursor_pos = col,
line = line,
trigger_char = trigger_char or '',
}
local items = {}
for _, source in ipairs(completion_sources) do
local source_items = source.complete(context):await()
for i, item in ipairs(source_items) do
local insert_text = item.insert_text or item.label
table.insert(items, {
label = item.label,
kind = item.kind,
kind_icon = item.kind_icon,
kind_hl = item.kind_hl,
detail = item.detail,
documentation = item.documentation,
filterText = item.filter_text or item.label,
insertText = insert_text,
sortText = string.format('%02d_%02d_%02d_%s', source.priority or 999, item.priority or 999, i, item.label),
score_offset = -(source.priority or 999) * 1000 + (item.priority or 999),
data = {
original_item = item,
},
})
end
end
callback({ is_incomplete_forward = true, is_incomplete_backward = true, items = items })
end)
end
function Source:execute(_, item, callback, default_implementation)
default_implementation()
if item.data and item.data.original_item then
CompletionEngine.on_complete(self, item.data.original_item)
end
callback()
end
-- Export module with dual interface:
-- - For our engine system: use BlinkCmpEngine methods
-- - For blink.cmp provider system: override 'new' to return Source instance
local M = BlinkCmpEngine
-- Save the engine constructor before overriding
M.create = BlinkCmpEngine.new
-- Override 'new' for blink.cmp compatibility (when blink loads this as a source)
M.new = Source.new
return M