Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131package tui
import ( "slices" "strings"
"charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "github.com/charmbracelet/x/ansi")
// form is a column of text fields with one focused; the MCP and webhook// forms both build on it.type form struct { Inputs map[string]*textinput.Model Focus int}
// newField is a text input that follows a label instead of a prompt.func newField() textinput.Model { input := newTextInput() input.Prompt = "" return input}
// ask re-arms input for the next question, masking a secret, and reports// the blink command that shows its cursor.func ask(input *textinput.Model, value string, secret bool) tea.Cmd { input.Reset() input.EchoMode = echo(secret) input.SetValue(value) input.Focus() return textinput.Blink}
func echo(secret bool) textinput.EchoMode { if secret { return textinput.EchoPassword } return textinput.EchoNormal}
// newForm makes a field for each key, the masked ones echoing dots, with a// length limit no pasted secret reaches.func newForm(keys []string, masked ...string) form { f := form{Inputs: map[string]*textinput.Model{}} for _, key := range keys { input := newField() input.CharLimit, input.EchoMode = 4096, echo(slices.Contains(masked, key)) f.Inputs[key] = &input } return f}
// focus focuses the field named current and blurs the rest.func (f *form) focus(current string) { for key, input := range f.Inputs { if key == current { input.Focus() } else { input.Blur() } }}
// key applies the movement keys every form shares, cycling through fields// from first on; submit reports that the form should be saved.func (f *form) key(msg tea.Msg, fields []string, first int) (submit, handled bool) { key, ok := msg.(tea.KeyPressMsg) if !ok { return false, false } delta := 0 switch key.String() { case "tab", "down": delta = 1 case "shift+tab", "up": delta = -1 case "ctrl+s": return true, true case "enter": if f.Focus == len(fields)-1 { return true, true } delta = 1 default: return false, false } n := len(fields) - first f.Focus = first + (f.Focus-first+delta+n)%n f.focus(fields[f.Focus]) return false, true}
// value is a field's text without surrounding space.func (f form) value(key string) string { return strings.TrimSpace(f.Inputs[key].Value()) }
// fit sizes the fields to the columns their rows leave for a value; a// textinput without a width shows only the first rune of its placeholder.func (f form) fit(width int) { for _, input := range f.Inputs { input.SetWidth(max(1, width)) }}
// formRow is one labelled field, marked when focused.func formRow(focused bool, label string, pad int, value string, width int) string { lead := " " if focused { lead = promptLead() } return ansi.Truncate(lead+padRight(label, pad)+value, width, "…")}
// formFooter explains the focused field, then offers the keys every form// shares after any of its own.func formFooter(note string, width int, own ...hint) string { line := keyHints(append(own, hint{"tab/↑↓", "move"}, hint{"enter", "next"}, hint{"ctrl+s", "save"}, hint{"esc", "cancel"})...) if note != "" { line = DefaultStyles.Faint.Render(note) + DefaultStyles.Decor.Render(" · ") + line } return ansi.Truncate(line, width, "…")}
func padRight(s string, n int) string { if len(s) >= n { return s + " " } return s + strings.Repeat(" ", n-len(s))}