11package dialog
22
33import (
4+ "fmt"
45 "github.com/charmbracelet/bubbles/key"
56 "github.com/charmbracelet/bubbles/textinput"
67 tea "github.com/charmbracelet/bubbletea"
@@ -11,35 +12,6 @@ import (
1112 "github.com/opencode-ai/opencode/internal/tui/util"
1213)
1314
14- // ArgumentsDialogCmp is a component that asks the user for command arguments.
15- type ArgumentsDialogCmp struct {
16- width , height int
17- textInput textinput.Model
18- keys argumentsDialogKeyMap
19- commandID string
20- content string
21- }
22-
23- // NewArgumentsDialogCmp creates a new ArgumentsDialogCmp.
24- func NewArgumentsDialogCmp (commandID , content string ) ArgumentsDialogCmp {
25- t := theme .CurrentTheme ()
26- ti := textinput .New ()
27- ti .Placeholder = "Enter arguments..."
28- ti .Focus ()
29- ti .Width = 40
30- ti .Prompt = ""
31- ti .PlaceholderStyle = ti .PlaceholderStyle .Background (t .Background ())
32- ti .PromptStyle = ti .PromptStyle .Background (t .Background ())
33- ti .TextStyle = ti .TextStyle .Background (t .Background ())
34-
35- return ArgumentsDialogCmp {
36- textInput : ti ,
37- keys : argumentsDialogKeyMap {},
38- commandID : commandID ,
39- content : content ,
40- }
41- }
42-
4315type argumentsDialogKeyMap struct {
4416 Enter key.Binding
4517 Escape key.Binding
@@ -64,77 +36,204 @@ func (k argumentsDialogKeyMap) FullHelp() [][]key.Binding {
6436 return [][]key.Binding {k .ShortHelp ()}
6537}
6638
39+ // ShowMultiArgumentsDialogMsg is a message that is sent to show the multi-arguments dialog.
40+ type ShowMultiArgumentsDialogMsg struct {
41+ CommandID string
42+ Content string
43+ ArgNames []string
44+ }
45+
46+ // CloseMultiArgumentsDialogMsg is a message that is sent when the multi-arguments dialog is closed.
47+ type CloseMultiArgumentsDialogMsg struct {
48+ Submit bool
49+ CommandID string
50+ Content string
51+ Args map [string ]string
52+ }
53+
54+ // MultiArgumentsDialogCmp is a component that asks the user for multiple command arguments.
55+ type MultiArgumentsDialogCmp struct {
56+ width , height int
57+ inputs []textinput.Model
58+ focusIndex int
59+ keys argumentsDialogKeyMap
60+ commandID string
61+ content string
62+ argNames []string
63+ }
64+
65+ // NewMultiArgumentsDialogCmp creates a new MultiArgumentsDialogCmp.
66+ func NewMultiArgumentsDialogCmp (commandID , content string , argNames []string ) MultiArgumentsDialogCmp {
67+ t := theme .CurrentTheme ()
68+ inputs := make ([]textinput.Model , len (argNames ))
69+
70+ for i , name := range argNames {
71+ ti := textinput .New ()
72+ ti .Placeholder = fmt .Sprintf ("Enter value for %s..." , name )
73+ ti .Width = 40
74+ ti .Prompt = ""
75+ ti .PlaceholderStyle = ti .PlaceholderStyle .Background (t .Background ())
76+ ti .PromptStyle = ti .PromptStyle .Background (t .Background ())
77+ ti .TextStyle = ti .TextStyle .Background (t .Background ())
78+
79+ // Only focus the first input initially
80+ if i == 0 {
81+ ti .Focus ()
82+ ti .PromptStyle = ti .PromptStyle .Foreground (t .Primary ())
83+ ti .TextStyle = ti .TextStyle .Foreground (t .Primary ())
84+ } else {
85+ ti .Blur ()
86+ }
87+
88+ inputs [i ] = ti
89+ }
90+
91+ return MultiArgumentsDialogCmp {
92+ inputs : inputs ,
93+ keys : argumentsDialogKeyMap {},
94+ commandID : commandID ,
95+ content : content ,
96+ argNames : argNames ,
97+ focusIndex : 0 ,
98+ }
99+ }
100+
67101// Init implements tea.Model.
68- func (m ArgumentsDialogCmp ) Init () tea.Cmd {
69- return tea .Batch (
70- textinput .Blink ,
71- m .textInput .Focus (),
72- )
102+ func (m MultiArgumentsDialogCmp ) Init () tea.Cmd {
103+ // Make sure only the first input is focused
104+ for i := range m .inputs {
105+ if i == 0 {
106+ m .inputs [i ].Focus ()
107+ } else {
108+ m .inputs [i ].Blur ()
109+ }
110+ }
111+
112+ return textinput .Blink
73113}
74114
75115// Update implements tea.Model.
76- func (m ArgumentsDialogCmp ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
77- var cmd tea.Cmd
116+ func (m MultiArgumentsDialogCmp ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
78117 var cmds []tea.Cmd
118+ t := theme .CurrentTheme ()
79119
80120 switch msg := msg .(type ) {
81121 case tea.KeyMsg :
82122 switch {
83123 case key .Matches (msg , key .NewBinding (key .WithKeys ("esc" ))):
84- return m , util .CmdHandler (CloseArgumentsDialogMsg {})
85- case key .Matches (msg , key .NewBinding (key .WithKeys ("enter" ))):
86- return m , util .CmdHandler (CloseArgumentsDialogMsg {
87- Submit : true ,
124+ return m , util .CmdHandler (CloseMultiArgumentsDialogMsg {
125+ Submit : false ,
88126 CommandID : m .commandID ,
89127 Content : m .content ,
90- Arguments : m . textInput . Value () ,
128+ Args : nil ,
91129 })
130+ case key .Matches (msg , key .NewBinding (key .WithKeys ("enter" ))):
131+ // If we're on the last input, submit the form
132+ if m .focusIndex == len (m .inputs )- 1 {
133+ args := make (map [string ]string )
134+ for i , name := range m .argNames {
135+ args [name ] = m .inputs [i ].Value ()
136+ }
137+ return m , util .CmdHandler (CloseMultiArgumentsDialogMsg {
138+ Submit : true ,
139+ CommandID : m .commandID ,
140+ Content : m .content ,
141+ Args : args ,
142+ })
143+ }
144+ // Otherwise, move to the next input
145+ m .inputs [m .focusIndex ].Blur ()
146+ m .focusIndex ++
147+ m .inputs [m .focusIndex ].Focus ()
148+ m .inputs [m .focusIndex ].PromptStyle = m .inputs [m .focusIndex ].PromptStyle .Foreground (t .Primary ())
149+ m .inputs [m .focusIndex ].TextStyle = m .inputs [m .focusIndex ].TextStyle .Foreground (t .Primary ())
150+ case key .Matches (msg , key .NewBinding (key .WithKeys ("tab" ))):
151+ // Move to the next input
152+ m .inputs [m .focusIndex ].Blur ()
153+ m .focusIndex = (m .focusIndex + 1 ) % len (m .inputs )
154+ m .inputs [m .focusIndex ].Focus ()
155+ m .inputs [m .focusIndex ].PromptStyle = m .inputs [m .focusIndex ].PromptStyle .Foreground (t .Primary ())
156+ m .inputs [m .focusIndex ].TextStyle = m .inputs [m .focusIndex ].TextStyle .Foreground (t .Primary ())
157+ case key .Matches (msg , key .NewBinding (key .WithKeys ("shift+tab" ))):
158+ // Move to the previous input
159+ m .inputs [m .focusIndex ].Blur ()
160+ m .focusIndex = (m .focusIndex - 1 + len (m .inputs )) % len (m .inputs )
161+ m .inputs [m .focusIndex ].Focus ()
162+ m .inputs [m .focusIndex ].PromptStyle = m .inputs [m .focusIndex ].PromptStyle .Foreground (t .Primary ())
163+ m .inputs [m .focusIndex ].TextStyle = m .inputs [m .focusIndex ].TextStyle .Foreground (t .Primary ())
92164 }
93165 case tea.WindowSizeMsg :
94166 m .width = msg .Width
95167 m .height = msg .Height
96168 }
97169
98- m .textInput , cmd = m .textInput .Update (msg )
170+ // Update the focused input
171+ var cmd tea.Cmd
172+ m .inputs [m .focusIndex ], cmd = m .inputs [m .focusIndex ].Update (msg )
99173 cmds = append (cmds , cmd )
100174
101175 return m , tea .Batch (cmds ... )
102176}
103177
104178// View implements tea.Model.
105- func (m ArgumentsDialogCmp ) View () string {
179+ func (m MultiArgumentsDialogCmp ) View () string {
106180 t := theme .CurrentTheme ()
107181 baseStyle := styles .BaseStyle ()
108182
109183 // Calculate width needed for content
110184 maxWidth := 60 // Width for explanation text
111185
112- title := baseStyle .
186+ title := lipgloss . NewStyle () .
113187 Foreground (t .Primary ()).
114188 Bold (true ).
115189 Width (maxWidth ).
116190 Padding (0 , 1 ).
191+ Background (t .Background ()).
117192 Render ("Command Arguments" )
118193
119- explanation := baseStyle .
194+ explanation := lipgloss . NewStyle () .
120195 Foreground (t .Text ()).
121196 Width (maxWidth ).
122197 Padding (0 , 1 ).
123- Render ("This command requires arguments. Please enter the text to replace $ARGUMENTS with:" )
198+ Background (t .Background ()).
199+ Render ("This command requires multiple arguments. Please enter values for each:" )
124200
125- inputField := baseStyle .
126- Foreground (t .Text ()).
127- Width (maxWidth ).
128- Padding (1 , 1 ).
129- Render (m .textInput .View ())
201+ // Create input fields for each argument
202+ inputFields := make ([]string , len (m .inputs ))
203+ for i , input := range m .inputs {
204+ // Highlight the label of the focused input
205+ labelStyle := lipgloss .NewStyle ().
206+ Width (maxWidth ).
207+ Padding (1 , 1 , 0 , 1 ).
208+ Background (t .Background ())
209+
210+ if i == m .focusIndex {
211+ labelStyle = labelStyle .Foreground (t .Primary ()).Bold (true )
212+ } else {
213+ labelStyle = labelStyle .Foreground (t .TextMuted ())
214+ }
215+
216+ label := labelStyle .Render (m .argNames [i ] + ":" )
217+
218+ field := lipgloss .NewStyle ().
219+ Foreground (t .Text ()).
220+ Width (maxWidth ).
221+ Padding (0 , 1 ).
222+ Background (t .Background ()).
223+ Render (input .View ())
224+
225+ inputFields [i ] = lipgloss .JoinVertical (lipgloss .Left , label , field )
226+ }
130227
131228 maxWidth = min (maxWidth , m .width - 10 )
132229
230+ // Join all elements vertically
231+ elements := []string {title , explanation }
232+ elements = append (elements , inputFields ... )
233+
133234 content := lipgloss .JoinVertical (
134235 lipgloss .Left ,
135- title ,
136- explanation ,
137- inputField ,
236+ elements ... ,
138237 )
139238
140239 return baseStyle .Padding (1 , 2 ).
@@ -147,27 +246,12 @@ func (m ArgumentsDialogCmp) View() string {
147246}
148247
149248// SetSize sets the size of the component.
150- func (m * ArgumentsDialogCmp ) SetSize (width , height int ) {
249+ func (m * MultiArgumentsDialogCmp ) SetSize (width , height int ) {
151250 m .width = width
152251 m .height = height
153252}
154253
155254// Bindings implements layout.Bindings.
156- func (m ArgumentsDialogCmp ) Bindings () []key.Binding {
255+ func (m MultiArgumentsDialogCmp ) Bindings () []key.Binding {
157256 return m .keys .ShortHelp ()
158- }
159-
160- // CloseArgumentsDialogMsg is a message that is sent when the arguments dialog is closed.
161- type CloseArgumentsDialogMsg struct {
162- Submit bool
163- CommandID string
164- Content string
165- Arguments string
166- }
167-
168- // ShowArgumentsDialogMsg is a message that is sent to show the arguments dialog.
169- type ShowArgumentsDialogMsg struct {
170- CommandID string
171- Content string
172- }
173-
257+ }
0 commit comments