6 "charm.land/bubbles/v2/help"
7 "charm.land/bubbles/v2/key"
8 "charm.land/bubbles/v2/textinput"
9 tea "charm.land/bubbletea/v2"
10 "charm.land/lipgloss/v2"
11 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
12 "github.com/charmbracelet/crush/internal/tui/styles"
13 "github.com/charmbracelet/crush/internal/tui/util"
17 argumentsDialogID dialogs.DialogID = "arguments"
20// ShowArgumentsDialogMsg is a message that is sent to show the arguments dialog.
21type ShowArgumentsDialogMsg struct {
25 OnSubmit func(args map[string]string) tea.Cmd
28// CloseArgumentsDialogMsg is a message that is sent when the arguments dialog is closed.
29type CloseArgumentsDialogMsg struct {
33 Args map[string]string
36// CommandArgumentsDialog represents the commands dialog.
37type CommandArgumentsDialog interface {
41type commandArgumentsDialogCmp struct {
45 inputs []textinput.Model
47 keys ArgumentsDialogKeyMap
56 onSubmit func(args map[string]string) tea.Cmd
60 Name, Title, Description string
64func NewCommandArgumentsDialog(
65 id, title, name, description string,
67 onSubmit func(args map[string]string) tea.Cmd,
68) CommandArgumentsDialog {
69 t := styles.CurrentTheme()
70 inputs := make([]textinput.Model, len(arguments))
72 for i, arg := range arguments {
74 ti.Placeholder = cmp.Or(arg.Description, "Enter value for "+arg.Title)
76 ti.SetVirtualCursor(false)
79 ti.SetStyles(t.S().TextInput)
80 // Only focus the first input initially
90 return &commandArgumentsDialogCmp{
92 keys: DefaultArgumentsDialogKeyMap(),
96 description: description,
104// Init implements CommandArgumentsDialog.
105func (c *commandArgumentsDialogCmp) Init() tea.Cmd {
109// Update implements CommandArgumentsDialog.
110func (c *commandArgumentsDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
111 switch msg := msg.(type) {
112 case tea.WindowSizeMsg:
114 c.wHeight = msg.Height
115 c.width = min(90, c.wWidth)
116 c.height = min(15, c.wHeight)
117 for i := range c.inputs {
118 c.inputs[i].SetWidth(c.width - (paddingHorizontal * 2))
120 case tea.KeyPressMsg:
122 case key.Matches(msg, c.keys.Close):
123 return c, util.CmdHandler(dialogs.CloseDialogMsg{})
124 case key.Matches(msg, c.keys.Confirm):
125 if c.focused == len(c.inputs)-1 {
126 args := make(map[string]string)
127 for i, arg := range c.arguments {
128 value := c.inputs[i].Value()
129 args[arg.Name] = value
131 return c, tea.Sequence(
132 util.CmdHandler(dialogs.CloseDialogMsg{}),
136 // Otherwise, move to the next input
137 c.inputs[c.focused].Blur()
139 c.inputs[c.focused].Focus()
140 case key.Matches(msg, c.keys.Next):
141 // Move to the next input
142 c.inputs[c.focused].Blur()
143 c.focused = (c.focused + 1) % len(c.inputs)
144 c.inputs[c.focused].Focus()
145 case key.Matches(msg, c.keys.Previous):
146 // Move to the previous input
147 c.inputs[c.focused].Blur()
148 c.focused = (c.focused - 1 + len(c.inputs)) % len(c.inputs)
149 c.inputs[c.focused].Focus()
150 case key.Matches(msg, c.keys.Close):
151 return c, util.CmdHandler(dialogs.CloseDialogMsg{})
154 c.inputs[c.focused], cmd = c.inputs[c.focused].Update(msg)
159 c.inputs[c.focused], cmd = c.inputs[c.focused].Update(msg)
165// View implements CommandArgumentsDialog.
166func (c *commandArgumentsDialogCmp) View() string {
167 t := styles.CurrentTheme()
168 baseStyle := t.S().Base
170 title := lipgloss.NewStyle().
171 Foreground(t.Primary).
174 Render(cmp.Or(c.title, c.name))
176 promptName := t.S().Text.
178 Render(c.description)
180 inputFields := make([]string, len(c.inputs))
181 for i, input := range c.inputs {
182 labelStyle := baseStyle.Padding(1, 1, 0, 1)
185 labelStyle = labelStyle.Foreground(t.FgBase).Bold(true)
187 labelStyle = labelStyle.Foreground(t.FgMuted)
190 arg := c.arguments[i]
191 argName := cmp.Or(arg.Title, arg.Name)
195 label := labelStyle.Render(argName + ":")
201 inputFields[i] = lipgloss.JoinVertical(lipgloss.Left, label, field)
204 elements := []string{title, promptName}
205 elements = append(elements, inputFields...)
207 c.help.ShowAll = false
208 helpText := baseStyle.Padding(0, 1).Render(c.help.View(c.keys))
209 elements = append(elements, "", helpText)
211 content := lipgloss.JoinVertical(lipgloss.Left, elements...)
213 return baseStyle.Padding(1, 1, 0, 1).
214 Border(lipgloss.RoundedBorder()).
215 BorderForeground(t.BorderFocus).
220func (c *commandArgumentsDialogCmp) Cursor() *tea.Cursor {
221 if len(c.inputs) == 0 {
224 cursor := c.inputs[c.focused].Cursor()
226 cursor = c.moveCursor(cursor)
234 paddingHorizontal = 3
237func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
238 row, col := c.Position()
239 offset := row + headerHeight + (1+c.focused)*itemHeight
241 cursor.X = cursor.X + col + paddingHorizontal
245func (c *commandArgumentsDialogCmp) Position() (int, int) {
246 row := (c.wHeight / 2) - (c.height / 2)
247 col := (c.wWidth / 2) - (c.width / 2)
251// ID implements CommandArgumentsDialog.
252func (c *commandArgumentsDialogCmp) ID() dialogs.DialogID {
253 return argumentsDialogID
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list