Files
bifrost/cli/internal/ui/tui/confirm.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

135 lines
3.1 KiB
Go

package tui
import (
"fmt"
"os"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// confirmModel holds information about current model selection
type confirmModel struct {
header string
prompt string
command string
idx int
quit bool
yes bool
clearFirst bool
}
// runConfirm runs a confirm dialog with the given model and returns the user's
// choice.
func runConfirm(m confirmModel) (bool, error) {
p := tea.NewProgram(
m,
tea.WithInput(os.Stdin),
tea.WithOutput(os.Stdout),
tea.WithInputTTY(),
)
out, err := p.Run()
if err != nil {
return false, err
}
fm, ok := out.(confirmModel)
if !ok {
return false, fmt.Errorf("unexpected model type from tui")
}
if fm.quit {
return false, nil
}
return fm.yes, nil
}
// RunConfirmInstall displays a yes/no confirmation dialog asking the user
// whether to install a missing harness. Returns true if the user confirms.
func RunConfirmInstall(header, harnessLabel, command string) (bool, error) {
return runConfirm(confirmModel{
header: header,
prompt: fmt.Sprintf("%s is not installed. Install now?", harnessLabel),
command: command,
})
}
// RunConfirmSettings displays a yes/no confirmation dialog asking the user
// whether to update the harness's native settings file. Returns true if the
// user confirms.
func RunConfirmSettings(harnessLabel, settingsPath string) (bool, error) {
return runConfirm(confirmModel{
prompt: fmt.Sprintf("Update %s settings? (%s)", harnessLabel, settingsPath),
clearFirst: true,
})
}
// Init implements tea.Model.
func (m confirmModel) Init() tea.Cmd {
if m.clearFirst {
return tea.ClearScreen
}
return nil
}
// Update implements tea.Model. Handles y/n, arrow keys, and enter for confirmation.
func (m confirmModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
s := msg.String()
switch s {
case "ctrl+c", "q", "esc":
m.quit = true
return m, tea.Quit
case "left", "h", "right", "l", "tab":
if m.idx == 0 {
m.idx = 1
} else {
m.idx = 0
}
return m, nil
case "y":
m.yes = true
return m, tea.Quit
case "n":
m.yes = false
return m, tea.Quit
case "enter":
m.yes = m.idx == 0
return m, tea.Quit
}
}
return m, nil
}
// View implements tea.Model. Renders the confirmation prompt with Yes/No buttons.
func (m confirmModel) View() string {
selected := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("42"))
normal := lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
yes := normal.Render("[ Yes ]")
no := normal.Render("[ No ]")
if m.idx == 0 {
yes = selected.Render("[ Yes ]")
} else {
no = selected.Render("[ No ]")
}
var b strings.Builder
if m.header != "" {
b.WriteString(m.header)
b.WriteString("\n")
}
b.WriteString(m.prompt)
b.WriteString("\n")
if m.command != "" {
b.WriteString("command: ")
b.WriteString(m.command)
b.WriteString("\n")
}
b.WriteString("\n")
b.WriteString(yes + " " + no)
b.WriteString("\n")
b.WriteString("enter: confirm, y/n quick choice, q: cancel")
return b.String()
}