@@ -1,11 +1,18 @@
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