@@ -3,7 +3,9 @@ local M = {
33 _sources = {},
44 _last_line = ' ' ,
55 _last_col = 0 ,
6+ _trigger_col = 0 ,
67 _pending = {},
8+ _done_by_event = false ,
79}
810
911function M .setup ()
@@ -42,11 +44,15 @@ function M.on_text_changed()
4244 return
4345 end
4446
47+ if M ._done_by_event then
48+ M ._done_by_event = false
49+ return
50+ end
51+
4552 local line = vim .api .nvim_get_current_line ()
4653 local col = vim .api .nvim_win_get_cursor (0 )[2 ]
4754
48- -- detect inserted text
49- local inserted = line :sub (M ._last_col + 1 , col )
55+ local inserted = line :sub (M ._trigger_col + 1 , col )
5056
5157 if M ._pending [inserted ] then
5258 local item = M ._pending [inserted ]
@@ -62,9 +68,15 @@ function M.on_text_changed()
6268end
6369
6470function M .store_completion_items (items )
65- M ._pending = {}
71+ local col = vim .api .nvim_win_get_cursor (0 )[2 ]
72+
73+ if not next (M ._pending ) then
74+ M ._trigger_col = col
75+ end
76+
6677 M ._last_line = vim .api .nvim_get_current_line ()
67- M ._last_col = vim .api .nvim_win_get_cursor (0 )[2 ]
78+ M ._last_col = col
79+ M ._pending = {}
6880
6981 for _ , item in ipairs (items or {}) do
7082 local word = item .insertText
@@ -94,6 +106,52 @@ function M.get_source_by_name(name)
94106 return nil
95107end
96108
109+ --- Handle the CompleteDone autocmd event.
110+ --- This is the primary completion-done path and works regardless of whether the
111+ --- completion list was filtered. vim.v.completed_item.word is the text that was
112+ --- actually inserted, so we look it up directly in _pending.
113+ function M .on_complete_done_event ()
114+ if not next (M ._pending ) then
115+ return
116+ end
117+
118+ local completed = vim .v .completed_item
119+ if not completed or completed .word == nil or completed .word == ' ' then
120+ M .clear_pending ()
121+ return
122+ end
123+
124+ -- completed_item.word is the raw inserted word. The full insertText we
125+ -- stored as the key may include more (e.g. the trigger char + word), so
126+ -- try both the word alone and with a leading trigger character.
127+ local function try (key )
128+ local lsp_item = M ._pending [key ]
129+ if lsp_item and lsp_item .data and lsp_item .data ._opencode_item then
130+ M ._done_by_event = true
131+ M ._pending = {}
132+ M .on_completion_done (lsp_item .data ._opencode_item )
133+ return true
134+ end
135+ return false
136+ end
137+
138+ if not try (completed .word ) then
139+ -- Some completion plugins strip the trigger char from the inserted text
140+ -- but store it in user_data. Try reconstructing the full insertText by
141+ -- scanning _pending keys that end with completed.word.
142+ for key , lsp_item in pairs (M ._pending ) do
143+ if key :sub (-# completed .word ) == completed .word then
144+ if lsp_item .data and lsp_item .data ._opencode_item then
145+ M ._done_by_event = true
146+ M ._pending = {}
147+ M .on_completion_done (lsp_item .data ._opencode_item )
148+ break
149+ end
150+ end
151+ end
152+ end
153+ end
154+
97155--- Call the on_completion_done method for a completion item
98156--- @param item CompletionItem
99157function M .on_completion_done (item )
@@ -108,10 +166,20 @@ function M.on_completion_done(item)
108166 break
109167 end
110168 end
169+ M .clear_pending ()
111170end
112171
113172function M .is_visible ()
114- return M ._pending and next (M ._pending ) ~= nil
173+ if not M ._pending or not next (M ._pending ) then
174+ return false
175+ end
176+ return true
177+ end
178+
179+ function M .clear_pending ()
180+ M ._pending = {}
181+ M ._done_by_event = false
182+ M ._trigger_col = 0
115183end
116184
117185return M
0 commit comments