From 6b5819a243fdb05a2d89bb81f4524346fbaa3743 Mon Sep 17 00:00:00 2001 From: Julio Garcia Date: Sat, 14 Mar 2026 18:04:24 +0100 Subject: [PATCH] feat(ui): add o to close all non-opencode windows Implements an opencode-aware "only" window command, mapped to o, that closes all windows except the opencode output and input windows. If the input window is hidden, it will be shown after closing other windows. Updates autocmds to handle live window ID lookups and avoid tearing down UI when only input/footer is closed. Keymap added to config for both normal and insert modes. --- lua/opencode/api.lua | 38 ++++++++++++++++++++++++++++++++++++ lua/opencode/config.lua | 2 ++ lua/opencode/ui/autocmds.lua | 28 +++++++++++++++++++------- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/lua/opencode/api.lua b/lua/opencode/api.lua index 212c1aa8..c9836a14 100644 --- a/lua/opencode/api.lua +++ b/lua/opencode/api.lua @@ -27,6 +27,37 @@ function M.toggle_input() input_window.toggle() end +---Close all windows except the opencode output and input windows (like o but opencode-aware). +---If input is hidden it will be shown after closing other windows. +function M.only_opencode() + local windows = state.windows + if not windows or not windows.output_win then + return + end + + -- Collect all windows in the current tabpage that are NOT opencode windows + local tabpage = vim.api.nvim_get_current_tabpage() + local all_wins = vim.api.nvim_tabpage_list_wins(tabpage) + for _, win in ipairs(all_wins) do + if win ~= windows.output_win + and win ~= windows.input_win + and win ~= windows.footer_win + and vim.api.nvim_win_is_valid(win) + then + local cfg = vim.api.nvim_win_get_config(win) + -- Skip floating windows that don't belong to opencode + if cfg.relative == '' then + pcall(vim.api.nvim_win_close, win, false) + end + end + end + + -- If input was hidden, show it + if input_window.is_hidden() then + input_window._show() + end +end + function M.open_input() return core.open({ new_session = false, focus = 'input', start_insert = true }) end @@ -1109,6 +1140,13 @@ M.commands = { end, }, + only_opencode = { + desc = 'Close all non-opencode windows (keep output + input)', + fn = function() + M.only_opencode() + end, + }, + hide = { desc = 'Hide opencode windows (preserve buffers for fast restore)', fn = function(args) diff --git a/lua/opencode/config.lua b/lua/opencode/config.lua index 85c02c57..29aaaaee 100644 --- a/lua/opencode/config.lua +++ b/lua/opencode/config.lua @@ -67,6 +67,7 @@ M.defaults = { ['gr'] = { 'references', desc = 'Browse code references' }, [''] = { 'toggle_input', mode = { 'n' }, desc = 'Toggle input window' }, [''] = { 'cycle_variant', mode = { 'n' }, desc = 'Cycle model variants' }, + ['o'] = { 'only_opencode', mode = { 'n' }, desc = 'Close all non-opencode windows' }, ['oS'] = { 'select_child_session', desc = 'Select child session' }, ['oD'] = { 'debug_message', desc = 'Open raw message debug view' }, ['oO'] = { 'debug_output', desc = 'Open raw output debug view' }, @@ -88,6 +89,7 @@ M.defaults = { [''] = { 'switch_mode', mode = { 'n', 'i' }, desc = 'Switch agent mode' }, [''] = { 'cycle_variant', mode = { 'n', 'i' }, desc = 'Cycle model variants' }, [''] = { 'toggle_input', mode = { 'n', 'i' }, desc = 'Toggle input window' }, + ['o'] = { 'only_opencode', mode = { 'n' }, desc = 'Close all non-opencode windows' }, ['gr'] = { 'references', desc = 'Browse code references' }, ['oS'] = { 'select_child_session', desc = 'Select child session' }, ['oD'] = { 'debug_message', desc = 'Open raw message debug view' }, diff --git a/lua/opencode/ui/autocmds.lua b/lua/opencode/ui/autocmds.lua index 4f6b9a11..85e4b55e 100644 --- a/lua/opencode/ui/autocmds.lua +++ b/lua/opencode/ui/autocmds.lua @@ -7,11 +7,11 @@ function M.setup_autocmds(windows) input_window.setup_autocmds(windows, group) output_window.setup_autocmds(windows, group) - -- Only keep shared autocmds here (e.g., WinClosed, WinLeave for all windows) - local wins = { windows.input_win, windows.output_win, windows.footer_win } + -- Use a global WinClosed handler that does live lookups to handle + -- the case where input_win is recreated (new ID) after a hide/show cycle. vim.api.nvim_create_autocmd('WinClosed', { group = group, - pattern = table.concat(wins, ','), + pattern = '*', callback = function(opts) -- Don't close everything if we're just toggling the input window if input_window._toggling then @@ -19,11 +19,25 @@ function M.setup_autocmds(windows) end local closed_win = tonumber(opts.match) - if vim.tbl_contains(wins, closed_win) then - vim.schedule(function() - require('opencode.ui.ui').teardown_visible_windows(windows) - end) + + -- Live lookup: get the current opencode window IDs at the time of the event + local is_output_win = closed_win == windows.output_win + local is_input_win = windows.input_win ~= nil and closed_win == windows.input_win + local is_footer_win = windows.footer_win ~= nil and closed_win == windows.footer_win + + if not is_output_win and not is_input_win and not is_footer_win then + return end + + -- If a non-output opencode window was closed (e.g. input/footer via o), + -- and the output window is still valid, don't tear down the whole UI. + if not is_output_win and windows.output_win and vim.api.nvim_win_is_valid(windows.output_win) then + return + end + + vim.schedule(function() + require('opencode.ui.ui').teardown_visible_windows(windows) + end) end, })