@@ -1,11 +1,18 @@
1package commands
1package commands
7+ "github.com/charmbracelet/bubbles/v2/help"
8+ "github.com/charmbracelet/bubbles/v2/key"
9+ "github.com/charmbracelet/bubbles/v2/textinput"
4 tea "github.com/charmbracelet/bubbletea/v2"
10 tea "github.com/charmbracelet/bubbletea/v2"
5 "github.com/charmbracelet/lipgloss/v2"
11 "github.com/charmbracelet/lipgloss/v2"
6 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
12 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
7 "github.com/opencode-ai/opencode/internal/tui/styles"
13 "github.com/opencode-ai/opencode/internal/tui/styles"
8 "github.com/opencode-ai/opencode/internal/tui/theme"
14 "github.com/opencode-ai/opencode/internal/tui/theme"
15+ "github.com/opencode-ai/opencode/internal/tui/util"
@@ -36,10 +43,55 @@ type commandArgumentsDialogCmp struct {
37 wWidth int // Width of the terminal window
44 wWidth int // Width of the terminal window
38 wHeight int // Height of the terminal window
45 wHeight int // Height of the terminal window
47+ inputs []textinput.Model
49+ keys ArgumentsDialogKeyMap
41−func NewCommandArgumentsDialog() CommandArgumentsDialog {
56+func NewCommandArgumentsDialog(commandID, content string, argNames []string) CommandArgumentsDialog {
57+ t := theme.CurrentTheme()
58+ inputs := make([]textinput.Model, len(argNames))
60+ for i, name := range argNames {
61+ ti := textinput.New()
62+ ti.Placeholder = fmt.Sprintf("Enter value for %s...", name)
64+ ti.SetVirtualCursor(false)
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())
75+ // Only focus the first input initially
42− return &commandArgumentsDialogCmp{}
85+ return &commandArgumentsDialogCmp{
87+ keys: DefaultArgumentsDialogKeyMap(),
88+ commandID: commandID,
45// Init implements CommandArgumentsDialog.
97// Init implements CommandArgumentsDialog.
@@ -48,20 +100,130 @@ func (c *commandArgumentsDialogCmp) Init() tea.Cmd {
50// Update implements CommandArgumentsDialog.
102// 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:
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)
118+ return c, tea.Sequence(
119+ util.CmdHandler(dialogs.CloseDialogMsg{}),
120+ util.CmdHandler(CommandRunCustomMsg{
125+ // Otherwise, move to the next input
126+ c.inputs[c.focusIndex].Blur()
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()
142+ c.inputs[c.focusIndex], cmd = c.inputs[c.focusIndex].Update(msg)
52 return c, nil
146 return c, nil
55// View implements CommandArgumentsDialog.
149// View implements CommandArgumentsDialog.
56func (c *commandArgumentsDialogCmp) View() tea.View {
150func (c *commandArgumentsDialogCmp) View() tea.View {
151+ t := theme.CurrentTheme()
152+ baseStyle := styles.BaseStyle()
154+ title := lipgloss.NewStyle().
155+ Foreground(t.Primary()).
158+ Background(t.Background()).
159+ Render("Command Arguments")
161+ explanation := lipgloss.NewStyle().
162+ Foreground(t.Text()).
164+ Background(t.Background()).
165+ Render("This command requires arguments.")
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())
175+ if i == c.focusIndex {
176+ labelStyle = labelStyle.Foreground(t.Text()).Bold(true)
178+ labelStyle = labelStyle.Foreground(t.TextMuted())
181+ label := labelStyle.Render(c.argNames[i] + ":")
183+ field := lipgloss.NewStyle().
184+ Foreground(t.Text()).
186+ Background(t.Background()).
187+ Render(input.View())
189+ inputFields[i] = lipgloss.JoinVertical(lipgloss.Left, label, field)
192+ // Join all elements vertically
193+ elements := []string{title, explanation}
194+ elements = append(elements, inputFields...)
196+ c.help.ShowAll = false
197+ helpText := baseStyle.Padding(0, 1).Render(c.help.View(c.keys))
198+ elements = append(elements, "", helpText)
200+ content := lipgloss.JoinVertical(
57− return tea.NewView("")
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()).
214+ cursor := c.inputs[c.focusIndex].Cursor()
216+ cursor = c.moveCursor(cursor)
218+ view.SetCursor(cursor)
60func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
222func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
61− offset := 10 + 1
223+ offset := 13 + (1+c.focusIndex)*3
62 cursor.Y += offset
224 cursor.Y += offset
63 _, col := c.Position()
225 _, col := c.Position()
64− cursor.X = cursor.X + col + 2
226+ cursor.X = cursor.X + col + 3
65 return cursor
227 return cursor