11local Promise = require (' opencode.promise' )
2- local M = {}
2+ local state = require (' opencode.state' )
3+ local CompletionEngine = require (' opencode.ui.completion.engines.base' )
4+
5+ --- @class BlinkCmpEngine : CompletionEngine
6+ local BlinkCmpEngine = setmetatable ({}, { __index = CompletionEngine })
7+ BlinkCmpEngine .__index = BlinkCmpEngine
8+
9+ --- Create a new blink-cmp completion engine
10+ --- @return BlinkCmpEngine
11+ function BlinkCmpEngine .new ()
12+ local self = CompletionEngine .new (' blink_cmp' )
13+ return setmetatable (self , BlinkCmpEngine )
14+ end
15+
16+ --- Check if blink-cmp is available
17+ --- @return boolean
18+ function BlinkCmpEngine :is_available ()
19+ local ok = pcall (require , ' blink.cmp' )
20+ return ok and CompletionEngine .is_available ()
21+ end
22+
23+ --- Setup blink-cmp completion engine
24+ --- @param completion_sources table[]
25+ --- @return boolean
26+ function BlinkCmpEngine :setup (completion_sources )
27+ local ok , blink = pcall (require , ' blink.cmp' )
28+ if not ok then
29+ return false
30+ end
31+
32+ CompletionEngine .setup (self , completion_sources )
33+
34+ blink .add_source_provider (' opencode_mentions' , {
35+ module = ' opencode.ui.completion.engines.blink_cmp' ,
36+ async = true ,
37+ })
38+
39+ -- Hide blink-cmp menu on certain trigger characters when opened via other completion sources
40+ vim .api .nvim_create_autocmd (' User' , {
41+ group = vim .api .nvim_create_augroup (' OpencodeBlinkCmp' , { clear = true }),
42+ pattern = ' BlinkCmpMenuOpen' ,
43+ callback = function ()
44+ local current_buf = vim .api .nvim_get_current_buf ()
45+ local input_buf = vim .tbl_get (state , ' windows' , ' input_buf' )
46+ if not state .windows or current_buf ~= input_buf then
47+ return
48+ end
49+
50+ local blink = require (' blink.cmp' )
51+ local ctx = blink .get_context ()
52+
53+ local triggers = CompletionEngine .get_trigger_characters ()
54+ if ctx .trigger .initial_kind == ' trigger_character' and vim .tbl_contains (triggers , ctx .trigger .character ) then
55+ blink .hide ()
56+ end
57+ end ,
58+ })
59+ return true
60+ end
361
62+ --- Trigger completion manually for blink-cmp
63+ --- @param trigger_char string
64+ function BlinkCmpEngine :trigger (trigger_char )
65+ local blink = require (' blink.cmp' )
66+
67+ vim .api .nvim_feedkeys (trigger_char , ' in' , true )
68+ if blink .is_visible () then
69+ blink .hide ()
70+ end
71+
72+ blink .show ({
73+ providers = { ' opencode_mentions' },
74+ trigger_character = trigger_char ,
75+ })
76+ end
77+
78+ function BlinkCmpEngine :hide ()
79+ local blink = require (' blink.cmp' )
80+ if blink .is_visible () then
81+ blink .hide ()
82+ end
83+ end
84+
85+ -- Source implementation for blink-cmp provider (when this module is loaded by blink.cmp)
486local Source = {}
587Source .__index = Source
688
@@ -10,19 +92,13 @@ function Source.new()
1092end
1193
1294function Source :get_trigger_characters ()
13- local config = require (' opencode.config' )
14- local mention_key = config .get_key_for_function (' input_window' , ' mention' )
15- local slash_key = config .get_key_for_function (' input_window' , ' slash_commands' )
16- local context_key = config .get_key_for_function (' input_window' , ' context_items' )
17- return {
18- slash_key or ' ' ,
19- mention_key or ' ' ,
20- context_key or ' ' ,
21- }
95+ local CompletionEngine = require (' opencode.ui.completion.engines.base' )
96+ return CompletionEngine .get_trigger_characters ()
2297end
2398
2499function Source :enabled ()
25- return vim .bo .filetype == ' opencode'
100+ local CompletionEngine = require (' opencode.ui.completion.engines.base' )
101+ return CompletionEngine .is_available ()
26102end
27103
28104function Source :get_completions (ctx , callback )
@@ -34,7 +110,9 @@ function Source:get_completions(ctx, callback)
34110 local col = ctx .cursor [2 ] + 1
35111 local before_cursor = line :sub (1 , col - 1 )
36112
37- local trigger_chars = table.concat (vim .tbl_map (vim .pesc , self :get_trigger_characters ()), ' ' )
113+ local CompletionEngine = require (' opencode.ui.completion.engines.base' )
114+ local triggers = CompletionEngine .get_trigger_characters ()
115+ local trigger_chars = table.concat (vim .tbl_map (vim .pesc , triggers ), ' ' )
38116 local trigger_char , trigger_match = before_cursor :match (' ([' .. trigger_chars .. ' ])([%w_/%-%.]*)$' )
39117
40118 if not trigger_match then
@@ -43,15 +121,15 @@ function Source:get_completions(ctx, callback)
43121 end
44122
45123 local context = {
46- input = trigger_match , -- Pass input for search-based sources (e.g., files)
124+ input = trigger_match ,
47125 cursor_pos = col ,
48126 line = line ,
49127 trigger_char = trigger_char ,
50128 }
51129
52130 local items = {}
53- for _ , completion_source in ipairs (completion_sources ) do
54- local source_items = completion_source .complete (context ):await ()
131+ for _ , source in ipairs (completion_sources ) do
132+ local source_items = source .complete (context ):await ()
55133 for i , item in ipairs (source_items ) do
56134 local insert_text = item .insert_text or item .label
57135 table.insert (items , {
@@ -61,18 +139,10 @@ function Source:get_completions(ctx, callback)
61139 kind_hl = item .kind_hl ,
62140 detail = item .detail ,
63141 documentation = item .documentation ,
64- -- Use filterText for fuzzy matching against the typed text after trigger char
65142 filterText = item .filter_text or item .label ,
66143 insertText = insert_text ,
67- sortText = string.format (
68- ' %02d_%02d_%02d_%s' ,
69- completion_source .priority or 999 ,
70- item .priority or 999 ,
71- i ,
72- item .label
73- ),
74- score_offset = - (completion_source .priority or 999 ) * 1000 + (item .priority or 999 ),
75-
144+ sortText = string.format (' %02d_%02d_%02d_%s' , source .priority or 999 , item .priority or 999 , i , item .label ),
145+ score_offset = - (source .priority or 999 ) * 1000 + (item .priority or 999 ),
76146 data = {
77147 original_item = item ,
78148 },
@@ -88,27 +158,23 @@ function Source:execute(ctx, item, callback, default_implementation)
88158 default_implementation ()
89159
90160 if item .data and item .data .original_item then
91- local completion = require (' opencode.ui.completion' )
92- completion .on_complete (item .data .original_item )
161+ local CompletionEngine = require (' opencode.ui.completion.engines.base' )
162+ local engine = CompletionEngine .new (' blink_cmp' )
163+ engine :on_complete (item .data .original_item )
93164 end
94165
95166 callback ()
96167end
97168
98- function M .setup (completion_sources )
99- local ok , blink = pcall (require , ' blink.cmp' )
100- if not ok then
101- return false
102- end
169+ -- Export module with dual interface:
170+ -- - For our engine system: use BlinkCmpEngine methods
171+ -- - For blink.cmp provider system: override 'new' to return Source instance
172+ local M = BlinkCmpEngine
103173
104- blink .add_source_provider (' opencode_mentions' , {
105- module = ' opencode.ui.completion.engines.blink_cmp' ,
106- async = true ,
107- })
108-
109- return true
110- end
174+ -- Save the engine constructor before overriding
175+ M .create = BlinkCmpEngine .new
111176
177+ -- Override 'new' for blink.cmp compatibility (when blink loads this as a source)
112178M .new = Source .new
113179
114180return M
0 commit comments