From cfa8c0acb16ef152d50dcdf65d71c77634ba734c Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Tue, 13 Jan 2026 17:31:45 -0800 Subject: [PATCH 1/5] fix: defer input window keymaps to blink.cmp when visible When blink.cmp completion menu is open, input window keymaps now defer to blink instead of taking precedence. Fixes #183. --- lua/opencode/keymap.lua | 27 ++++++++++++++++++++++----- lua/opencode/ui/input_window.lua | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lua/opencode/keymap.lua b/lua/opencode/keymap.lua index 2ed362fb..3d09c28f 100644 --- a/lua/opencode/keymap.lua +++ b/lua/opencode/keymap.lua @@ -1,10 +1,24 @@ local M = {} --- Helper function to process keymap entries +local function is_blink_visible() + local ok, blink = pcall(require, 'blink.cmp') + return ok and blink.is_visible() +end + +local function wrap_with_blink_check(key_binding, callback) + return function() + if is_blink_visible() then + return vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key_binding, true, false, true), 'n', false) + end + return callback() + end +end + ---@param keymap_config table The keymap configuration table ---@param default_modes table Default modes for these keymaps ---@param base_opts table Base options to use for all keymaps -local function process_keymap_entry(keymap_config, default_modes, base_opts) +---@param defer_to_blink boolean? Whether to defer to blink.cmp when visible +local function process_keymap_entry(keymap_config, default_modes, base_opts, defer_to_blink) local api = require('opencode.api') local cmds = api.commands @@ -19,6 +33,9 @@ local function process_keymap_entry(keymap_config, default_modes, base_opts) opts.desc = config_entry.desc or cmds[func_name] and cmds[func_name].desc if callback then + if defer_to_blink then + callback = wrap_with_blink_check(key_binding, callback) + end vim.keymap.set(modes, key_binding, callback, opts) else vim.notify(string.format('No action found for keymap: %s -> %s', key_binding, func_name), vim.log.levels.WARN) @@ -34,15 +51,15 @@ function M.setup(keymap) process_keymap_entry(keymap.editor or {}, { 'n', 'v' }, { silent = false }) end --- Setup window-specific keymaps (shared helper for input/output windows) ---@param keymap_config table Window keymap configuration ---@param buf_id integer Buffer ID to set keymaps for -function M.setup_window_keymaps(keymap_config, buf_id) +---@param defer_to_blink boolean? Whether to defer to blink.cmp when visible (default: false) +function M.setup_window_keymaps(keymap_config, buf_id, defer_to_blink) if not vim.api.nvim_buf_is_valid(buf_id) then return end - process_keymap_entry(keymap_config or {}, { 'n' }, { silent = true, buffer = buf_id }) + process_keymap_entry(keymap_config or {}, { 'n' }, { silent = true, buffer = buf_id }, defer_to_blink) end return M diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index d567b9b3..a8c1e2b6 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -351,7 +351,7 @@ end function M.setup_keymaps(windows) local keymap = require('opencode.keymap') - keymap.setup_window_keymaps(config.keymap.input_window, windows.input_buf) + keymap.setup_window_keymaps(config.keymap.input_window, windows.input_buf, true) end function M.setup_autocmds(windows, group) From 921c2e1080c10b7264f1ca27ba8c8eb02237b85d Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Tue, 13 Jan 2026 17:42:22 -0800 Subject: [PATCH 2/5] feat(keymap): integrate completion visibility checks into keymap processing - replace blink checks with completion checks to enhance functionality - add new `is_visible` methods to completion engines for consistent API - updates on keymap entry processing to use new completion checks - refactor related function names for clarity and alignment with new logic --- lua/opencode/keymap.lua | 24 +++++++++---------- lua/opencode/ui/completion.lua | 7 ++++++ lua/opencode/ui/completion/engines/base.lua | 7 ++++++ .../ui/completion/engines/blink_cmp.lua | 7 ++++++ .../ui/completion/engines/nvim_cmp.lua | 7 ++++++ .../ui/completion/engines/vim_complete.lua | 6 +++++ 6 files changed, 46 insertions(+), 12 deletions(-) diff --git a/lua/opencode/keymap.lua b/lua/opencode/keymap.lua index 3d09c28f..86ac60b6 100644 --- a/lua/opencode/keymap.lua +++ b/lua/opencode/keymap.lua @@ -1,13 +1,13 @@ local M = {} -local function is_blink_visible() - local ok, blink = pcall(require, 'blink.cmp') - return ok and blink.is_visible() +local function is_completion_visible() + local ok, completion = pcall(require, 'opencode.ui.completion') + return ok and completion.is_visible() end -local function wrap_with_blink_check(key_binding, callback) +local function wrap_with_completion_check(key_binding, callback) return function() - if is_blink_visible() then + if is_completion_visible() then return vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key_binding, true, false, true), 'n', false) end return callback() @@ -17,8 +17,8 @@ end ---@param keymap_config table The keymap configuration table ---@param default_modes table Default modes for these keymaps ---@param base_opts table Base options to use for all keymaps ----@param defer_to_blink boolean? Whether to defer to blink.cmp when visible -local function process_keymap_entry(keymap_config, default_modes, base_opts, defer_to_blink) +---@param defer_to_completion boolean? Whether to defer to completion engine when visible +local function process_keymap_entry(keymap_config, default_modes, base_opts, defer_to_completion) local api = require('opencode.api') local cmds = api.commands @@ -33,8 +33,8 @@ local function process_keymap_entry(keymap_config, default_modes, base_opts, def opts.desc = config_entry.desc or cmds[func_name] and cmds[func_name].desc if callback then - if defer_to_blink then - callback = wrap_with_blink_check(key_binding, callback) + if defer_to_completion then + callback = wrap_with_completion_check(key_binding, callback) end vim.keymap.set(modes, key_binding, callback, opts) else @@ -53,13 +53,13 @@ end ---@param keymap_config table Window keymap configuration ---@param buf_id integer Buffer ID to set keymaps for ----@param defer_to_blink boolean? Whether to defer to blink.cmp when visible (default: false) -function M.setup_window_keymaps(keymap_config, buf_id, defer_to_blink) +---@param defer_to_completion boolean? Whether to defer to completion engine when visible (default: false) +function M.setup_window_keymaps(keymap_config, buf_id, defer_to_completion) if not vim.api.nvim_buf_is_valid(buf_id) then return end - process_keymap_entry(keymap_config or {}, { 'n' }, { silent = true, buffer = buf_id }, defer_to_blink) + process_keymap_entry(keymap_config or {}, { 'n' }, { silent = true, buffer = buf_id }, defer_to_completion) end return M diff --git a/lua/opencode/ui/completion.lua b/lua/opencode/ui/completion.lua index 4877368d..f0d9a65b 100644 --- a/lua/opencode/ui/completion.lua +++ b/lua/opencode/ui/completion.lua @@ -136,4 +136,11 @@ function M.hide_completion() end end +function M.is_visible() + if M._current_engine and M._current_engine.is_visible then + return M._current_engine:is_visible() + end + return false +end + return M diff --git a/lua/opencode/ui/completion/engines/base.lua b/lua/opencode/ui/completion/engines/base.lua index b12fda9d..4a0546aa 100644 --- a/lua/opencode/ui/completion/engines/base.lua +++ b/lua/opencode/ui/completion/engines/base.lua @@ -106,6 +106,13 @@ function CompletionEngine:trigger(trigger_char) -- Default implementation does nothing end +---Check if completion menu is currently visible +---Child classes should override this with engine-specific implementation +---@return boolean +function CompletionEngine:is_visible() + return false +end + ---Handle completion item selection ---Called when a completion item is selected by the user ---Delegates to the completion module's on_complete handler diff --git a/lua/opencode/ui/completion/engines/blink_cmp.lua b/lua/opencode/ui/completion/engines/blink_cmp.lua index 6b293bb2..8cc63ac7 100644 --- a/lua/opencode/ui/completion/engines/blink_cmp.lua +++ b/lua/opencode/ui/completion/engines/blink_cmp.lua @@ -59,6 +59,13 @@ function BlinkCmpEngine:setup(completion_sources) return true end +---Check if blink-cmp completion menu is visible +---@return boolean +function BlinkCmpEngine:is_visible() + local ok, blink = pcall(require, 'blink.cmp') + return ok and blink.is_visible() +end + ---Trigger completion manually for blink-cmp ---@param trigger_char string function BlinkCmpEngine:trigger(trigger_char) diff --git a/lua/opencode/ui/completion/engines/nvim_cmp.lua b/lua/opencode/ui/completion/engines/nvim_cmp.lua index ae929f10..c60dc901 100644 --- a/lua/opencode/ui/completion/engines/nvim_cmp.lua +++ b/lua/opencode/ui/completion/engines/nvim_cmp.lua @@ -125,6 +125,13 @@ function NvimCmpEngine:setup(completion_sources) return true end +---Check if nvim-cmp completion menu is visible +---@return boolean +function NvimCmpEngine:is_visible() + local ok, cmp = pcall(require, 'cmp') + return ok and cmp.visible() +end + ---Trigger completion manually for nvim-cmp ---@param trigger_char string function NvimCmpEngine:trigger(trigger_char) diff --git a/lua/opencode/ui/completion/engines/vim_complete.lua b/lua/opencode/ui/completion/engines/vim_complete.lua index d679ec47..46696ac7 100644 --- a/lua/opencode/ui/completion/engines/vim_complete.lua +++ b/lua/opencode/ui/completion/engines/vim_complete.lua @@ -45,6 +45,12 @@ function VimCompleteEngine:setup(completion_sources) return true end +---Check if vim completion menu is visible +---@return boolean +function VimCompleteEngine:is_visible() + return vim.fn.pumvisible() == 1 +end + ---Trigger completion manually for vim ---@param trigger_char string function VimCompleteEngine:trigger(trigger_char) From 748073d224723cba40de07fcbb3ceb3a9b53719e Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Tue, 13 Jan 2026 18:50:20 -0800 Subject: [PATCH 3/5] feat(ui): enhance input window with keymap setup logic --- lua/opencode/ui/input_window.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index a8c1e2b6..5c41476c 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -349,7 +349,14 @@ function M.is_empty() return #lines == 0 or (#lines == 1 and lines[1] == '') end +local keymaps_set_for_buf = {} + function M.setup_keymaps(windows) + if keymaps_set_for_buf[windows.input_buf] then + return + end + keymaps_set_for_buf[windows.input_buf] = true + local keymap = require('opencode.keymap') keymap.setup_window_keymaps(config.keymap.input_window, windows.input_buf, true) end From 87933962e98ab6358ab732c788e5ba8a883ad7c9 Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Tue, 13 Jan 2026 19:03:08 -0800 Subject: [PATCH 4/5] fix(ui): improve input window focus logic - remove unnecessary return after showing the input window - streamlines the focus_input function by reducing early exits --- lua/opencode/ui/ui.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/opencode/ui/ui.lua b/lua/opencode/ui/ui.lua index 140bd7b8..ce293ebe 100644 --- a/lua/opencode/ui/ui.lua +++ b/lua/opencode/ui/ui.lua @@ -137,7 +137,6 @@ function M.focus_input(opts) if input_window.is_hidden() then input_window._show() - return end if not windows.input_win then From c8240acba8133cf5249c040f51da27fe7f5a41dd Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Wed, 14 Jan 2026 14:52:46 -0800 Subject: [PATCH 5/5] fix(completion): enhance availability check for completion engine - improve logic to check if the current buffer is the opencode input - ensure engines remain enabled when the input window is toggled - updates(input_window): set filetype for input buffer to 'opencode' --- lua/opencode/ui/completion/engines/base.lua | 12 ++++++++++-- lua/opencode/ui/input_window.lua | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lua/opencode/ui/completion/engines/base.lua b/lua/opencode/ui/completion/engines/base.lua index 4a0546aa..2ec4a3ba 100644 --- a/lua/opencode/ui/completion/engines/base.lua +++ b/lua/opencode/ui/completion/engines/base.lua @@ -46,11 +46,19 @@ function CompletionEngine.get_trigger_characters() end ---Check if the completion engine is available for use ----Default implementation checks if current buffer filetype is 'opencode' +---Default implementation checks if current buffer is the opencode input buffer +---or has filetype 'opencode'. ---Child classes can override this to add engine-specific availability checks ---@return boolean true if the engine can be used in the current context function CompletionEngine.is_available() - return vim.bo.filetype == 'opencode' + local current_buf = vim.api.nvim_get_current_buf() + if vim.bo[current_buf].filetype == 'opencode' then + return true + end + + local ok, state = pcall(require, 'opencode.state') + local input_buf = ok and state.windows and state.windows.input_buf + return input_buf ~= nil and input_buf == current_buf end ---Parse trigger characters from text before cursor diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index 5c41476c..9625201e 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -183,6 +183,10 @@ M._execute_slash_command = function(command) end function M.setup(windows) + -- Ensure completion engines relying on `opencode` filetype stay enabled even + -- after the input window is hidden/shown (some user autocmds can clobber it). + vim.api.nvim_set_option_value('filetype', 'opencode', { buf = windows.input_buf }) + vim.api.nvim_set_option_value('winhighlight', config.ui.window_highlight, { win = windows.input_win }) vim.api.nvim_set_option_value('wrap', config.ui.input.text.wrap, { win = windows.input_win }) vim.api.nvim_set_option_value('signcolumn', 'yes', { win = windows.input_win })