confirm.go

  1package screens
  2
  3import (
  4	"charm.land/bubbles/v2/key"
  5	tea "charm.land/bubbletea/v2"
  6
  7	"git.secluded.site/keld/internal/ui"
  8)
  9
 10// PreviewFunc returns the formatted command preview text. It is
 11// called asynchronously from Init via a tea.Cmd. The returned string
 12// should match the format of [restic.DryRun].
 13type PreviewFunc func() string
 14
 15// previewMsg carries the result of the async preview generation.
 16type previewMsg struct {
 17	text string
 18}
 19
 20// Confirm is a Screen adapter that shows the resolved restic command
 21// and waits for the user to confirm execution. Enter proceeds,
 22// Esc goes back.
 23//
 24// When showCommand is true, Init returns DoneCmd immediately without
 25// calling the preview function — the session completes and the caller
 26// prints the dry-run output through the normal --show-command path.
 27type Confirm struct {
 28	previewFn   PreviewFunc
 29	showCommand bool
 30	partial     bool
 31	preview     string
 32	selection   string
 33}
 34
 35// NewConfirm creates a confirmation screen. The previewFn is called
 36// to generate the command preview text. When showCommand is true,
 37// the screen auto-completes without user interaction.
 38func NewConfirm(previewFn PreviewFunc, showCommand bool) *Confirm {
 39	return &Confirm{
 40		previewFn:   previewFn,
 41		showCommand: showCommand,
 42	}
 43}
 44
 45// Init starts the preview generation. In --show-command mode, it
 46// skips the preview and returns DoneCmd immediately.
 47func (c *Confirm) Init() tea.Cmd {
 48	c.selection = ""
 49
 50	if c.showCommand {
 51		return ui.DoneCmd
 52	}
 53
 54	return func() tea.Msg {
 55		return previewMsg{text: c.previewFn()}
 56	}
 57}
 58
 59// Update handles messages. Esc navigates back, Enter confirms.
 60func (c *Confirm) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
 61	switch msg := msg.(type) {
 62	case previewMsg:
 63		c.preview = msg.text
 64		return c, nil
 65
 66	case tea.KeyPressMsg:
 67		if msg.Code == tea.KeyEscape {
 68			return c, ui.BackCmd
 69		}
 70		if msg.Code == tea.KeyEnter && c.preview != "" {
 71			c.selection = "confirmed"
 72			return c, ui.DoneCmd
 73		}
 74	}
 75
 76	return c, nil
 77}
 78
 79// View renders the command preview. When partial is set, a note
 80// is appended explaining the preview may be incomplete.
 81func (c *Confirm) View() string {
 82	if c.preview == "" {
 83		return ""
 84	}
 85	if !c.partial {
 86		return c.preview
 87	}
 88	return c.preview + "\n" + partialNote
 89}
 90
 91// partialNote is shown below the preview when the command has no
 92// dedicated TUI screens, meaning the session could not collect all
 93// inputs interactively.
 94const partialNote = "Note: this preview may be incomplete. " +
 95	"Additional prompts may follow after confirmation.\n" +
 96	"Use --show-command to see the final command after all inputs are collected."
 97
 98// Title returns the screen's display title.
 99func (c *Confirm) Title() string { return "Confirm execution" }
100
101// KeyBindings returns bindings for the help bar.
102func (c *Confirm) KeyBindings() []key.Binding {
103	return []key.Binding{
104		key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "execute")),
105	}
106}
107
108// SetPartial marks the preview as potentially incomplete because
109// the command has no dedicated TUI screens to collect all inputs.
110func (c *Confirm) SetPartial(partial bool) { c.partial = partial }
111
112// Selection returns "confirmed" after the user presses Enter, or ""
113// if the screen has not been confirmed yet.
114func (c *Confirm) Selection() string { return c.selection }