diff --git a/lua/lazyvim/plugins/extras/ai/opencode.lua b/lua/lazyvim/plugins/extras/ai/opencode.lua index 77f066b6..bfa37c19 100644 --- a/lua/lazyvim/plugins/extras/ai/opencode.lua +++ b/lua/lazyvim/plugins/extras/ai/opencode.lua @@ -1,4 +1,9 @@ -local function safe_ask(prompt, opts) +if not vim.fn.executable("opencode") then + vim.notify("opencode: binary not installed. See opencode.ai", vim.log.levels.ERROR) + return {} +end + +local function ask(prompt, opts) if not vim.fn.executable("opencode") then vim.notify("opencode: binary not installed. See opencode.ai", vim.log.levels.ERROR) return @@ -6,20 +11,7 @@ local function safe_ask(prompt, opts) require("opencode").ask(prompt, opts) end -local function focus_opencode() - vim.schedule(function() - for _, win in ipairs(vim.api.nvim_list_wins()) do - local buf = vim.api.nvim_win_get_buf(win) - if vim.api.nvim_buf_get_name(buf):match("opencode %-%-port") then - vim.api.nvim_set_current_win(win) - end - end - end) -end - return { - recommended = true, - -- which-key { "folke/which-key.nvim", @@ -46,9 +38,8 @@ return { "aa", function() require("opencode").toggle() - focus_opencode() end, - mode = { "n", "t" }, + mode = { "n" }, desc = "Toggle OpenCode", }, @@ -57,7 +48,7 @@ return { function() require("opencode").stop() end, - mode = { "n", "t" }, + mode = { "n" }, desc = "Stop/Close OpenCode", }, @@ -73,7 +64,7 @@ return { { "ai", function() - safe_ask("", { submit = true, focus = false }) + ask("", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask (empty)", @@ -82,7 +73,7 @@ return { { "aI", function() - safe_ask("@this: ", { submit = true, focus = false }) + ask("@this: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask with context", @@ -91,7 +82,7 @@ return { { "ab", function() - safe_ask("@buffer ", { submit = true, focus = false }) + ask("@buffer ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask about buffer", @@ -100,7 +91,7 @@ return { { "agd", function() - safe_ask("@diff ", { submit = true, focus = false }) + ask("@diff ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Git diff", @@ -109,7 +100,7 @@ return { { "ax", function() - safe_ask("@quickfix ", { submit = true, focus = false }) + ask("@quickfix ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask with quickfix", @@ -118,7 +109,7 @@ return { { "av", function() - safe_ask("@visible ", { submit = true, focus = false }) + ask("@visible ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask with visible text", @@ -127,7 +118,7 @@ return { { "aB", function() - safe_ask("@buffers ", { submit = true, focus = false }) + ask("@buffers ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask with all buffers", @@ -136,7 +127,7 @@ return { { "am", function() - safe_ask("@marks ", { submit = true, focus = false }) + ask("@marks ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Ask with marks", @@ -145,7 +136,7 @@ return { { "ape", function() - safe_ask("@this explain: ", { submit = true, focus = false }) + ask("@this explain: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Explain", @@ -154,7 +145,7 @@ return { { "apf", function() - safe_ask("@this fix: ", { submit = true, focus = false }) + ask("@this fix: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Fix", @@ -163,7 +154,7 @@ return { { "apd", function() - safe_ask("@this diagnose: ", { submit = true, focus = false }) + ask("@this diagnose: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Diagnose", @@ -172,7 +163,7 @@ return { { "apr", function() - safe_ask("@this review: ", { submit = true, focus = false }) + ask("@this review: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Review", @@ -181,7 +172,7 @@ return { { "apt", function() - safe_ask("@this test: ", { submit = true, focus = false }) + ask("@this test: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Test", @@ -190,7 +181,7 @@ return { { "apo", function() - safe_ask("@this optimize: ", { submit = true, focus = false }) + ask("@this optimize: ", { submit = true, focus = false }) end, mode = { "n", "x" }, desc = "Optimize", @@ -234,48 +225,58 @@ return { desc = "Add line to OpenCode", }, }, - opts = function() - -- 1. Setup autogroups/autocmds for terminal optimizations - local opencode_augroup = vim.api.nvim_create_augroup("opencode_integrated", { clear = true }) + config = function(_, opts) + local config = require("opencode.config") + local opencode_cmd = "opencode --port" + local snacks_terminal_opts = { + win = { + position = "right", + on_win = function(win) + -- Set up keymaps and cleanup for an arbitrary terminal + require("opencode.terminal").setup(win.win) + end, + enter = false, + }, + -- Otherwise, we get error `Terminal exited with code` when closing Opencode because + -- the `stop` command already closes the terminal window, but the Snacks.terminal `TermClose` + -- autocmd kicks in and tries to close it again, causing the error. + auto_close = false, + } - vim.api.nvim_create_autocmd({ "BufEnter" }, { - group = opencode_augroup, - pattern = "*:opencode --port*", - callback = function() - vim.cmd("startinsert") - end, - }) + config.opts = vim.tbl_deep_extend( + "force", + config.opts, + { + server = { + start = function() + require("snacks.terminal").open(opencode_cmd, snacks_terminal_opts) + end, + stop = function() + local win, created = require("snacks.terminal").get(opencode_cmd, snacks_terminal_opts) + if win and created then + win:close() + vim.cmd.stopinsert() + elseif win and not created then + win:close() + end + end, + toggle = function() + require("snacks.terminal").toggle(opencode_cmd, snacks_terminal_opts) + end, + }, + }, + -- We merge here also `opts` so we can bypass the following limitation with `vim.g` https://github.com/nickjvandyke/opencode.nvim/issues/36 + opts or {}, + vim.g.opencode_opts or {} + ) vim.api.nvim_create_autocmd({ "TermOpen" }, { - -- group = vim.api.nvim_create_augroup("opencode_integrated", { clear = true }), - group = opencode_augroup, + group = vim.api.nvim_create_augroup("opencode_integrated", { clear = true }), pattern = "*:opencode --port*", callback = function(event) vim.bo[event.buf].buflisted = false + vim.bo[event.buf].filetype = "opencode" - -- Override space layout globally inside this exact buffer window - vim.keymap.set("t", "", "", { buffer = event.buf, nowait = true, noremap = true }) - vim.b[event.buf].which_key_ignore = true - - vim.keymap.set({ "t", "n" }, "", "h", { buffer = event.buf, desc = "Go to Left Window" }) - vim.keymap.set( - { "t", "n" }, - "", - "j", - { buffer = event.buf, desc = "Go to Lower Window" } - ) - vim.keymap.set( - { "t", "n" }, - "", - "k", - { buffer = event.buf, desc = "Go to Upper Window" } - ) - vim.keymap.set( - { "t", "n" }, - "", - "l", - { buffer = event.buf, desc = "Go to Right Window" } - ) vim.keymap.set("t", "", function() require("opencode").command("session.half.page.up") end, { buffer = event.buf, desc = "Half scroll back" }) @@ -290,45 +291,6 @@ return { end, { buffer = event.buf, desc = "Scroll forward" }) end, }) - - vim.api.nvim_create_autocmd("VimLeavePre", { - callback = function() - if vim.fn.has("unix") == 1 then - local pid = vim.fn.getpid() - vim.fn.system({ "pkill", "-P", tostring(pid), "-f", "opencode" }) - end - end, - }) - -- Essential: Return an empty table container to stop lazy.nvim fallback calls - return {} - end, - }, - - -- 2. Modify which-key v3 auto-triggers to fully drop terminal mode input tracking - { - "folke/which-key.nvim", - optional = true, - opts = function(_, opts) - opts.triggers = opts.triggers or {} - - -- Check if default auto triggers are present - local has_auto = false - for _, trigger in ipairs(opts.triggers) do - -- Standard v3 layout tables wrap the auto hook directive as an array string value - if type(trigger) == "table" and trigger == "" then - has_auto = true - -- Strip terminal mode ("t") and command mode ("c") from automatic hooking loops - trigger.mode = "nixso" - elseif trigger == "" then - -- Fallback mutation path if declared as a raw flat string pattern - has_auto = true - end - end - - -- If LazyVim default configs haven't initialized it, enforce a clean non-terminal rule - if not has_auto then - table.insert(opts.triggers, { "", mode = "nixso" }) - end end, }, }