diff --git a/lua/opencode/keymap.lua b/lua/opencode/keymap.lua index 2ed362fb..86ac60b6 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_completion_visible() + local ok, completion = pcall(require, 'opencode.ui.completion') + return ok and completion.is_visible() +end + +local function wrap_with_completion_check(key_binding, callback) + return function() + 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() + 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_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 @@ -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_completion then + callback = wrap_with_completion_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_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 }) + 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..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 @@ -106,6 +114,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) diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index f849435c..e1cfbb68 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -183,11 +183,13 @@ M._execute_slash_command = function(command) end function M.setup(windows) + if config.ui.input.text.wrap then vim.api.nvim_set_option_value('wrap', true, { win = windows.input_win }) vim.api.nvim_set_option_value('linebreak', true, { win = windows.input_win }) end + 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('signcolumn', 'yes', { win = windows.input_win }) vim.api.nvim_set_option_value('cursorline', false, { win = windows.input_win }) @@ -353,9 +355,16 @@ 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) + keymap.setup_window_keymaps(config.keymap.input_window, windows.input_buf, true) end function M.setup_autocmds(windows, group) 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