overwrite.go

  1package screens
  2
  3import (
  4	"charm.land/bubbles/v2/key"
  5	tea "charm.land/bubbletea/v2"
  6
  7	"charm.land/huh/v2"
  8
  9	"git.secluded.site/keld/internal/theme"
 10	"git.secluded.site/keld/internal/ui"
 11)
 12
 13// overwriteOptions defines the choices for restic's --overwrite flag
 14// in the order they are presented to the user.
 15var overwriteOptions = []huh.Option[string]{
 16	huh.NewOption("if-changed  (recommended — only restore what differs)", "if-changed"),
 17	huh.NewOption("if-newer    (only overwrite older files)", "if-newer"),
 18	huh.NewOption("never       (skip existing files entirely)", "never"),
 19	huh.NewOption("always      (restic default — overwrite everything)", "always"),
 20}
 21
 22// Overwrite is a Screen adapter that wraps a huh Select for choosing
 23// the restore --overwrite behaviour. It follows the same pattern as
 24// [Preset]: builds the huh form on Init, intercepts Esc for back
 25// navigation, and returns DoneCmd on completion.
 26type Overwrite struct {
 27	styles    *theme.Styles
 28	form      *huh.Form
 29	selected  string
 30	selection string
 31}
 32
 33// NewOverwrite creates an overwrite selector screen. The styles
 34// pointer should come from the session so theme updates propagate.
 35func NewOverwrite(styles *theme.Styles) *Overwrite {
 36	return &Overwrite{
 37		styles: styles,
 38	}
 39}
 40
 41// buildForm constructs the huh form and select field.
 42func (o *Overwrite) buildForm() {
 43	o.selection = ""
 44
 45	sel := huh.NewSelect[string]().
 46		Options(overwriteOptions...).
 47		Value(&o.selected)
 48
 49	o.form = huh.NewForm(
 50		huh.NewGroup(sel),
 51	).WithTheme(o.styles.Huh).WithShowHelp(false)
 52}
 53
 54// Init initialises the embedded form. On first call or after
 55// completion, the form is (re)built.
 56func (o *Overwrite) Init() tea.Cmd {
 57	if o.form == nil || o.form.State != huh.StateNormal {
 58		o.buildForm()
 59	}
 60	return o.form.Init()
 61}
 62
 63// Update handles messages. Esc is intercepted for back navigation.
 64func (o *Overwrite) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
 65	if o.form == nil {
 66		return o, nil
 67	}
 68
 69	switch msg.(type) {
 70	case tea.BackgroundColorMsg:
 71		o.buildForm()
 72		return o, o.form.Init()
 73	}
 74
 75	if kp, ok := msg.(tea.KeyPressMsg); ok {
 76		if kp.Code == tea.KeyEscape {
 77			return o, ui.BackCmd
 78		}
 79	}
 80
 81	model, cmd := o.form.Update(msg)
 82	if f, ok := model.(*huh.Form); ok {
 83		o.form = f
 84	}
 85
 86	if o.form.State == huh.StateCompleted {
 87		o.selection = o.selected
 88		return o, ui.DoneCmd
 89	}
 90
 91	return o, cmd
 92}
 93
 94// View renders the form.
 95func (o *Overwrite) View() string {
 96	if o.form == nil {
 97		return ""
 98	}
 99	return o.form.View()
100}
101
102// Title returns the screen's display title.
103func (o *Overwrite) Title() string { return "Overwrite existing files?" }
104
105// KeyBindings returns bindings for the help bar.
106func (o *Overwrite) KeyBindings() []key.Binding {
107	return []key.Binding{
108		key.NewBinding(key.WithKeys("↑/↓"), key.WithHelp("↑/↓", "navigate")),
109		key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
110	}
111}
112
113// Selection returns the chosen overwrite mode for breadcrumb display,
114// or "" if nothing has been selected yet.
115func (o *Overwrite) Selection() string { return o.selection }
116
117// Value returns the chosen overwrite mode value (one of: if-changed,
118// if-newer, never, always).
119func (o *Overwrite) Value() string { return o.selected }