Skip to content

Commit

Permalink
unexport Field, add type converter AsField
Browse files Browse the repository at this point in the history
  • Loading branch information
ardnew committed Jan 17, 2024
1 parent d96b712 commit 96402e2
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 65 deletions.
127 changes: 63 additions & 64 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/charmbracelet/huh"
)

type Field struct {
type field struct {
*Model

value *FilePath
Expand Down Expand Up @@ -39,163 +39,162 @@ type Field struct {
// KeyMap(keys *huh.KeyMap) huh.Field
// Width(width int) huh.Field

func Value(value string) Option[*Field] {
return func(f *Field) *Field { return f.WithValue(value) }
// Value returns an Option that sets the value of a field.
func Value(value string) Option[*field] {
return func(f *field) *field { return f.WithValue(value) }
}

func Key(key string) Option[*Field] {
return func(f *Field) *Field { return f.WithKey(key) }
// Key returns an Option that sets the key of a field.
func Key(key string) Option[*field] {
return func(f *field) *field { return f.WithKey(key) }
}

func Heading(heading string) Option[*Field] {
return func(f *Field) *Field { return f.WithHeading(heading) }
// Heading returns an Option that sets the heading of a field.
func Heading(heading string) Option[*field] {
return func(f *field) *field { return f.WithHeading(heading) }
}

func Caption(caption string) Option[*Field] {
return func(f *Field) *Field { return f.WithCaption(caption) }
// Caption returns an Option that sets the caption of a field.
func Caption(caption string) Option[*field] {
return func(f *field) *field { return f.WithCaption(caption) }
}

func Validate(validate func(FilePath) error) Option[*Field] {
return func(f *Field) *Field { return f.WithValidate(validate) }
// Validate returns an Option that sets the validation function of a field.
func Validate(validate func(FilePath) error) Option[*field] {
return func(f *field) *field { return f.WithValidate(validate) }
}

// Field returns a new Field of the receiver Model.
//
// Field implements both its epynomous interface and the Model interface used in
// the Bubble Tea framework (module packages "huh" & "bubbletea", respectively).
func (m *Model) Field(options ...Option[*Field]) *Field {
filter := textinput.New()
filter.Prompt = "/"

return &Field{
Model: m,
value: new(FilePath),
validate: func(FilePath) error { return nil },
filter: filter,
}
// Prompt returns an Option that sets the prompt of a field.
func Prompt(prompt string) Option[*field] {
return func(f *field) *field { return f.WithPrompt(prompt) }
}

// Init initializes the Field.
func (f *Field) Init() tea.Cmd {
// Init initializes the field.
func (f *field) Init() tea.Cmd {
return f.Model.Init()
}

func (f *Field) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (f *field) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
_, cmd := f.Model.Update(msg)
return f, cmd
}

func (f *Field) View() string {
func (f *field) View() string {
return f.Model.View()
}

// Blur blurs the Field.
func (f *Field) Blur() tea.Cmd {
// Blur blurs the field.
func (f *field) Blur() tea.Cmd {
f.isFocused = false
f.err = f.validate(*f.value)
return nil
}

// Focus focuses the Field.
func (f *Field) Focus() tea.Cmd {
// Focus focuses the field.
func (f *field) Focus() tea.Cmd {
f.isFocused = true
return nil
}

// Error returns the error of the Field.
func (f *Field) Error() error {
// Error returns the error of the field.
func (f *field) Error() error {
return f.err
}

// Run runs the Field.
func (f *Field) Run() error {
// Run runs the field.
func (f *field) Run() error {
if f.accessible {
return f.runAccessible()
}
return newRunError(huh.Run(f))
}

// KeyBinds returns the keybindings for the Field.
func (f *Field) KeyBinds() []key.Binding {
// KeyBinds returns the keybindings for the field.
func (f *field) KeyBinds() []key.Binding {
return []key.Binding{} // f.keys.bindings()
}

// With returns the receiver with the given options applied.
func (f *Field) With(options ...Option[*Field]) *Field {
func (f *field) With(options ...Option[*field]) *field {
for _, option := range options {
f = option(f)
}
return f
}

// WithTheme sets the theme of the Field.
func (f *Field) WithTheme(theme *huh.Theme) huh.Field {
// WithTheme sets the theme of the field.
func (f *field) WithTheme(theme *huh.Theme) huh.Field {
f.theme = theme
f.filter.Cursor.Style = f.theme.Focused.TextInput.Cursor
f.filter.PromptStyle = f.theme.Focused.TextInput.Prompt
return f
}

// WithAccessible sets the accessible mode of the Field.
func (f *Field) WithAccessible(accessible bool) huh.Field {
// WithAccessible sets the accessible mode of the field.
func (f *field) WithAccessible(accessible bool) huh.Field {
f.accessible = accessible
return f
}

// WithKeyMap sets the keymap on a Field.
func (f *Field) WithKeyMap(keys *huh.KeyMap) huh.Field {
// WithKeyMap sets the keymap on a field.
func (f *field) WithKeyMap(keys *huh.KeyMap) huh.Field {
// TBD
return f
}

// WithWidth sets the width of the Field.
func (f *Field) WithWidth(width int) huh.Field {
// WithWidth sets the width of the field.
func (f *field) WithWidth(width int) huh.Field {
f.width = width
return f
}

// GetKey returns the key of the field.
func (f *Field) GetKey() string {
func (f *field) GetKey() string {
return f.key
}

// GetValue returns the value of the field.
func (f *Field) GetValue() any {
func (f *field) GetValue() any {
return f.value.path()
}

// Value sets the value of the Field.
func (f *Field) WithValue(value string) *Field {
// WithValue sets the value of the field.
func (f *field) WithValue(value string) *field {
f.value = f.value.init(value)
return f
}

// Key sets the key of the Field which can be used to retrieve the value
// after submission.
func (f *Field) WithKey(key string) *Field {
// WithKey sets the key of the field.
func (f *field) WithKey(key string) *field {
f.key = key
return f
}

// Heading sets the heading of the Field.
func (f *Field) WithHeading(heading string) *Field {
// WithHeading sets the heading of the field.
func (f *field) WithHeading(heading string) *field {
f.heading = heading
return f
}

// Caption sets the caption of the Field.
func (f *Field) WithCaption(caption string) *Field {
// WithCaption sets the caption of the field.
func (f *field) WithCaption(caption string) *field {
f.caption = caption
return f
}

// Validate sets the validation function of the Field.
func (f *Field) WithValidate(validate func(FilePath) error) *Field {
// WithValidate sets the validation function of the field.
func (f *field) WithValidate(validate func(FilePath) error) *field {
f.validate = validate
return f
}

func (f *Field) runAccessible() error {
// WithPrompt sets the prompt of the field.
func (f *field) WithPrompt(prompt string) *field {
f.filter.Prompt = prompt
return f
}

func (f *field) runAccessible() error {
var sb strings.Builder
sb.WriteString(f.theme.Focused.Title.Render(f.heading) + "\n")

Expand All @@ -221,11 +220,11 @@ func (f *Field) runAccessible() error {
return nil
}

func (f *Field) setIsFiltered(isFiltered bool) {
func (f *field) setIsFiltered(isFiltered bool) {
f.isFiltered = isFiltered
}

func (f *Field) filterFunc(option string) bool {
func (f *field) filterFunc(option string) bool {
// XXX: remove diacritics or allow customization of filter function.
return strings.Contains(
strings.ToLower(option),
Expand Down
2 changes: 1 addition & 1 deletion option.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package walk

// Optional is a type with which Option arguments and methods can be applied.
type Optional interface{ *Model | *Field }
type Optional interface{ *Model | *field }

type Option[O Optional] func(O) O
43 changes: 43 additions & 0 deletions walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/antonmedv/clipboard"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/sahilm/fuzzy"
Expand All @@ -34,6 +35,7 @@ type Model struct {
path string // Current dir path we are looking at.
files []fs.DirEntry // Files we are looking at.
err error // Error while listing files.
field *field // Bubble Tea Huh form field.
keys *KeyMap // Key bindings.
st *Styles // Rendering attributes.
cmdline []string // Command line to open files.
Expand Down Expand Up @@ -75,12 +77,16 @@ type (
func New(options ...Option[*Model]) *Model {
m := (&Model{positions: make(map[string]position)}).With(options...)

// Use the default key bindings if none provided.
if m.keys == nil {
m.keys = m.keys.Default()
}

// Use the default style if none provided.
if m.st == nil {
m.st = m.st.Default()
}

return m
}

Expand Down Expand Up @@ -114,6 +120,13 @@ func Keys(keys *KeyMap) Option[*Model] {
return func(m *Model) *Model { return m.WithKeys(keys) }
}

// Field returns an Option that configures Model as a form field.
//
// This Option must be provided to initialize Model as a form field.
func Field(options ...Option[*field]) Option[*Model] {
return func(m *Model) *Model { return m.withField(options...) }
}

// Kill exits the program with the given exit status.
func Kill(status int) { os.Exit(status) }

Expand Down Expand Up @@ -508,6 +521,9 @@ func (m *Model) View() string {
// Exit exits the program with the receiver's current exit status.
func (m *Model) Exit() { Kill(m.status) }

// Field returns the receiver's field used in a form.
func (m *Model) Field() *field { return m.field }

// Value returns the path of the currently selected file.
func (m *Model) Value() string {
path, _ := m.filePath()
Expand Down Expand Up @@ -560,6 +576,33 @@ func (m *Model) WithKeys(keys *KeyMap) *Model {
return m
}

// withField returns the receiver with the given options applied to its field.
//
// This method must be called to initialize walk as a form field, and it must
// be called when initializing a new Model by providing a field Option.
//
// The types Model and field both implement different interfaces from the
// Bubble Tea framework. field is a specialization of Model that allows walk to
// be used as a discrete field in a Bubble Tea "huh" form application:
//
// | Interface | `field` | `Model` |
// |------------:|:-------:|:-------:|
// | `tea.Model` | ✓ | ✓ |
// | `huh.Field` | ✓ | |
func (m *Model) withField(options ...Option[*field]) *Model {
m.field = (&field{
Model: m,
value: new(FilePath),
validate: func(FilePath) error { return nil },
filter: textinput.New(),
}).With(options...)
return m
}

func (m *Model) AsField(options ...Option[*field]) *field {
return m.withField(options...).field
}

func (m *Model) moveUp() {
m.r--
if m.r < 0 {
Expand Down

0 comments on commit 96402e2

Please sign in to comment.