From 7f558289e93a7a1257a630156f4c50c7d6852596 Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Thu, 8 Jan 2026 21:17:06 -0800 Subject: [PATCH] fix(ui): fallback to snacks picker --- lua/opencode/api.lua | 3 +- lua/opencode/git_review.lua | 9 +++-- lua/opencode/provider.lua | 3 +- lua/opencode/ui/input_window.lua | 3 +- lua/opencode/ui/picker.lua | 66 ++++++++++++++++++++++++++++++++ lua/opencode/ui/ui.lua | 3 +- 6 files changed, 79 insertions(+), 8 deletions(-) diff --git a/lua/opencode/api.lua b/lua/opencode/api.lua index c366df73..5ab215d7 100644 --- a/lua/opencode/api.lua +++ b/lua/opencode/api.lua @@ -422,7 +422,8 @@ end M.select_agent = Promise.async(function() local modes = config_file.get_opencode_agents():await() - vim.ui.select(modes, { + local picker = require('opencode.ui.picker') + picker.select(modes, { prompt = 'Select mode:', }, function(selection) if not selection then diff --git a/lua/opencode/git_review.lua b/lua/opencode/git_review.lua index d15bbf61..d3b2bb13 100644 --- a/lua/opencode/git_review.lua +++ b/lua/opencode/git_review.lua @@ -5,6 +5,7 @@ local diff_tab = require('opencode.ui.diff_tab') local utils = require('opencode.util') local session = require('opencode.session') local config_file = require('opencode.config_file') +local picker = require('opencode.ui.picker') local M = {} @@ -156,7 +157,7 @@ M.review = require_git_project(function(ref) M.__current_file_index = 1 diff_tab.open_diff_tab(files[1].left, files[1].right, files[1].file_type) else - vim.ui.select( + picker.select( vim.tbl_map(function(f) return vim.fn.fnamemodify(f.left, ':.') end, files), @@ -267,7 +268,7 @@ M.revert_selected_file = require_git_project(function(ref) return end - vim.ui.select( + picker.select( vim.tbl_map(function(f) return vim.fn.fnamemodify(f.left, ':.') end, files), @@ -336,7 +337,7 @@ M.restore_snapshot_file = require_git_project(function(restore_point_id) end local files = get_changed_files(restore_point.id) - vim.ui.select( + picker.select( vim.tbl_map(function(f) return vim.fn.fnamemodify(f.left, ':.') end, files), @@ -363,7 +364,7 @@ function M.with_restore_point(restore_point_id, fn) if #restore_points == 1 then return fn(restore_points[1]) end - vim.ui.select(restore_points, { + picker.select(restore_points, { prompt = 'Select a restore point to restore:', format_item = function(item) return (require('opencode.ui.icons').get('file') .. '[+%d,-%d] %s - %s (from: %s)'):format( diff --git a/lua/opencode/provider.lua b/lua/opencode/provider.lua index ec0aa4fd..daa6e573 100644 --- a/lua/opencode/provider.lua +++ b/lua/opencode/provider.lua @@ -24,7 +24,8 @@ end function M.select(cb) local models = M._get_models() - vim.ui.select(models, { + local picker = require('opencode.ui.picker') + picker.select(models, { prompt = 'Select model:', format_item = function(item) return item.display diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index 0e589cba..183f5f32 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -124,7 +124,8 @@ M._prompt_add_to_context = function(cmd, output, exit_code) output_window.set_lines(lines) - vim.ui.select({ 'Yes', 'No' }, { + local picker = require('opencode.ui.picker') + picker.select({ 'Yes', 'No' }, { prompt = 'Add command + output to context?', }, function(choice) if choice == 'Yes' then diff --git a/lua/opencode/ui/picker.lua b/lua/opencode/ui/picker.lua index e4803820..2be5dc1c 100644 --- a/lua/opencode/ui/picker.lua +++ b/lua/opencode/ui/picker.lua @@ -27,4 +27,70 @@ function M.get_best_picker() return nil end +---Select function that works around Snacks.nvim vim.ui.select bug +---See: https://github.com/folke/snacks.nvim/issues/2539 +---For Snacks, uses Snacks.picker directly to avoid the height calculation bug +---For all other pickers, uses vim.ui.select which respects user customizations +---@param items any[] The items to select from +---@param opts { prompt?: string, format_item?: fun(item: any): string, kind?: string } Options for the select +---@param on_choice fun(item: any?, idx: integer?) Callback when item is selected +function M.select(items, opts, on_choice) + opts = opts or {} + + local picker_type = M.get_best_picker() + + if picker_type == 'snacks' then + M._snacks_select(items, opts, on_choice) + else + vim.ui.select(items, opts, on_choice) + end +end + +---Snacks picker implementation for select (workaround for vim.ui.select bug) +---@param items any[] +---@param opts { prompt?: string, format_item?: fun(item: any): string } +---@param on_choice fun(item: any?, idx: integer?) +function M._snacks_select(items, opts, on_choice) + local Snacks = require('snacks') + + local format_item = opts.format_item or tostring + + -- Build items with indices for tracking + local picker_items = {} + for idx, item in ipairs(items) do + table.insert(picker_items, { + text = format_item(item), + item = item, + idx = idx, + }) + end + + Snacks.picker.pick({ + title = opts.prompt and opts.prompt:gsub(':?%s*$', '') or 'Select', + items = picker_items, + layout = { + preview = false, + preset = 'select', + }, + format = function(picker_item) + return { { picker_item.text } } + end, + preview = function() + return false + end, + confirm = function(picker, picker_item) + picker:close() + if picker_item then + vim.schedule(function() + on_choice(picker_item.item, picker_item.idx) + end) + else + vim.schedule(function() + on_choice(nil, nil) + end) + end + end, + }) +end + return M diff --git a/lua/opencode/ui/ui.lua b/lua/opencode/ui/ui.lua index c160840c..f267fc5c 100644 --- a/lua/opencode/ui/ui.lua +++ b/lua/opencode/ui/ui.lua @@ -215,10 +215,11 @@ end function M.select_session(sessions, cb) local session_picker = require('opencode.ui.session_picker') local util = require('opencode.util') + local picker = require('opencode.ui.picker') local success = session_picker.pick(sessions, cb) if not success then - vim.ui.select(sessions, { + picker.select(sessions, { prompt = '', format_item = function(session) local parts = {}