diff --git a/lua/opencode/state.lua b/lua/opencode/state.lua index 47561ef4..f87471f0 100644 --- a/lua/opencode/state.lua +++ b/lua/opencode/state.lua @@ -80,7 +80,8 @@ ---@field clear_hidden_window_state fun() ---@field has_hidden_buffers fun(): boolean ---@field consume_hidden_buffers fun(): OpencodeHiddenBuffers|nil ----@field resolve_toggle_decision fun(persist_state: boolean, has_display_route: boolean): OpencodeToggleDecision + ---@field are_opencode_only_windows fun(): boolean + ---@field resolve_toggle_decision fun(persist_state: boolean, has_display_route: boolean): OpencodeToggleDecision ---@field resolve_open_windows_action fun(): 'reuse_visible'|'restore_hidden'|'create_fresh' ---@field get_window_cursor fun(win_id: integer|nil): integer[]|nil @@ -264,6 +265,43 @@ function M.are_windows_in_current_tab() or M.is_window_in_current_tab(_state.windows.output_win) end +--- Returns true when every normal (non-floating) window in the current tab +--- belongs to opencode (i.e. there are no code windows open alongside it). +---@return boolean +function M.are_opencode_only_windows() + local w = _state.windows + if not w then + return false + end + + local opencode_wins = {} + if w.input_win and vim.api.nvim_win_is_valid(w.input_win) then + opencode_wins[w.input_win] = true + end + if w.output_win and vim.api.nvim_win_is_valid(w.output_win) then + opencode_wins[w.output_win] = true + end + if w.footer_win and vim.api.nvim_win_is_valid(w.footer_win) then + opencode_wins[w.footer_win] = true + end + + -- No opencode windows tracked → not an only-opencode situation + if vim.tbl_isempty(opencode_wins) then + return false + end + + local current_tab = vim.api.nvim_get_current_tabpage() + for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(current_tab)) do + local cfg = vim.api.nvim_win_get_config(win_id) + -- Skip floating windows + if cfg.relative == '' and not opencode_wins[win_id] then + return false + end + end + + return true +end + ---@return boolean function M.is_visible() return M.get_window_state().status == 'visible' @@ -274,6 +312,7 @@ end ---@field in_tab boolean ---@field persist_state boolean ---@field has_display_route boolean +---@field only_windows boolean ---@generic T ---@param rules T[] @@ -323,6 +362,12 @@ local TOGGLE_ACTION_RULES = { return ctx.status == 'visible' and ctx.in_tab and not ctx.persist_state end, }, + { + action = 'hide', + when = function(ctx) + return ctx.status == 'visible' and ctx.in_tab and ctx.only_windows and not ctx.has_display_route + end, + }, { action = 'hide', when = function(ctx) @@ -348,6 +393,7 @@ local function lookup_toggle_action(status, in_tab, persist_state, has_display_r in_tab = in_tab, persist_state = persist_state, has_display_route = has_display_route, + only_windows = M.are_opencode_only_windows(), } local matched_rule = first_matching_rule(TOGGLE_ACTION_RULES, function(rule) diff --git a/lua/opencode/ui/autocmds.lua b/lua/opencode/ui/autocmds.lua index 4f6b9a11..303018e4 100644 --- a/lua/opencode/ui/autocmds.lua +++ b/lua/opencode/ui/autocmds.lua @@ -113,6 +113,46 @@ function M.setup_autocmds(windows) end, }) end + + M.setup_window_only_keymap(windows) +end + +--- Set o / on both opencode buffers to close all other +--- non-floating windows while keeping the opencode pair intact. +--- Also clears the saved width ratio so the next open uses the config default. +---@param windows OpencodeWindowState +function M.setup_window_only_keymap(windows) + local opencode_wins = function() + local t = {} + for _, w in ipairs({ windows.input_win, windows.output_win, windows.footer_win }) do + if w then + t[w] = true + end + end + return t + end + + local handler = function() + local keep = opencode_wins() + keep[vim.api.nvim_get_current_win()] = true + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if not keep[win] and vim.api.nvim_win_is_valid(win) then + local cfg = vim.api.nvim_win_get_config(win) + if cfg.relative == '' then + pcall(vim.api.nvim_win_close, win, false) + end + end + end + -- Don't remember the current opencode width; next open will use config default + require('opencode.state').last_window_width_ratio = nil + end + + for _, buf in ipairs({ windows.input_buf, windows.output_buf }) do + if buf and vim.api.nvim_buf_is_valid(buf) then + vim.keymap.set('n', 'o', handler, { buffer = buf, desc = 'Keep only opencode windows', nowait = true }) + vim.keymap.set('n', '', handler, { buffer = buf, desc = 'Keep only opencode windows', nowait = true }) + end + end end ---@param windows OpencodeWindowState? diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index 9889081e..99bbad8d 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -280,9 +280,9 @@ function M.setup(windows) set_buf_option('buflisted', false, windows) set_buf_option('swapfile', false, windows) - if config.ui.position ~= 'current' then - set_win_option('winfixbuf', true, windows) - end + require('opencode.ui.buf_fix_win').fix_to_win(windows.input_buf, function() + return state.windows and state.windows.input_win + end) set_win_option('winfixwidth', true, windows) M.update_dimensions(windows) diff --git a/lua/opencode/ui/output_window.lua b/lua/opencode/ui/output_window.lua index 5984b1c9..26d3b902 100644 --- a/lua/opencode/ui/output_window.lua +++ b/lua/opencode/ui/output_window.lua @@ -116,9 +116,9 @@ function M.setup(windows) set_buf_option('buflisted', false, windows.output_buf) set_buf_option('swapfile', false, windows.output_buf) - if config.ui.position ~= 'current' then - set_win_option('winfixbuf', true, windows.output_win) - end + require('opencode.ui.buf_fix_win').fix_to_win(windows.output_buf, function() + return state.windows and state.windows.output_win + end) set_win_option('winfixheight', true, windows.output_win) set_win_option('winfixwidth', true, windows.output_win) set_win_option('signcolumn', 'yes', windows.output_win) diff --git a/lua/opencode/ui/ui.lua b/lua/opencode/ui/ui.lua index 92724e08..0eecf22e 100644 --- a/lua/opencode/ui/ui.lua +++ b/lua/opencode/ui/ui.lua @@ -108,7 +108,6 @@ end local function close_or_restore_output_window(windows) if config.ui.position == 'current' then if windows.output_win and vim.api.nvim_win_is_valid(windows.output_win) then - pcall(vim.api.nvim_set_option_value, 'winfixbuf', false, { win = windows.output_win }) if state.current_code_buf and vim.api.nvim_buf_is_valid(state.current_code_buf) then pcall(vim.api.nvim_win_set_buf, windows.output_win, state.current_code_buf) end @@ -135,7 +134,6 @@ function M.hide_visible_windows(windows) local snapshot = capture_hidden_snapshot(windows) - -- Only save width ratio for split modes (not dialog/current mode) if config.ui.position ~= 'current' then local total_cols = vim.o.columns local current_width = vim.api.nvim_win_get_width(windows.output_win) @@ -329,10 +327,6 @@ function M.create_split_windows(input_buf, output_buf) local input_win = open_split(ui_conf.input_position, 'horizontal') local output_win = main_win - if ui_conf.position == 'current' then - pcall(vim.api.nvim_set_option_value, 'winfixbuf', false, { win = output_win }) - end - vim.api.nvim_win_set_buf(input_win, input_buf) vim.api.nvim_win_set_buf(output_win, output_buf) return { input_win = input_win, output_win = output_win }