Skip to content

Commit da52c5b

Browse files
committed
fix: accept completion once filtered
1 parent 54ed293 commit da52c5b

2 files changed

Lines changed: 92 additions & 7 deletions

File tree

ftplugin/opencode.lua

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ if vim.b.did_ftplugin then
22
return
33
end
44
vim.b.did_ftplugin = true
5-
-- Auto-attach opencode LSP to opencode input buffers
6-
75
-- This provides completion for files, subagents, commands, and context items
86
-- Works with any LSP-compatible completion plugin (blink.cmp, nvim-cmp, etc.)
97

@@ -16,8 +14,10 @@ local config = require('opencode.config')
1614
local use_native_completion = config.ui.completion.use_native_completion
1715

1816
if client_id then
17+
local augroup = vim.api.nvim_create_augroup('OpencodeLSP', { clear = true })
1918
-- track insert start state
2019
vim.api.nvim_create_autocmd('InsertEnter', {
20+
group = augroup,
2121
buffer = bufnr,
2222
callback = function()
2323
if use_native_completion then
@@ -28,9 +28,26 @@ if client_id then
2828
})
2929

3030
vim.api.nvim_create_autocmd('TextChangedI', {
31+
group = augroup,
3132
buffer = bufnr,
3233
callback = function(e)
3334
completion.on_text_changed()
3435
end,
3536
})
37+
38+
vim.api.nvim_create_autocmd('InsertLeave', {
39+
group = augroup,
40+
buffer = bufnr,
41+
callback = function()
42+
completion.clear_pending()
43+
end,
44+
})
45+
46+
vim.api.nvim_create_autocmd({ 'CompleteDone' }, {
47+
group = augroup,
48+
buffer = bufnr,
49+
callback = function(e)
50+
completion.on_complete_done_event()
51+
end,
52+
})
3653
end

lua/opencode/ui/completion.lua

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

911
function 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()
6268
end
6369

6470
function 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
95107
end
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
99157
function 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()
111170
end
112171

113172
function 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
115183
end
116184

117185
return M

0 commit comments

Comments
 (0)