11package commands
22
33import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/charmbracelet/bubbles/v2/help"
8+ "github.com/charmbracelet/bubbles/v2/key"
9+ "github.com/charmbracelet/bubbles/v2/textinput"
410 tea "github.com/charmbracelet/bubbletea/v2"
511 "github.com/charmbracelet/lipgloss/v2"
612 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
713 "github.com/opencode-ai/opencode/internal/tui/styles"
814 "github.com/opencode-ai/opencode/internal/tui/theme"
15+ "github.com/opencode-ai/opencode/internal/tui/util"
916)
1017
1118const (
@@ -36,10 +43,55 @@ type commandArgumentsDialogCmp struct {
3643 width int
3744 wWidth int // Width of the terminal window
3845 wHeight int // Height of the terminal window
46+
47+ inputs []textinput.Model
48+ focusIndex int
49+ keys ArgumentsDialogKeyMap
50+ commandID string
51+ content string
52+ argNames []string
53+ help help.Model
3954}
4055
41- func NewCommandArgumentsDialog () CommandArgumentsDialog {
42- return & commandArgumentsDialogCmp {}
56+ func NewCommandArgumentsDialog (commandID , content string , argNames []string ) CommandArgumentsDialog {
57+ t := theme .CurrentTheme ()
58+ inputs := make ([]textinput.Model , len (argNames ))
59+
60+ for i , name := range argNames {
61+ ti := textinput .New ()
62+ ti .Placeholder = fmt .Sprintf ("Enter value for %s..." , name )
63+ ti .SetWidth (40 )
64+ ti .SetVirtualCursor (false )
65+ ti .Prompt = ""
66+ ds := ti .Styles ()
67+
68+ ds .Blurred .Placeholder = ds .Blurred .Placeholder .Background (t .Background ()).Foreground (t .TextMuted ())
69+ ds .Blurred .Prompt = ds .Blurred .Prompt .Background (t .Background ()).Foreground (t .TextMuted ())
70+ ds .Blurred .Text = ds .Blurred .Text .Background (t .Background ()).Foreground (t .TextMuted ())
71+ ds .Focused .Placeholder = ds .Blurred .Placeholder .Background (t .Background ()).Foreground (t .TextMuted ())
72+ ds .Focused .Prompt = ds .Blurred .Prompt .Background (t .Background ()).Foreground (t .Text ())
73+ ds .Focused .Text = ds .Blurred .Text .Background (t .Background ()).Foreground (t .Text ())
74+ ti .SetStyles (ds )
75+ // Only focus the first input initially
76+ if i == 0 {
77+ ti .Focus ()
78+ } else {
79+ ti .Blur ()
80+ }
81+
82+ inputs [i ] = ti
83+ }
84+
85+ return & commandArgumentsDialogCmp {
86+ inputs : inputs ,
87+ keys : DefaultArgumentsDialogKeyMap (),
88+ commandID : commandID ,
89+ content : content ,
90+ argNames : argNames ,
91+ focusIndex : 0 ,
92+ width : 60 ,
93+ help : help .New (),
94+ }
4395}
4496
4597// Init implements CommandArgumentsDialog.
@@ -48,20 +100,130 @@ func (c *commandArgumentsDialogCmp) Init() tea.Cmd {
48100}
49101
50102// Update implements CommandArgumentsDialog.
51- func (c * commandArgumentsDialogCmp ) Update (tea.Msg ) (tea.Model , tea.Cmd ) {
103+ func (c * commandArgumentsDialogCmp ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
104+ switch msg := msg .(type ) {
105+ case tea.WindowSizeMsg :
106+ c .wWidth = msg .Width
107+ c .wHeight = msg .Height
108+ case tea.KeyPressMsg :
109+ switch {
110+ case key .Matches (msg , c .keys .Confirm ):
111+ if c .focusIndex == len (c .inputs )- 1 {
112+ content := c .content
113+ for i , name := range c .argNames {
114+ value := c .inputs [i ].Value ()
115+ placeholder := "$" + name
116+ content = strings .ReplaceAll (content , placeholder , value )
117+ }
118+ return c , tea .Sequence (
119+ util .CmdHandler (dialogs.CloseDialogMsg {}),
120+ util .CmdHandler (CommandRunCustomMsg {
121+ Content : content ,
122+ }),
123+ )
124+ }
125+ // Otherwise, move to the next input
126+ c .inputs [c .focusIndex ].Blur ()
127+ c .focusIndex ++
128+ c .inputs [c .focusIndex ].Focus ()
129+ case key .Matches (msg , c .keys .Next ):
130+ // Move to the next input
131+ c .inputs [c .focusIndex ].Blur ()
132+ c .focusIndex = (c .focusIndex + 1 ) % len (c .inputs )
133+ c .inputs [c .focusIndex ].Focus ()
134+ case key .Matches (msg , c .keys .Previous ):
135+ // Move to the previous input
136+ c .inputs [c .focusIndex ].Blur ()
137+ c .focusIndex = (c .focusIndex - 1 + len (c .inputs )) % len (c .inputs )
138+ c .inputs [c .focusIndex ].Focus ()
139+
140+ default :
141+ var cmd tea.Cmd
142+ c .inputs [c .focusIndex ], cmd = c .inputs [c .focusIndex ].Update (msg )
143+ return c , cmd
144+ }
145+ }
52146 return c , nil
53147}
54148
55149// View implements CommandArgumentsDialog.
56150func (c * commandArgumentsDialogCmp ) View () tea.View {
57- return tea .NewView ("" )
151+ t := theme .CurrentTheme ()
152+ baseStyle := styles .BaseStyle ()
153+
154+ title := lipgloss .NewStyle ().
155+ Foreground (t .Primary ()).
156+ Bold (true ).
157+ Padding (0 , 1 ).
158+ Background (t .Background ()).
159+ Render ("Command Arguments" )
160+
161+ explanation := lipgloss .NewStyle ().
162+ Foreground (t .Text ()).
163+ Padding (0 , 1 ).
164+ Background (t .Background ()).
165+ Render ("This command requires arguments." )
166+
167+ // Create input fields for each argument
168+ inputFields := make ([]string , len (c .inputs ))
169+ for i , input := range c .inputs {
170+ // Highlight the label of the focused input
171+ labelStyle := lipgloss .NewStyle ().
172+ Padding (1 , 1 , 0 , 1 ).
173+ Background (t .Background ())
174+
175+ if i == c .focusIndex {
176+ labelStyle = labelStyle .Foreground (t .Text ()).Bold (true )
177+ } else {
178+ labelStyle = labelStyle .Foreground (t .TextMuted ())
179+ }
180+
181+ label := labelStyle .Render (c .argNames [i ] + ":" )
182+
183+ field := lipgloss .NewStyle ().
184+ Foreground (t .Text ()).
185+ Padding (0 , 1 ).
186+ Background (t .Background ()).
187+ Render (input .View ())
188+
189+ inputFields [i ] = lipgloss .JoinVertical (lipgloss .Left , label , field )
190+ }
191+
192+ // Join all elements vertically
193+ elements := []string {title , explanation }
194+ elements = append (elements , inputFields ... )
195+
196+ c .help .ShowAll = false
197+ helpText := baseStyle .Padding (0 , 1 ).Render (c .help .View (c .keys ))
198+ elements = append (elements , "" , helpText )
199+
200+ content := lipgloss .JoinVertical (
201+ lipgloss .Left ,
202+ elements ... ,
203+ )
204+
205+ view := tea .NewView (
206+ baseStyle .Padding (1 , 1 , 0 , 1 ).
207+ Border (lipgloss .RoundedBorder ()).
208+ BorderBackground (t .Background ()).
209+ BorderForeground (t .TextMuted ()).
210+ Background (t .Background ()).
211+ Width (c .width ).
212+ Render (content ),
213+ )
214+ cursor := c .inputs [c .focusIndex ].Cursor ()
215+ if cursor != nil {
216+ cursor = c .moveCursor (cursor )
217+ }
218+ view .SetCursor (cursor )
219+ return view
58220}
59221
60222func (c * commandArgumentsDialogCmp ) moveCursor (cursor * tea.Cursor ) * tea.Cursor {
61- offset := 10 + 1
223+ offset := 13 + ( 1 + c . focusIndex ) * 3
62224 cursor .Y += offset
63225 _ , col := c .Position ()
64- cursor .X = cursor .X + col + 2
226+ cursor .X = cursor .X + col + 3
65227 return cursor
66228}
67229
0 commit comments