Skip to content

Commit 6b5819a

Browse files
committed
feat(ui): add <C-w>o to close all non-opencode windows
Implements an opencode-aware "only" window command, mapped to <C-w>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.
1 parent e95905b commit 6b5819a

3 files changed

Lines changed: 61 additions & 7 deletions

File tree

lua/opencode/api.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,37 @@ function M.toggle_input()
2727
input_window.toggle()
2828
end
2929

30+
---Close all windows except the opencode output and input windows (like <C-w>o but opencode-aware).
31+
---If input is hidden it will be shown after closing other windows.
32+
function M.only_opencode()
33+
local windows = state.windows
34+
if not windows or not windows.output_win then
35+
return
36+
end
37+
38+
-- Collect all windows in the current tabpage that are NOT opencode windows
39+
local tabpage = vim.api.nvim_get_current_tabpage()
40+
local all_wins = vim.api.nvim_tabpage_list_wins(tabpage)
41+
for _, win in ipairs(all_wins) do
42+
if win ~= windows.output_win
43+
and win ~= windows.input_win
44+
and win ~= windows.footer_win
45+
and vim.api.nvim_win_is_valid(win)
46+
then
47+
local cfg = vim.api.nvim_win_get_config(win)
48+
-- Skip floating windows that don't belong to opencode
49+
if cfg.relative == '' then
50+
pcall(vim.api.nvim_win_close, win, false)
51+
end
52+
end
53+
end
54+
55+
-- If input was hidden, show it
56+
if input_window.is_hidden() then
57+
input_window._show()
58+
end
59+
end
60+
3061
function M.open_input()
3162
return core.open({ new_session = false, focus = 'input', start_insert = true })
3263
end
@@ -1109,6 +1140,13 @@ M.commands = {
11091140
end,
11101141
},
11111142

1143+
only_opencode = {
1144+
desc = 'Close all non-opencode windows (keep output + input)',
1145+
fn = function()
1146+
M.only_opencode()
1147+
end,
1148+
},
1149+
11121150
hide = {
11131151
desc = 'Hide opencode windows (preserve buffers for fast restore)',
11141152
fn = function(args)

lua/opencode/config.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ M.defaults = {
6767
['gr'] = { 'references', desc = 'Browse code references' },
6868
['<M-i>'] = { 'toggle_input', mode = { 'n' }, desc = 'Toggle input window' },
6969
['<M-r>'] = { 'cycle_variant', mode = { 'n' }, desc = 'Cycle model variants' },
70+
['<C-w>o'] = { 'only_opencode', mode = { 'n' }, desc = 'Close all non-opencode windows' },
7071
['<leader>oS'] = { 'select_child_session', desc = 'Select child session' },
7172
['<leader>oD'] = { 'debug_message', desc = 'Open raw message debug view' },
7273
['<leader>oO'] = { 'debug_output', desc = 'Open raw output debug view' },
@@ -88,6 +89,7 @@ M.defaults = {
8889
['<M-m>'] = { 'switch_mode', mode = { 'n', 'i' }, desc = 'Switch agent mode' },
8990
['<M-r>'] = { 'cycle_variant', mode = { 'n', 'i' }, desc = 'Cycle model variants' },
9091
['<M-i>'] = { 'toggle_input', mode = { 'n', 'i' }, desc = 'Toggle input window' },
92+
['<C-w>o'] = { 'only_opencode', mode = { 'n' }, desc = 'Close all non-opencode windows' },
9193
['gr'] = { 'references', desc = 'Browse code references' },
9294
['<leader>oS'] = { 'select_child_session', desc = 'Select child session' },
9395
['<leader>oD'] = { 'debug_message', desc = 'Open raw message debug view' },

lua/opencode/ui/autocmds.lua

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,37 @@ function M.setup_autocmds(windows)
77
input_window.setup_autocmds(windows, group)
88
output_window.setup_autocmds(windows, group)
99

10-
-- Only keep shared autocmds here (e.g., WinClosed, WinLeave for all windows)
11-
local wins = { windows.input_win, windows.output_win, windows.footer_win }
10+
-- Use a global WinClosed handler that does live lookups to handle
11+
-- the case where input_win is recreated (new ID) after a hide/show cycle.
1212
vim.api.nvim_create_autocmd('WinClosed', {
1313
group = group,
14-
pattern = table.concat(wins, ','),
14+
pattern = '*',
1515
callback = function(opts)
1616
-- Don't close everything if we're just toggling the input window
1717
if input_window._toggling then
1818
return
1919
end
2020

2121
local closed_win = tonumber(opts.match)
22-
if vim.tbl_contains(wins, closed_win) then
23-
vim.schedule(function()
24-
require('opencode.ui.ui').teardown_visible_windows(windows)
25-
end)
22+
23+
-- Live lookup: get the current opencode window IDs at the time of the event
24+
local is_output_win = closed_win == windows.output_win
25+
local is_input_win = windows.input_win ~= nil and closed_win == windows.input_win
26+
local is_footer_win = windows.footer_win ~= nil and closed_win == windows.footer_win
27+
28+
if not is_output_win and not is_input_win and not is_footer_win then
29+
return
2630
end
31+
32+
-- If a non-output opencode window was closed (e.g. input/footer via <C-w>o),
33+
-- and the output window is still valid, don't tear down the whole UI.
34+
if not is_output_win and windows.output_win and vim.api.nvim_win_is_valid(windows.output_win) then
35+
return
36+
end
37+
38+
vim.schedule(function()
39+
require('opencode.ui.ui').teardown_visible_windows(windows)
40+
end)
2741
end,
2842
})
2943

0 commit comments

Comments
 (0)