diff --git a/README.md b/README.md index 0c80d419..063d17c8 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,6 @@ require('opencode').setup({ input_position = 'bottom', -- 'bottom' (default) or 'top'. Position of the input window window_width = 0.40, -- Width as percentage of editor width zoom_width = 0.8, -- Zoom width as percentage of editor width - input_height = 0.15, -- Input height as percentage of window height display_model = true, -- Display model name on top winbar display_context_size = true, -- Display context size in the footer display_cost = true, -- Display cost in the footer @@ -220,9 +219,13 @@ require('opencode').setup({ }, }, input = { + min_height = 0.10, -- min height of prompt input as percentage of window height + max_height = 0.25, -- max height of prompt input as percentage of window height text = { wrap = false, -- Wraps text inside input window }, + -- Auto-hide input window when prompt is submitted or focus switches to output window + auto_hide = false, }, picker = { snacks_layout = nil -- `layout` opts to pass to Snacks.picker.pick({ layout = ... }) diff --git a/lua/opencode/config.lua b/lua/opencode/config.lua index 500d1cf2..14d131ff 100644 --- a/lua/opencode/config.lua +++ b/lua/opencode/config.lua @@ -110,7 +110,6 @@ M.defaults = { input_position = 'bottom', window_width = 0.40, zoom_width = 0.8, - input_height = 0.15, picker_width = 100, display_model = true, display_context_size = true, @@ -137,6 +136,8 @@ M.defaults = { always_scroll_to_bottom = false, }, input = { + min_height = 0.10, + max_height = 0.25, text = { wrap = false, }, diff --git a/lua/opencode/health.lua b/lua/opencode/health.lua index 77c9d91b..cad27ceb 100644 --- a/lua/opencode/health.lua +++ b/lua/opencode/health.lua @@ -133,13 +133,38 @@ local function check_configuration() health.ok(string.format('Window width: %s', config.ui.window_width)) end - if config.ui.input_height <= 0 or config.ui.input_height > 1 then + local min_height = config.ui.input.min_height + local max_height = config.ui.input.max_height + if type(min_height) ~= 'number' or type(max_height) ~= 'number' then health.warn( - string.format('Invalid input height: %s', config.ui.input_height), - { 'Input height should be between 0 and 1 (percentage of screen)' } + 'Input min/max height configuration incomplete', + { 'Set both ui.input.min_height and ui.input.max_height to enable auto-resize' } ) else - health.ok(string.format('Input height: %s', config.ui.input_height)) + if min_height <= 0 or min_height > 1 then + health.warn( + string.format('Invalid input min height: %s', min_height), + { 'Input min height should be between 0 and 1 (percentage of screen)' } + ) + else + health.ok(string.format('Input min height: %s', min_height)) + end + + if max_height <= 0 or max_height > 1 then + health.warn( + string.format('Invalid input max height: %s', max_height), + { 'Input max height should be between 0 and 1 (percentage of screen)' } + ) + else + health.ok(string.format('Input max height: %s', max_height)) + end + + if min_height > max_height then + health.warn( + 'Input min height exceeds max height', + { 'Ensure ui.input.min_height is less than or equal to ui.input.max_height' } + ) + end end health.ok('Configuration loaded successfully') diff --git a/lua/opencode/types.lua b/lua/opencode/types.lua index 7689a4c6..69067c13 100644 --- a/lua/opencode/types.lua +++ b/lua/opencode/types.lua @@ -116,7 +116,6 @@ ---@field input_position 'bottom'|'top' # Position of the input window (default: 'bottom') ---@field window_width number ---@field zoom_width number ----@field input_height number ---@field picker_width number|nil # Default width for all pickers (nil uses current window width) ---@field display_model boolean ---@field display_context_size boolean @@ -125,11 +124,17 @@ ---@field icons { preset: 'text'|'nerdfonts', overrides: table } ---@field loading_animation OpencodeLoadingAnimationConfig ---@field output OpencodeUIOutputConfig ----@field input { text: { wrap: boolean } } +---@field input OpencodeUIInputConfig ---@field completion OpencodeCompletionConfig ---@field highlights? OpencodeHighlightConfig ---@field picker OpencodeUIPickerConfig +---@class OpencodeUIInputConfig +---@field text { wrap: boolean } +---@field min_height number +---@field max_height number +---@field auto_hide boolean + ---@class OpencodeHighlightConfig ---@field vertical_borders? { tool?: { fg?: string, bg?: string }, user?: { fg?: string, bg?: string }, assistant?: { fg?: string, bg?: string } } diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index 0f092ca2..ad7de21f 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -6,6 +6,54 @@ local M = {} M._hidden = false -- Flag to prevent WinClosed autocmd from closing all windows during toggle M._toggling = false +M._resize_scheduled = false + +local function get_content_height(windows) + local line_count = vim.api.nvim_buf_line_count(windows.input_buf) + if line_count <= 0 then + return 1 + end + if config.ui.input.text.wrap then + local ok, result = pcall(vim.api.nvim_win_text_height, windows.input_win, { + start_row = 0, + end_row = math.max(0, line_count - 1), + }) + if ok and result and result.all then + return result.all + end + end + + return line_count +end + +local function get_winbar_height(windows) + local ok, winbar = pcall(vim.api.nvim_get_option_value, 'winbar', { win = windows.input_win }) + if ok and type(winbar) == 'string' and winbar ~= '' then + return 1 + end + + return 0 +end + +local function calculate_height(windows) + local total_height = vim.api.nvim_get_option_value('lines', {}) + local min_height = math.max(1, math.floor(total_height * config.ui.input.min_height)) + local max_height = math.max(min_height, math.floor(total_height * config.ui.input.max_height)) + local content_height = get_content_height(windows) + get_winbar_height(windows) + return math.min(max_height, math.max(min_height, content_height)) +end + +local function apply_dimensions(windows, height) + if config.ui.position == 'current' then + pcall(vim.api.nvim_win_set_height, windows.input_win, height) + return + end + + local total_width = vim.api.nvim_get_option_value('columns', {}) + local width = math.floor(total_width * config.ui.window_width) + + vim.api.nvim_win_set_config(windows.input_win, { width = width, height = height }) +end function M.create_buf() local input_buf = vim.api.nvim_create_buf(false, true) @@ -226,18 +274,23 @@ function M.update_dimensions(windows) return end - local total_height = vim.api.nvim_get_option_value('lines', {}) - local height = math.floor(total_height * config.ui.input_height) + local height = calculate_height(windows) + apply_dimensions(windows, height) +end - if config.ui.position == 'current' then - pcall(vim.api.nvim_win_set_height, windows.input_win, height) +function M.schedule_resize(windows) + windows = windows or state.windows + if not M.mounted(windows) or M._resize_scheduled then return end - local total_width = vim.api.nvim_get_option_value('columns', {}) - local width = math.floor(total_width * config.ui.window_width) - - vim.api.nvim_win_set_config(windows.input_win, { width = width, height = height }) + M._resize_scheduled = true + vim.schedule(function() + M._resize_scheduled = false + if M.mounted(windows) then + M.update_dimensions(windows) + end + end) end function M.refresh_placeholder(windows, input_lines) @@ -289,6 +342,7 @@ end function M.recover_input(windows) M.set_content(state.input_content, windows) require('opencode.ui.mention').highlight_all_mentions(windows.input_buf) + M.update_dimensions(windows) end function M.focus_input() @@ -413,6 +467,7 @@ function M.setup_autocmds(windows, group) state.input_content = input_lines M.refresh_placeholder(windows, input_lines) require('opencode.ui.context_bar').render() + M.schedule_resize(windows) end, }) end