1
1
package dialog
2
2
3
3
import (
4
+ "fmt"
4
5
"github.com/charmbracelet/bubbles/key"
5
6
"github.com/charmbracelet/bubbles/textinput"
6
7
tea "github.com/charmbracelet/bubbletea"
@@ -11,35 +12,6 @@ import (
11
12
"github.com/opencode-ai/opencode/internal/tui/util"
12
13
)
13
14
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
-
43
15
type argumentsDialogKeyMap struct {
44
16
Enter key.Binding
45
17
Escape key.Binding
@@ -64,77 +36,204 @@ func (k argumentsDialogKeyMap) FullHelp() [][]key.Binding {
64
36
return [][]key.Binding {k .ShortHelp ()}
65
37
}
66
38
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
+
67
101
// 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
73
113
}
74
114
75
115
// 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 ) {
78
117
var cmds []tea.Cmd
118
+ t := theme .CurrentTheme ()
79
119
80
120
switch msg := msg .(type ) {
81
121
case tea.KeyMsg :
82
122
switch {
83
123
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 ,
88
126
CommandID : m .commandID ,
89
127
Content : m .content ,
90
- Arguments : m . textInput . Value () ,
128
+ Args : nil ,
91
129
})
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 ())
92
164
}
93
165
case tea.WindowSizeMsg :
94
166
m .width = msg .Width
95
167
m .height = msg .Height
96
168
}
97
169
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 )
99
173
cmds = append (cmds , cmd )
100
174
101
175
return m , tea .Batch (cmds ... )
102
176
}
103
177
104
178
// View implements tea.Model.
105
- func (m ArgumentsDialogCmp ) View () string {
179
+ func (m MultiArgumentsDialogCmp ) View () string {
106
180
t := theme .CurrentTheme ()
107
181
baseStyle := styles .BaseStyle ()
108
182
109
183
// Calculate width needed for content
110
184
maxWidth := 60 // Width for explanation text
111
185
112
- title := baseStyle .
186
+ title := lipgloss . NewStyle () .
113
187
Foreground (t .Primary ()).
114
188
Bold (true ).
115
189
Width (maxWidth ).
116
190
Padding (0 , 1 ).
191
+ Background (t .Background ()).
117
192
Render ("Command Arguments" )
118
193
119
- explanation := baseStyle .
194
+ explanation := lipgloss . NewStyle () .
120
195
Foreground (t .Text ()).
121
196
Width (maxWidth ).
122
197
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:" )
124
200
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
+ }
130
227
131
228
maxWidth = min (maxWidth , m .width - 10 )
132
229
230
+ // Join all elements vertically
231
+ elements := []string {title , explanation }
232
+ elements = append (elements , inputFields ... )
233
+
133
234
content := lipgloss .JoinVertical (
134
235
lipgloss .Left ,
135
- title ,
136
- explanation ,
137
- inputField ,
236
+ elements ... ,
138
237
)
139
238
140
239
return baseStyle .Padding (1 , 2 ).
@@ -147,27 +246,12 @@ func (m ArgumentsDialogCmp) View() string {
147
246
}
148
247
149
248
// SetSize sets the size of the component.
150
- func (m * ArgumentsDialogCmp ) SetSize (width , height int ) {
249
+ func (m * MultiArgumentsDialogCmp ) SetSize (width , height int ) {
151
250
m .width = width
152
251
m .height = height
153
252
}
154
253
155
254
// Bindings implements layout.Bindings.
156
- func (m ArgumentsDialogCmp ) Bindings () []key.Binding {
255
+ func (m MultiArgumentsDialogCmp ) Bindings () []key.Binding {
157
256
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