Skip to content

Commit a53056d

Browse files
authored
fix: snacks model picker order (#282)
1 parent edfecfa commit a53056d

2 files changed

Lines changed: 154 additions & 3 deletions

File tree

lua/opencode/ui/base_picker.lua

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,24 @@ local function snacks_picker_ui(opts)
461461
finder = function()
462462
return opts.items
463463
end,
464+
matcher = {
465+
sort_empty = false,
466+
},
467+
sort = {
468+
fields = { 'score:desc', 'idx' },
469+
},
464470
transform = function(item, ctx)
465-
if not item.text then
466-
local picker_item = opts.format_fn(item)
467-
item.text = picker_item:to_string()
471+
if type(item) == 'table' then
472+
if item.idx == nil then
473+
item.idx = ctx.idx
474+
end
475+
if item.favorite_index and item.favorite_index < 999 then
476+
item.score_add = (item.score_add or 0) + (1000 - item.favorite_index) * 1000
477+
end
478+
if not item.text then
479+
local picker_item = opts.format_fn(item)
480+
item.text = picker_item:to_string()
481+
end
468482
end
469483
end,
470484
format = function(item)

tests/unit/base_picker_spec.lua

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
describe('opencode.ui.base_picker', function()
2+
local base_picker
3+
local captured_opts
4+
local original_schedule
5+
local saved_modules
6+
7+
before_each(function()
8+
original_schedule = vim.schedule
9+
vim.schedule = function(fn)
10+
fn()
11+
end
12+
13+
saved_modules = {
14+
['opencode.config'] = package.loaded['opencode.config'],
15+
['opencode.util'] = package.loaded['opencode.util'],
16+
['opencode.promise'] = package.loaded['opencode.promise'],
17+
['opencode.ui.picker'] = package.loaded['opencode.ui.picker'],
18+
['opencode.ui.base_picker'] = package.loaded['opencode.ui.base_picker'],
19+
['snacks'] = package.loaded['snacks'],
20+
}
21+
22+
package.loaded['opencode.config'] = {
23+
ui = {
24+
picker_width = 80,
25+
},
26+
debug = {
27+
show_ids = false,
28+
},
29+
}
30+
31+
package.loaded['opencode.util'] = {}
32+
33+
package.loaded['opencode.promise'] = {
34+
wrap = function(value)
35+
return {
36+
and_then = function(_, cb)
37+
cb(value)
38+
end,
39+
}
40+
end,
41+
}
42+
43+
package.loaded['opencode.ui.picker'] = {
44+
get_best_picker = function()
45+
return 'snacks'
46+
end,
47+
}
48+
49+
captured_opts = nil
50+
package.loaded['snacks'] = {
51+
picker = {
52+
pick = function(opts)
53+
captured_opts = opts
54+
end,
55+
},
56+
}
57+
58+
package.loaded['opencode.ui.base_picker'] = nil
59+
base_picker = require('opencode.ui.base_picker')
60+
end)
61+
62+
after_each(function()
63+
vim.schedule = original_schedule
64+
65+
for module_name, module_value in pairs(saved_modules) do
66+
package.loaded[module_name] = module_value
67+
end
68+
end)
69+
70+
it('configures snacks picker to preserve source ordering', function()
71+
base_picker.pick({
72+
title = 'Select model',
73+
items = {
74+
{ name = 'favorite model' },
75+
{ name = 'other model' },
76+
},
77+
format_fn = function(item)
78+
return base_picker.create_picker_item({
79+
{ text = item.name },
80+
})
81+
end,
82+
actions = {},
83+
callback = function() end,
84+
})
85+
86+
assert.is_not_nil(captured_opts)
87+
assert.are.same(false, captured_opts.matcher.sort_empty)
88+
assert.are.same({ 'score:desc', 'idx' }, captured_opts.sort.fields)
89+
end)
90+
91+
it('assigns stable idx values in snacks transform', function()
92+
base_picker.pick({
93+
title = 'Select model',
94+
items = {
95+
{ name = 'favorite model' },
96+
},
97+
format_fn = function(item)
98+
return base_picker.create_picker_item({
99+
{ text = item.name },
100+
})
101+
end,
102+
actions = {},
103+
callback = function() end,
104+
})
105+
106+
assert.is_not_nil(captured_opts)
107+
108+
local item = { name = 'favorite model' }
109+
captured_opts.transform(item, { idx = 7 })
110+
111+
assert.equal(7, item.idx)
112+
assert.equal('favorite model', item.text)
113+
end)
114+
115+
it('boosts score for favorites in snacks transform', function()
116+
base_picker.pick({
117+
title = 'Select model',
118+
items = {
119+
{ name = 'favorite model', favorite_index = 1 },
120+
},
121+
format_fn = function(item)
122+
return base_picker.create_picker_item({
123+
{ text = item.name },
124+
})
125+
end,
126+
actions = {},
127+
callback = function() end,
128+
})
129+
130+
assert.is_not_nil(captured_opts)
131+
132+
local item = { name = 'favorite model', favorite_index = 2 }
133+
captured_opts.transform(item, { idx = 3 })
134+
135+
assert.equal(998000, item.score_add)
136+
end)
137+
end)

0 commit comments

Comments
 (0)