arguments.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package dialog
  2. import (
  3. "github.com/charmbracelet/bubbles/key"
  4. "github.com/charmbracelet/bubbles/textinput"
  5. tea "github.com/charmbracelet/bubbletea"
  6. "github.com/charmbracelet/lipgloss"
  7. "github.com/sst/opencode/internal/tui/styles"
  8. "github.com/sst/opencode/internal/tui/theme"
  9. "github.com/sst/opencode/internal/tui/util"
  10. )
  11. // ArgumentsDialogCmp is a component that asks the user for command arguments.
  12. type ArgumentsDialogCmp struct {
  13. width, height int
  14. textInput textinput.Model
  15. keys argumentsDialogKeyMap
  16. commandID string
  17. content string
  18. }
  19. // NewArgumentsDialogCmp creates a new ArgumentsDialogCmp.
  20. func NewArgumentsDialogCmp(commandID, content string) ArgumentsDialogCmp {
  21. t := theme.CurrentTheme()
  22. ti := textinput.New()
  23. ti.Placeholder = "Enter arguments..."
  24. ti.Focus()
  25. ti.Width = 40
  26. ti.Prompt = ""
  27. ti.PlaceholderStyle = ti.PlaceholderStyle.Background(t.Background())
  28. ti.PromptStyle = ti.PromptStyle.Background(t.Background())
  29. ti.TextStyle = ti.TextStyle.Background(t.Background())
  30. return ArgumentsDialogCmp{
  31. textInput: ti,
  32. keys: argumentsDialogKeyMap{},
  33. commandID: commandID,
  34. content: content,
  35. }
  36. }
  37. type argumentsDialogKeyMap struct {
  38. Enter key.Binding
  39. Escape key.Binding
  40. }
  41. // ShortHelp implements key.Map.
  42. func (k argumentsDialogKeyMap) ShortHelp() []key.Binding {
  43. return []key.Binding{
  44. key.NewBinding(
  45. key.WithKeys("enter"),
  46. key.WithHelp("enter", "confirm"),
  47. ),
  48. key.NewBinding(
  49. key.WithKeys("esc"),
  50. key.WithHelp("esc", "cancel"),
  51. ),
  52. }
  53. }
  54. // FullHelp implements key.Map.
  55. func (k argumentsDialogKeyMap) FullHelp() [][]key.Binding {
  56. return [][]key.Binding{k.ShortHelp()}
  57. }
  58. // Init implements tea.Model.
  59. func (m ArgumentsDialogCmp) Init() tea.Cmd {
  60. return tea.Batch(
  61. textinput.Blink,
  62. m.textInput.Focus(),
  63. )
  64. }
  65. // Update implements tea.Model.
  66. func (m ArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  67. var cmd tea.Cmd
  68. var cmds []tea.Cmd
  69. switch msg := msg.(type) {
  70. case tea.KeyMsg:
  71. switch {
  72. case key.Matches(msg, key.NewBinding(key.WithKeys("esc"))):
  73. return m, util.CmdHandler(CloseArgumentsDialogMsg{})
  74. case key.Matches(msg, key.NewBinding(key.WithKeys("enter"))):
  75. return m, util.CmdHandler(CloseArgumentsDialogMsg{
  76. Submit: true,
  77. CommandID: m.commandID,
  78. Content: m.content,
  79. Arguments: m.textInput.Value(),
  80. })
  81. }
  82. case tea.WindowSizeMsg:
  83. m.width = msg.Width
  84. m.height = msg.Height
  85. }
  86. m.textInput, cmd = m.textInput.Update(msg)
  87. cmds = append(cmds, cmd)
  88. return m, tea.Batch(cmds...)
  89. }
  90. // View implements tea.Model.
  91. func (m ArgumentsDialogCmp) View() string {
  92. t := theme.CurrentTheme()
  93. baseStyle := styles.BaseStyle()
  94. // Calculate width needed for content
  95. maxWidth := 60 // Width for explanation text
  96. title := baseStyle.
  97. Foreground(t.Primary()).
  98. Bold(true).
  99. Width(maxWidth).
  100. Padding(0, 1).
  101. Render("Command Arguments")
  102. explanation := baseStyle.
  103. Foreground(t.Text()).
  104. Width(maxWidth).
  105. Padding(0, 1).
  106. Render("This command requires arguments. Please enter the text to replace $ARGUMENTS with:")
  107. inputField := baseStyle.
  108. Foreground(t.Text()).
  109. Width(maxWidth).
  110. Padding(1, 1).
  111. Render(m.textInput.View())
  112. maxWidth = min(maxWidth, m.width-10)
  113. content := lipgloss.JoinVertical(
  114. lipgloss.Left,
  115. title,
  116. explanation,
  117. inputField,
  118. )
  119. return baseStyle.Padding(1, 2).
  120. Border(lipgloss.RoundedBorder()).
  121. BorderBackground(t.Background()).
  122. BorderForeground(t.TextMuted()).
  123. Background(t.Background()).
  124. Width(lipgloss.Width(content) + 4).
  125. Render(content)
  126. }
  127. // SetSize sets the size of the component.
  128. func (m *ArgumentsDialogCmp) SetSize(width, height int) {
  129. m.width = width
  130. m.height = height
  131. }
  132. // Bindings implements layout.Bindings.
  133. func (m ArgumentsDialogCmp) Bindings() []key.Binding {
  134. return m.keys.ShortHelp()
  135. }
  136. // CloseArgumentsDialogMsg is a message that is sent when the arguments dialog is closed.
  137. type CloseArgumentsDialogMsg struct {
  138. Submit bool
  139. CommandID string
  140. Content string
  141. Arguments string
  142. }
  143. // ShowArgumentsDialogMsg is a message that is sent to show the arguments dialog.
  144. type ShowArgumentsDialogMsg struct {
  145. CommandID string
  146. Content string
  147. }