33--- @field on_select function (index : integer ) Called when an option is selected
44--- @field on_dismiss ? function () Called when dialog is dismissed
55--- @field on_navigate ? function () Called when selection changes
6+ --- @field on_navigate_group ? function (index : integer ) Called when group selection changes
67--- @field get_option_count function (): integer Returns the total number of options
8+ --- @field get_group_count ? function (): integer Returns the total number of groups
79--- @field check_focused ? function (): boolean Returns whether dialog should be active
810--- @field keymaps ? DialogKeymaps Custom keymap configuration
911--- @field namespace_prefix ? string Prefix for vim.on_key namespace (default : ' opencode_dialog' )
1214--- @class DialogKeymaps
1315--- @field up ? string[] Keys for navigating up (default : { ' k' , ' <Up>' } )
1416--- @field down ? string[] Keys for navigating down (default : { ' j' , ' <Down>' } )
17+ --- @field left ? string[] Keys for navigating left between groups
18+ --- @field right ? string[] Keys for navigating right between groups
1519--- @field select ? string Key for selecting current option (default : ' <CR>' )
1620--- @field dismiss ? string Key for dismissing dialog (default : ' <Esc>' )
1721--- @field number_shortcuts ? boolean Enable 1-9 number shortcuts (default : true )
2226--- @field private _key_capture_ns integer ? Namespace for vim.on_key
2327--- @field private _selected_index integer Currently selected option index
2428--- @field private _active boolean Whether dialog is currently active
29+ --- @field private _group_index integer Currently selected group index
2530local Dialog = {}
2631Dialog .__index = Dialog
2732
@@ -35,6 +40,8 @@ function Dialog.new(config)
3540 local default_keymaps = {
3641 up = { ' k' , ' <Up>' },
3742 down = { ' j' , ' <Down>' },
43+ left = {},
44+ right = {},
3845 select = ' <CR>' ,
3946 dismiss = ' <Esc>' ,
4047 number_shortcuts = true ,
@@ -52,6 +59,7 @@ function Dialog.new(config)
5259 self ._keymaps = {}
5360 self ._key_capture_ns = nil
5461 self ._selected_index = 1
62+ self ._group_index = 1
5563 self ._active = false
5664
5765 return self
@@ -72,6 +80,24 @@ function Dialog:set_selection(index)
7280 end
7381end
7482
83+ --- @return integer
84+ function Dialog :get_group_selection ()
85+ return self ._group_index
86+ end
87+
88+ --- @param index integer
89+ function Dialog :set_group_selection (index )
90+ local group_count = self ._config .get_group_count and self ._config .get_group_count () or 0
91+ if group_count == 0 then
92+ self ._group_index = 1
93+ return
94+ end
95+
96+ if index >= 1 and index <= group_count then
97+ self ._group_index = index
98+ end
99+ end
100+
75101--- Navigate selection by delta (positive for down, negative for up)
76102--- @param delta integer Amount to move selection
77103function Dialog :navigate (delta )
@@ -98,6 +124,28 @@ function Dialog:navigate(delta)
98124 end
99125end
100126
127+ --- @param delta integer
128+ function Dialog :navigate_group (delta )
129+ if not self ._active or not self ._config .check_focused () or not self ._config .on_navigate_group then
130+ return
131+ end
132+
133+ local group_count = self ._config .get_group_count and self ._config .get_group_count () or 0
134+ if group_count <= 1 then
135+ return
136+ end
137+
138+ self ._group_index = self ._group_index + delta
139+
140+ if self ._group_index < 1 then
141+ self ._group_index = group_count
142+ elseif self ._group_index > group_count then
143+ self ._group_index = 1
144+ end
145+
146+ self ._config .on_navigate_group (self ._group_index )
147+ end
148+
101149--- Select the current option
102150function Dialog :select ()
103151 if not self ._active or not self ._config .check_focused () then
@@ -178,31 +226,30 @@ function Dialog:format_legend(output, options)
178226 end
179227
180228 if ui .is_opencode_focused () then
181- local legend_parts = {}
182229 local keymaps = self ._config .keymaps
183230 if not keymaps then
184231 return
185232 end
186233
187234 if keymaps .up and # keymaps .up > 0 and keymaps .down and # keymaps .down > 0 then
188- table.insert (legend_parts , string.format (' Navigate: `%s`/`%s` or `↑`/`↓`' , keymaps .down [1 ], keymaps .up [1 ]))
235+ local line = output :add_line (' Move: `j/k` or `↑/↓`' )
236+ end
237+
238+ if keymaps .left and # keymaps .left > 0 and keymaps .right and # keymaps .right > 0 then
239+ local line = output :add_line (' Question: `h/l` or `<-/->`' )
189240 end
190241
191242 if keymaps .select and keymaps .select ~= ' ' then
192- local select_text = string.format ( ' Select: `%s` ' , keymaps . select )
243+ local select_text = ' Select: `<CR>` '
193244 if keymaps .number_shortcuts and option_count > 0 then
194245 local max_shortcut = math.min (option_count , 9 )
195246 select_text = select_text .. string.format (' or `1-%d`' , max_shortcut )
196247 end
197- table.insert ( legend_parts , select_text )
248+ local line = output : add_line ( select_text )
198249 end
199250
200251 if keymaps .dismiss and keymaps .dismiss ~= ' ' then
201- table.insert (legend_parts , string.format (' Dismiss: `%s`' , keymaps .dismiss ))
202- end
203-
204- if # legend_parts > 0 then
205- output :add_line (table.concat (legend_parts , ' ' ))
252+ local line = output :add_line (' Close: `<Esc>`' )
206253 end
207254 else
208255 local message = options .unfocused_message or ' Focus Opencode window to interact'
@@ -353,6 +400,42 @@ function Dialog:_setup_keymaps()
353400 end
354401 end
355402
403+ if keymaps .left then
404+ for _ , key in ipairs (keymaps .left ) do
405+ if key and key ~= ' ' then
406+ vim .keymap .set (
407+ ' n' ,
408+ key ,
409+ function ()
410+ self :navigate_group (- 1 )
411+ end ,
412+ vim .tbl_extend (' force' , keymap_opts , {
413+ desc = ' Dialog: navigate left' ,
414+ })
415+ )
416+ table.insert (self ._keymaps , key )
417+ end
418+ end
419+ end
420+
421+ if keymaps .right then
422+ for _ , key in ipairs (keymaps .right ) do
423+ if key and key ~= ' ' then
424+ vim .keymap .set (
425+ ' n' ,
426+ key ,
427+ function ()
428+ self :navigate_group (1 )
429+ end ,
430+ vim .tbl_extend (' force' , keymap_opts , {
431+ desc = ' Dialog: navigate right' ,
432+ })
433+ )
434+ table.insert (self ._keymaps , key )
435+ end
436+ end
437+ end
438+
356439 if keymaps .select and keymaps .select ~= ' ' then
357440 vim .keymap .set (
358441 ' n' ,
0 commit comments