Skip to content

Commit

Permalink
feat: dynamic
Browse files Browse the repository at this point in the history
Adds `Func`-able properties to all fields to allow dynamic updates
based on updates in other parts of the group.

Note that bindings must be specified. Bindings allow the Function to
know when it should update as the functoin re-evaluates only when the
value of the binding changes.
  • Loading branch information
maaslalani committed May 9, 2024
1 parent 0a35040 commit c7d3231
Show file tree
Hide file tree
Showing 20 changed files with 1,228 additions and 197 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@

# Go workspace file
go.work

# Debugging
debug.log
79 changes: 79 additions & 0 deletions eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package huh

import (
"time"

"github.com/mitchellh/hashstructure/v2"
)

type Eval[T any] struct {

Check warning on line 9 in eval.go

View workflow job for this annotation

GitHub Actions / lint

exported: exported type Eval should have comment or be unexported (revive)
val T
fn func() T

bindings any
bindingsHash uint64
cache map[uint64]T

loading bool
loadingStart time.Time
}

const spinnerShowThreshold = 25 * time.Millisecond

func hash(val any) uint64 {
hash, _ := hashstructure.Hash(val, hashstructure.FormatV2, nil)
return hash
}

func (e *Eval[T]) shouldUpdate() (bool, uint64) {
if e.fn == nil {
return false, 0
}
newHash := hash(e.bindings)
return e.bindingsHash != newHash, newHash
}

func (e *Eval[T]) loadFromCache() bool {
val, ok := e.cache[e.bindingsHash]
if ok {
e.loading = false
e.val = val
}
return ok
}

func (e *Eval[T]) update(val T) {
e.val = val
e.cache[e.bindingsHash] = val
e.loading = false
}

type updateTitleMsg struct {
id int
hash uint64
title string
}

type updateDescriptionMsg struct {
id int
hash uint64
description string
}

type updatePlaceholderMsg struct {
id int
hash uint64
placeholder string
}

type updateSuggestionsMsg struct {
id int
hash uint64
suggestions []string
}

type updateOptionsMsg[T comparable] struct {
id int
hash uint64
options []Option[T]
}
68 changes: 32 additions & 36 deletions examples/conditional/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,44 +38,40 @@ func main() {
huh.NewOption("A vegetable", vegetables),
huh.NewOption("A drink", drinks),
),
),

huh.NewGroup(
huh.NewSelect[string]().
Title("Okay, what kind of fruit are you in the mood for?").
Options(
huh.NewOption("Tangerine", "tangerine"),
huh.NewOption("Canteloupe", "canteloupe"),
huh.NewOption("Pomelo", "pomelo"),
huh.NewOption("Grapefruit", "grapefruit"),
).
Value(&choice),
).WithHideFunc(func() bool { return category != fruits }),

huh.NewGroup(
huh.NewSelect[string]().
Title("Okay, what kind of vegetable are you in the mood for?").
Options(
huh.NewOption("Carrot", "carrot"),
huh.NewOption("Jicama", "jicama"),
huh.NewOption("Kohlrabi", "kohlrabi"),
huh.NewOption("Fennel", "fennel"),
huh.NewOption("Ginger", "ginger"),
).
Value(&choice),
).WithHideFunc(func() bool { return category != vegetables }),

huh.NewGroup(
huh.NewSelect[string]().
Title(fmt.Sprintf("Okay, what kind of %s are you in the mood for?", category)).
Options(
huh.NewOption("Coffee", "coffee"),
huh.NewOption("Tea", "tea"),
huh.NewOption("Bubble Tea", "bubble tea"),
huh.NewOption("Agua Fresca", "agua-fresca"),
).
Value(&choice),
).WithHideFunc(func() bool { return category != drinks }),
Value(&choice).
Height(7).
TitleFunc(func() string {
return fmt.Sprintf("Okay, what kind of %s are you in the mood for?", category)
}, &category).
OptionsFunc(func() []huh.Option[string] {
switch category {
case fruits:
return []huh.Option[string]{
huh.NewOption("Tangerine", "tangerine"),
huh.NewOption("Canteloupe", "canteloupe"),
huh.NewOption("Pomelo", "pomelo"),
huh.NewOption("Grapefruit", "grapefruit"),
}
case vegetables:
return []huh.Option[string]{
huh.NewOption("Carrot", "carrot"),
huh.NewOption("Jicama", "jicama"),
huh.NewOption("Kohlrabi", "kohlrabi"),
huh.NewOption("Fennel", "fennel"),
huh.NewOption("Ginger", "ginger"),
}
default:
return []huh.Option[string]{
huh.NewOption("Coffee", "coffee"),
huh.NewOption("Tea", "tea"),
huh.NewOption("Bubble Tea", "bubble tea"),
huh.NewOption("Agua Fresca", "agua-fresca"),
}
}
}, &category),
),
).Run()

if err != nil {
Expand Down
54 changes: 54 additions & 0 deletions examples/dynamic/dynamic-all/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"log"
"strconv"

"github.com/charmbracelet/huh"
)

func main() {
var value string = "Dynamic"

f := huh.NewForm(
huh.NewGroup(
huh.NewInput().Value(&value).Title("Dynamic").Description("Dynamic"),
huh.NewNote().
TitleFunc(func() string { return value }, &value).
DescriptionFunc(func() string { return value }, &value),
huh.NewSelect[string]().
Height(7).
TitleFunc(func() string { return value }, &value).
DescriptionFunc(func() string { return value }, &value).
OptionsFunc(func() []huh.Option[string] {
var options []huh.Option[string]
for i := 1; i < 6; i++ {
options = append(options, huh.NewOption(value+" "+strconv.Itoa(i), value+strconv.Itoa(i)))
}
return options
}, &value),
huh.NewMultiSelect[string]().
Height(7).
TitleFunc(func() string { return value }, &value).
DescriptionFunc(func() string { return value }, &value).
OptionsFunc(func() []huh.Option[string] {
var options []huh.Option[string]
for i := 1; i < 6; i++ {
options = append(options, huh.NewOption(value+" "+strconv.Itoa(i), value+strconv.Itoa(i)))
}
return options
}, &value),
huh.NewConfirm().
TitleFunc(func() string { return value }, &value).
DescriptionFunc(func() string { return value }, &value),
huh.NewText().
TitleFunc(func() string { return value }, &value).
DescriptionFunc(func() string { return value }, &value).
PlaceholderFunc(func() string { return value }, &value),
),
)
err := f.Run()
if err != nil {
log.Fatal(err)
}
}
Loading

0 comments on commit c7d3231

Please sign in to comment.