charmbracelet / huh Goto Github PK
View Code? Open in Web Editor NEWBuild terminal forms and prompts ๐คท๐ปโโ๏ธ
License: MIT License
Build terminal forms and prompts ๐คท๐ปโโ๏ธ
License: MIT License
Hey team, first of all thanks for the great library. I'm planning to replace the [archived] survey with huh, so just playing around with different methods.
I believe that it expects that the "theme" member variable to be populated, but when running the sample code below, it is nil.
The panic happens in field-input.go:262
on v0.2.3:
frameSize := i.theme.Blurred.Base.GetHorizontalFrameSize()
And it looks like it can still happen on master (permalink)
Now, let's do the formalities:
Describe the bug
var workspace string
huh.NewInput().
Title("Enter Slack workspace name").
Value(&workspace).
Description("Blah").
WithWidth(40).
Run()
return workspace, nil
Causes
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x60 pc=0x191a54a]
goroutine 1 [running]:
github.com/charmbracelet/huh.(*Input).WithWidth(0xc000328e00, 0x28)
/Users/rustam/go/pkg/mod/github.com/charmbracelet/[email protected]/field_input.go:262 +0x2a
github.com/rusq/slackdump/v2/auth/auth_ui.(*Huh).RequestWorkspace(0x223c438, {0xc000374a00?, 0xc0007bf050?})
/Users/rustam/wp/_github/slackdump/auth/auth_ui/huh.go:18 +0x165
github.com/rusq/slackdump/v2/auth.NewRODAuth({0x223ab38, 0xc0004a7200}, {0xc0007bf1b0, 0x1, 0xc0000061a0?})
/Users/rustam/wp/_github/slackdump/auth/rod.go:53 +0x15d
github.com/rusq/slackdump/v2/internal/app.SlackCreds.AuthProvider({{0x0?, 0x1bf33a0?}, {0x0?, 0x1413ff2?}}, {0x223ab38, 0xc0004a7200}, {0x0, 0x0}, 0x0, 0x0)
/Users/rustam/wp/_github/slackdump/internal/app/auth.go:81 +0x2f6
github.com/rusq/slackdump/v2/internal/app.InitProvider({0x223ab38?, 0xc0004a71d0?}, {0xc0000e6720, 0x26}, {0x0, 0x0}, {0x2237d60, 0xc0002e2d00}, 0x29cde90?, 0x0)
/Users/rustam/wp/_github/slackdump/internal/app/auth.go:155 +0x319
main.run({_, _}, {{{0x1, 0x0}, {0xc0004a68a0}, {{0x222c888, 0x1}, {0x1d17490, 0x4}, {0x0, ...}}, ...}, ...})
/Users/rustam/wp/_github/slackdump/cmd/slackdump/main.go:146 +0x28f
main.main()
/Users/rustam/wp/_github/slackdump/cmd/slackdump/main.go:110 +0x546
exit status 2
Expected behavior
Don't panic
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
Additional context
Add any other context about the problem here.
hi
Describe the bug
In the runAccessible
method of the MultiSelect field, there is a missing check for the limit when selecting options. This causes the accessible mode to allow selecting more options than the specified limit. This limit checkis handle in update method.
To Reproduce
Steps to reproduce the behavior:
WithAccessible(true)
Expected behavior
The accessible mode should enforce the specified limit on the number of options that can be selected, just like in the regular Run
method (in update function).
Screenshots
In below image you can see that i can able to select more options than specified limit.
Desktop :
Device : Laptop
OS : WIN 11
go Version : go 1.21.6 windows/amd64
Proposed solution
Option 1:
func (m *MultiSelect[T]) runAccessible() error {
m.printOptions()
var choice int
for {
fmt.Printf("Select up to %d options. 0 to continue.\n", m.limit)
choice = accessibility.PromptInt("Select: ", 0, len(m.options))
if choice == 0 {
m.finalize()
err := m.validate(*m.value)
if err != nil {
fmt.Println(err)
continue
}
break
}
+ if !m.options[choice-1].selected && m.limit > 0 && m.numSelected() >= m.limit {
+ fmt.Printf("You can't select above %d options. 0 to continue.\n", m.limit)
+ continue
+ }
m.options[choice-1].selected = !m.options[choice-1].selected
if m.options[choice-1].selected {
fmt.Printf("Selected: %s\n\n", m.options[choice-1].Key)
} else {
fmt.Printf("Deselected: %s\n\n", m.options[choice-1].Key)
}
m.printOptions()
}
var values []string
for _, option := range m.options {
if option.selected {
*m.value = append(*m.value, option.Value)
values = append(values, option.Key)
}
}
fmt.Println(m.theme.Focused.SelectedOption.Render("Selected:", strings.Join(values, ", ")+"\n"))
return nil
}
Option 2:
Alternatively, a separate checker function can be introduced to handle the all the limit check. This function can be called before toggling the selected state of an option in the runAccessible
method and in Run
method (update function).
Assign this to me.
Describe the bug
If you create e.g. a NewMultiSelect with a lot of items than it happens that some items are not visible (depending on the terminal hright
To Reproduce
Steps to reproduce the behavior:
package main
import (
"strconv"
"github.com/charmbracelet/huh"
)
func main() {
result := []string{}
list := []huh.Option[string]{}
for i := 0; i < 30; i++ {
item := "item-" + strconv.Itoa(i)
list = append(list, huh.NewOption(item, item))
}
sel := huh.NewMultiSelect[string]().Options(list...).Value(&result)
form := huh.NewForm(
huh.NewGroup(sel).Description("Long List"),
)
err := form.Run()
if err != nil {
panic(err)
}
}
Run in a terminal which can show less then 30 lines.
Expected behavior
List should be scrollable
Screenshots
Desktop (please complete the following information):
Describe the bug
You cannot currently override the theme for a single Field
or Group
element.
Is your feature request related to a problem? Please describe.
When creating prompts for editing a file for example, some values may already have been filled previously. Instead of only showing it as placeholder and needing the user to retype it, it would be great if it was there and editable.
Describe the solution you'd like
An option on input to add a string as a default value that will be prefilled in the input
Is your feature request related to a problem? Please describe.
While moving from survey to huh, I noticed a significant increase of my binary.
So an increase around 35%.
Describe the solution you'd like
Reduce the lib footprint.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
I'm implementing a project where it's a CLI that will create shortcuts with the keyboard that will run complex tasks. There's one flow where we can select the events we want and which keys will trigger them in the same form process. We currently have 5 options available where you can select the key to press, but we want to make it more user-friendly and filter the keys that the customer has already selected to execute something.
Describe the solution you'd like
As we are familiar with the WithHideFunc at the group level, I believe it would be nice to have something like that at the Option level.
Describe alternatives you've considered
I was thinking of using WithHideFunc at the group level to create a brute force of all possibilities, but it will create a lot of boilerplate for a simple functionality
PS: If we have another way to implement such a feature with the actual API please let me know ๐โโ๏ธ
Also, thanks for all those amazing libraries for CLI. It made me go back to Golang to have some fun ๐
Hi team, it's me again! I promise I will stop creating these after I finish the migration from Survey.
Is your feature request related to a problem? Please describe.
Survey inputs had an option to add an input "suggestion", user could press the [Tab] key and they would get the list of suggestions. This could be useful, if programme asks the user to input the existing filename (at least this is my use case).
I know that you already know what I'm writing about, but for completeness, here's the link.
Describe the solution you'd like
I would like to be able to specify the function that would make an input suggestion to the user.
When there's one and only one suggestion, the tab should complete the input to the suggested value.
When there's more than one suggestion there are two possible options: first one is to show the list of all, or "top N" items that match the prefix entered, or, cycle through suggestions on each press of the [Tab] key, just like cmd.exe
prompt does in Windows.
Please keep in mind that I just started using Huh, so maybe there's some bubble-magic that I'm unaware of that allows to implement this easily?
Describe alternatives you've considered
The alternative that I have considered is not using the suggestions, or implement it myself, which I haven't gotten around to, as I'm quite new to the whole charm.sh framework.
Additional context
Here's how it's implemented in Survey (copy/pasting here for convenience):
file := ""
prompt := &survey.Input{
Message: "inform a file to save:",
Suggest: func (toComplete string) []string {
files, _ := filepath.Glob(toComplete + "*")
return files
},
}
}
survey.AskOne(prompt, &file)
Thank you for consideration.
I was playing around with huh and on the select input type there is a get value function and I am wondering why it returns any
and not T
/*T
.
This func specifically
Lines 542 to 545 in 71f31c1
Should this be?
// GetValue returns the value of the field.
func (s *Select[T]) GetValue() T {
return *s.value
}
Or as a pointer.
// GetValue returns the value of the field.
func (s *Select[T]) GetValue() *T {
return s.value
}
This may affect other input types, I did not really check. I can definitely contribute as well if a change like this makes sense. ( May break compatibility so not sure )
[Question] How to implement multi-select filter
The test found that select filter is supported, but multi-select filter is not supported
Describe the bug
The render function currently exhibits undesired behavior when handling overlapping styles, specifically with italic, bold, and code block styles.
The current behavior makes it challenging to render text when overlapping styles are present. This issue arises when closing tags for italic and bold styles coincide with the opening or closing tags of a code block. This happens when any styles overlap with each other.
Code
huh.NewNote().
Title("").
Description("*Jon, _Welcome to_ Joncafe*, How may we take your *order*?")
In above image, you can see that the "JonCafe" should be bold but it is not.
To Reproduce
Steps to reproduce the behavior:
NewNote
field in a form with overlapping styles as shown in example aboveDesktop (please complete the following information):
Proposed solution
Instead of resetting all the formatting using \033[0m
, we can just the reset the formatting for that particular style. as shown in solution below:
func render(input string) string {
var result strings.Builder
var italic, bold, codeblock bool
for _, char := range input {
switch char {
case '_':
if !italic {
result.WriteString("\033[3m")
italic = true
} else {
- result.WriteString("\033[0m")
+ result.WriteString("\033[23m")
italic = false
}
case '*':
if !bold {
result.WriteString("\033[1m")
bold = true
} else {
- result.WriteString("\033[0m")
+ result.WriteString("\033[22m")
bold = false
}
case '`':
if !codeblock {
result.WriteString("\033[0;37;40m")
result.WriteString(" ")
codeblock = true
} else {
result.WriteString(" ")
result.WriteString("\033[0m")
codeblock = false
+ if bold {
+ result.WriteString("\033[1m")
+ }
+ if italic {
+ result.WriteString("\033[3m")
+ }
}
default:
result.WriteRune(char)
}
}
// Reset any open formatting
result.WriteString("\033[0m")
return result.String()
}
Assign this to me.
Is your feature request related to a problem? Please describe.
I want a Confirm
field automatically go on with the preselection after 5 seconds without user interaction.
Describe the solution you'd like
Add a timeout config to the Run
func
Some pseudo code:
form := huh.NewForm(...)
form.Run(cfg{timeout: time.Duration})
huh.NewInput().
...
Run(cfg{timeout: time.Duration})
If a timeout is set, a countdown starts when Run is called. When it finishes, Run
does not block anymore and adds preselections to Values.
As soon as a key is hit while countdown has not finished, count down stops and works as w/o timeout
I'm wondering if it's possible to support tab completion for input fields. Ideally it would be possible to use tab completion when entering values like it is outside of the form.
It may already be possible to configure this and I am missing something.
Thanks in Advance!
Note
Fields should allow to automatically progress to the next field so that the user does not have to interactive with them.
This would be especially useful for using notes to separate fields in the same group.
I thought it would be a nice idea to add the ability to search items in a select field. When there are a lot of items in it, it could be too much effort to scroll all the items until I reach the item I want.
I thought it would be nice to be able to search the items in the list.
I want to experiment with different Group layouts (for example as rows, as many of my forms are short fields, but many fields). Currently, I can't really do too much because I have to use the Group struct. I would like to play with the group titles and such as well.
Turning Group into an interface would make it fairly easy to experiment more (like with Field). This would also allow pulling in thirdparty huh-improvements easier.
I'm happy to do the necessary work, but wanted to check in before opening a pull request.
Just like Inline(true)
for inputs. We should allow Inline(true)
for Confirm fields to allow for more compact layouts.
I have a framework to describe application's input parameters in YAML, and use that to generate CLI flags/REST endpoints/HTML forms and much more, and I was about to write a TUI widget to render them with bubbletea. Here are a list of the widgets I need to build to support my tool:
I was wondering if you had any plans to implement these (and thus a design in mind), or if I should just go ahead and build my own and keep them say, in a separate library?
Here's a proof of concept: https://asciinema.org/a/ndG2OO2XqjYtenE5x9LYLRKvh
Here's my framework: https://github.com/go-go-golems/glazed
It does now appear to be possible to go back in a form using shift-tab
on Windows (using Windows Terminal).
To Reproduce
Expected behavior
Moving back to the previous form input.
Desktop:
Is your feature request related to a problem? Please describe.
I am attempting to allow users to input sensitive information, such as API keys, but currently, this displays the information on screen.
Describe the solution you'd like
Add either a Hidden()
and/or Mask()
method to the input filed.
Hidden input:
huh.NewInput().
Title("Enter your Credentials").
Prompt("API Key:").
Hidden().
Value(&apiKey)
This would prevent the characters typed from appearing on the screen, similar to how entering your user passwd on linux would be.
Masked Input:
huh.NewInput().
Title("Enter your Credentials").
Prompt("API Key:").
Hidden().
Value(&apiKey)
This would mask the input, similar to web forms, replacing password
with ********
Describe alternatives you've considered
I have not seen yet a standalone bubble tea component that could be easily integrated with huh.
Additional context
I am currently working on a project that needs this. I will be moving past it for now, but when I come back to it, if this is still needed, I may contribute the solution.
Is your feature request related to a problem? Please describe.
Currently, when a user confirms a prompt such as an input, or a select for instance, and confirm it, his choice is clear from the terminal. Itโs kind of frustracting when you came back few times later and what to check what you did.
Describe the solution you'd like
Add a parameter to each field, named for instance Persist
, that will trigger this behavior.
Describe alternatives you've considered
N/A
Additional context
It was the default behavior of survey.
When customizing themes for a form I frequently want to make a copy from a base theme. The theme copy function is currently not exported.
To get around this I have to write my own function that deep copies every field of theme. Basically exactly what theme.copy does.
Are you open to exporting theme.copy? I would be happy to put up a PR.
Describe the bug
I'm integrating Huh with a Bubbletea app and it seems like m.form.GetValue("field") does not work for Text fields
To Reproduce
type Model struct {
form *huh.Form // huh.Form is just a tea.Model
}
func NewModel() Model {
return Model{
form: huh.NewForm(
huh.NewGroup(
huh.NewText().
Key("question").
Title("What's your question?"),
),
)
}
}
func (m Model) Init() tea.Cmd {
return m.form.Init()
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// ...
form, cmd := m.form.Update(msg)
if f, ok := form.(*huh.Form); ok {
m.form = f
}
return m, cmd
}
func (m Model) View() string {
if m.form.State == huh.StateCompleted {
question := m.form.GetString("question")
return fmt.Sprintf("You selected: %s", question)
}
return m.form.View()
}
Expected behavior
question should contain the value of the Text input once it's completed. It is empty. If I change the code to be huh.NewInput(...)
getting the value works.
Thanks!
Describe the bug
I imagined Value
could be used to set the default selection for a Select
that way:
foo := "two"
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().Title("foo").Options(huh.NewOptions("one", "two", "three")...).Value(&foo),
),
)
However, the form still selects one
by default.
To Reproduce
See code snippet above.
Expected behavior
two
selected by default.
Is your feature request related to a problem? Please describe.
My current CLI ask several questions to the end user, and each question depends on the choice of the previous one. For instance, select an AWSโฏcluster, then an AWS service.
So I cannot use the form feature of huh (or can I?). Because of this, I cannot display the help message for my prompt.
Describe the solution you'd like
Add a parameter ShowHelpMessage for the fields also.
Describe alternatives you've considered
Create a form for each single question.
Additional context
N/A
Describe the bug
WithKeyMap
does not seem to have any effect on the Text
field.
To Reproduce
Steps to reproduce the behavior:
textField := huh.NewText().
Title("...").
Value(&message).
WithKeyMap(&huh.KeyMap{
Text: huh.TextKeyMap{
Next: key.NewBinding(key.WithKeys("ctrl+enter"), key.WithHelp("ctrl+enter", "next")),
Prev: key.NewBinding(key.WithKeys("shift+tab"), key.WithHelp("shift+tab", "back")),
NewLine: key.NewBinding(key.WithKeys("enter", "ctrl+j"), key.WithHelp("enter / ctrl+j", "new line")),
Editor: key.NewBinding(key.WithKeys("ctrl+e"), key.WithHelp("ctrl+e", "open editor")),
},
})
form := huh.NewForm(
huh.NewGroup(
textField,
),
)
Help text at the end of the field does not reflect the new KeyMap and the default keymap still applies.
Expected behavior
Next and NewLine should have been overriden.
Is your feature request related to a problem? Please describe.
It will be challenging to track the progress of the form submission without a visual representation, making it less user-friendly.
Describe the solution you'd like
Addition of a progress bar feature for the form will solve this probelm. The progress bar should dynamically update as the user completes each step of the form, providing a visual indication of the overall progress. This progress bar will be optional.
Assign this to me. (but need better design of the progress bar)
Hi, thanks for that lib, which seems to contain so interesting stuff.
We'd like still to have a very little feature that would clearly be an enabler for us.
Is your feature request related to a problem? Please describe.
We donยดt find any good lib rendering correctly the password. Every time, it is proposed to replace characters by a mark. This is not what we'd like but something more like the EchoNone
proposed by the bubble library. Why not propose it as well in huh?
Describe the solution you'd like
I'd like a setter similar with Password() one but that would set EchoNone
as EchoMode
under the hood.
Describe alternatives you've considered
I tried to create a custom InputField without success.
I finally ending up using huh for all my fields except password for which I use raw bubble directly.
Anyway simple setter, like Password() is doing, would be much more readable.
Additional context
No further context
Edit:
To be 100% clear, I'm speaking about these lines of code https://github.com/charmbracelet/huh/blob/main/field_input.go#L103-L110
Hey team, happy new year!
Describe the bug
When setting Select.WithAccessible(true)
, and then Run
'ning, it causes panic:
.../[email protected]/field_select.go:290 +0x5f
sb.WriteString(s.theme.Focused.Title.Render(s.title) + "\n")
To Reproduce
Sample code:
var resp string
q := huh.NewSelect[string]().
Options(
huh.NewOptions("one", "two", "three")...,
).
Title("What would you like to do?").
Value(&resp).
WithAccessible(p.accessible)
if err := q.Run(); err != nil {
return err
}
Panic happens on Run.
Expected behavior
No panic.
Screenshots
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x310 pc=0x1afeddf]
goroutine 1 [running]:
github.com/charmbracelet/huh.(*Select[...]).runAccessible(0x0)
/Users/rustam/go/pkg/mod/github.com/charmbracelet/[email protected]/field_select.go:290 +0x5f
github.com/charmbracelet/huh.(*Select[...]).Run(0x0?)
/Users/rustam/go/pkg/mod/github.com/charmbracelet/[email protected]/field_select.go:281 +0x25
main.Interactive(0xc000331680)
/Users/rustam/wp/_github/slackdump/cmd/slackdump/interactive.go:63 +0x57f
main.main()
/Users/rustam/wp/_github/slackdump/cmd/slackdump/main.go:98 +0x426
exit status 2
Desktop (please complete the following information):
Hey All, I've been looking into this library and the other projects bubbletea etc.
I've often had a need for a select or multiselect input for CLIs that can display table data as the options.
Ideally with a
It is possible to use the existing multiSelect and format the option value with spacing between fields however there is no header option, and it would also need an overridable filter function.
Extending on the work in #81 I have put together an example input that is styled as a table.
PR in my fork is here JoshuaWilkes#1
To pull this off I borrowed from this example https://github.com/charmbracelet/bubbletea/blob/master/examples/table/main.go
And I had to drop the generics support so the the Option could be replaced with a Row which has a string slice []string{}
for each column value.
Is this something you would consider adding in?
Thanks!
Describe the bug
The Key
in the NewSelect[string]()
field is not set.
To Reproduce
Steps to reproduce the behavior:
package main
import (
"fmt"
"github.com/charmbracelet/huh"
)
func main() {
- var country string
+ var countryName, countryCode string
s := huh.NewSelect[string]().
Title("Pick a country.").
Options(
huh.NewOption("United States", "US"),
huh.NewOption("Germany", "DE"),
huh.NewOption("Brazil", "BR"),
huh.NewOption("Canada", "CA"),
).
- Value(&country)
+ Key(countryName).
+ Value(&countryCode)
huh.NewForm(huh.NewGroup(s)).Run()
+
+ fmt.Println(countryName, countryCode)
}
countryName
variable.Expected behavior
I thought the countryName
variable would be updated in the same way as countryCode
. Otherwise, I don't understand how to get the selected key from this field for later use in my program code?
Screenshots
No need.
Desktop (please complete the following information):
Darwin user.local 22.6.0 Darwin Kernel Version 22.6.0: Tue Nov 7 21:42:27 PST 2023; root:xnu-8796.141.3.702.9~2/RELEASE_ARM64_T8103 arm64
latest
v0.2.3
Smartphone (please complete the following information):
No need.
Additional context
No.
Huh should define some common validation so that users can use them easily without having to write their own.
For example:
huh.ValidateNotEmpty
huh.ValidateLength(min, max int)
huh.ValidateOneOf(options ...string)
huh.NewInput().Title("What's your name?").Validate(huh.ValidateLength(3, 50)
Describe the bug
When using a single line input, if the answer length exceeds the terminal length, the cursor and the rest of the answer disappear.
To Reproduce
Steps to reproduce the behavior:
. Create an input
. Input a very long answer
Expected behavior
The exceeding characters are wrapped back to the next line.
Screenshots
In this example the terminal stops at the right of the recording. I input only a's then delete some to come back in the frame.
Desktop (please complete the following information):
Additional context
This may be an intended behavior but it seems not really user friendly
Describe the bug
When I go to type in the field, no input shows up. Additionally if I GetString
the key from the form, its empty. TextInput
also has the same issue but options behave as expected.
To Reproduce
https://github.com/liamawhite/tsk
Press 'a' then start typing and nothing changes.
Expected behavior
For text to appear in the input and GetString
to return the value.
Desktop (please complete the following information):
Additional context
Not sure if its something I'm doing wrong or a bug but I've been banging my head against this for several hours now and am out of ideas. ๐
Describe the bug
The commit b6644a1 for some reason seems to break my custom theming.
I'm using the following code to create my theme based on the huh.ThemeBase()
:
package theme
import (
_ "unsafe" // used for the hacky interalCopy linking.
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
)
// maybe a bit hacky but the copy method isn't exported for some reason
//
//go:linkname internalCopy github.com/charmbracelet/huh.Theme.copy
func internalCopy(huh.Theme) huh.Theme
// MyCompany is a mycompany flavored huh theme.
func MyCompany() *huh.Theme {
t := *huh.ThemeBase()
t = internalCopy(t)
var (
normalFg = lipgloss.AdaptiveColor{Light: "235", Dark: "252"}
blue = lipgloss.AdaptiveColor{Light: "#7d86f7", Dark: "#7d86f7"}
darkBlue = lipgloss.AdaptiveColor{Light: "#000b7b", Dark: "#000b7b"}
green = lipgloss.AdaptiveColor{Light: "#7afbc4", Dark: "#7afbc4"}
red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
)
f := &t.Focused
f.Base = f.Base.BorderForeground(lipgloss.Color("238"))
f.Title.Foreground(blue).Bold(true)
f.NoteTitle.Foreground(blue).Bold(true).MarginBottom(1)
f.Description.Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"})
f.ErrorIndicator.Foreground(red)
f.ErrorMessage.Foreground(red)
f.SelectSelector.Foreground(green)
f.Option.Foreground(normalFg)
f.MultiSelectSelector.Foreground(green)
f.SelectedOption.Foreground(green)
f.SelectedPrefix = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#02CF92", Dark: "#02A877"}).SetString("โ ")
f.UnselectedPrefix = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}).SetString("โข ")
f.UnselectedOption.Foreground(normalFg)
f.FocusedButton.Foreground(darkBlue).Background(green)
f.Next = f.FocusedButton.Copy()
f.BlurredButton.Foreground(normalFg).Background(lipgloss.AdaptiveColor{Light: "252", Dark: "237"})
f.TextInput.Cursor.Foreground(green)
f.TextInput.Placeholder.Foreground(blue)
f.TextInput.Prompt.Foreground(green)
f.TextInput.Text.Foreground(blue).Bold(true)
t.Blurred.Base.BorderStyle(lipgloss.HiddenBorder())
return &t
}
And this to create a custom form:
func ConfirmForm(title, desc string, confirm *bool) *huh.Form {
return huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(title).
Description(desc).
Affirmative("Yes!").
Negative("No.").
Value(confirm),
),
).WithTheme(theme.MyCompany())
}
When I go get
huh at commit 2d49f54 this approach works perfectly fine. However, when I checkout b6644a1 my custom theme breaks and the default huh theme is used.
Expected behavior
I expected huh to use my custom theme.
Additional context
I think this happens because the huh.NewConfirm()
constructor sets the huh.ThemeCharm()
by default. So theme
is never actually nil.
Describe the bug
When leaving an input field that has been created with a validation function, that validation function is not called.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Expect to see the validation message displayed.
Additional context
See field_input.go
:
// Blur blurs the input field.
func (i *Input) Blur() tea.Cmd {
i.focused = false
i.textinput.Blur()
return nil
}
Describe the bug
When navigating through a form and currently in the first group, if the first field is skippable and the form starts with the second field due to the skipping of the first field , But toggling back does not correctly handle the skippable state of the first field .
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Toggling back to the first field in the first group should correctly handle the skippable state. If the first field was initially skipped, it should still be skippable while toggling back.
Screenshots
intial state of form: (it starts with 2nd field because 1st field is skippable):
Toggle back state of form: (bottom command gets change to commands of the skippable component):
Desktop :
Device : Laptop
OS : WIN 11
go Version : go 1.21.6 windows/amd64
It would be nice to be able to set Tea program options. Currently we don't have to way to do that since programs are instantiated during run()
.
One way would be to provide a RunWith(*tea.Program)
method or something equivalent.
My use case is the following:
I'm using Huh for a CLI application and when running under CI I don't have access to a TTY. I'd like to be able to run my tests using the WithInput
option to prevent the program from attempting to open a TTY.
Thanks
Hi, the spinner gif in the readme doesn't loop. So by the time you get to the section of the doc that has the gif, it's stuck at the last frame. The other gifs loop. I suppose this is more of a VHS question... I thought gifs recorded with VHS looped by default? In fact, I only see an option for the loop offset - nothing to disable looping. The demo.tape file that creates the spinner.gif doesn't mention looping at all. So why does the gif not loop? (Part VHS question, part bug report for the huh readme) :)
It would be really cool if this tied into an Ssh or Wish instance so i could run 'huh serve' and then provide the ip address and anyone can go fill out the survey
Is your feature request related to a problem? Please describe.
I have a select component where the number of input elements is dynamic. It can be as few as 2 or 3 elements or even a few dozen. The problem is that if I don't set a height on a select component and I have lots of elements to display, the selection starts in the middle of the list.
If I set a fixed height, this fixes my problem for big lists:
However, for short lists, I get a UI that is too big:
Describe the solution you'd like
My ideal UI would be what survey
does by default:
For huh
I would maybe imagine a .MaxHeight()
method so that the small list would still fit the contents, but the big list would be contained within the MaxHeight value.
// Select is a form select field.
type Select[T comparable] struct {
value *T
key string
viewport viewport.Model
// customization
title string
description string
options []Option[T]
filteredOptions []Option[T]
height int
+ maxHeight int
...
+ func (s *Select[T]) MaxHeight(height int) *Select[T] {
+ s.maxHeight = height
+ s.updateViewportHeight()
+ return s
+ }
func (s *Select[T]) updateViewportHeight() {
// If no height is set size the viewport to the number of options.
- if s.height <= 0 {
- s.viewport.Height = len(s.options)
- return
- }
+ if s.height <= 0 {
+ s.viewport.Height = min(len(s.options), s.maxHeight)
+ return
+ }
// Wait until the theme has appied.
if s.theme == nil {
return
}
const minHeight = 1
- s.viewport.Height = max(minHeight, s.height-
- lipgloss.Height(s.titleView())-
- lipgloss.Height(s.descriptionView()))
+ s.viewport.Height = max(minHeight, min(s.height-
+ lipgloss.Height(s.titleView())-
+ lipgloss.Height(s.descriptionView()), s.maxHeight))
}
This would result in the desired UI state:
Describe alternatives you've considered
Have the .Height()
method shrink to fit the contents by default and add a parameter to .Height()
to allow users to make it a fixed height if they want.
Additional context
Here's how I'm currently using the Select component:
// PromptSelect launches the selection UI
func PromptSelect(message string, options []string) (string, error) {
var result string
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title(message).
Height(10).
Options(huh.NewOptions(options...)...).
Value(&result),
),
).WithKeyMap(NewKeyMap())
err := form.Run()
if err != nil {
return "", ExitUserSelectionError{Err: err}
}
return result, nil
}
Is your feature request related to a problem? Please describe.
I would like to be able to stop the form from quitting after submitting. In my use case I want to use the form component to browse previously submitted forms as wel as create new responses.
Describe the solution you'd like
I'd like to be able to continue using the form after the user has submitted (or aborted it). This way I can use the form both as a way of getting the information and presenting the information.
Additional context
An small(ish) example of what I am trying to achieve can be found on here
First, thank you all for the incredible hard work and nice design of the charm.sh suite. I'm hella diggin' it!
When I attempt to change the theme of a huh Form with .WithTheme() the form style is unchanged from the default theme.
Not only colors not changing, but also any block formatting, etc.
err = huh.NewMultiSelect[*App]().
Options(options...).
Title("Select Apps to stop").
Value(&selectedApps).
WithTheme(CustomTheme()).
Run()
In the example screenshot below, I am also printing out the theme items and see changes in those elements, but no change in the actual form theme.
When running a Spinner with a quick action, any output following the spinner's lifecycle is consistently pushed to the side.
Here's a simple file that shows this weird behavior:
package main
import (
"time"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/huh/spinner"
)
func main() {
spinner.New().Title("I'm not so quick..").Action(func() {
time.Sleep(2 * time.Second)
}).Run()
huh.NewNote().Title("I'm right over here! Hit Enter to see the next spinner!").Run()
spinner.New().Title("I'm too fast!").Action(func() {
// No-op
}).Run()
huh.NewNote().Title("<- I swear there was a spinner here somewhere!").Run()
}
In this case, I've used a no-op action to showcase the behavior, but I've been able to replicate this in other projects where the action is occasionally faster than expected.
I expected this to behave similarly to its equivalent counterpart, using context:
package main
import (
"context"
"time"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/huh/spinner"
)
func main() {
ctxSlow, cancelSlow := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancelSlow()
}()
spinner.New().Title("I'm not so quick..").Context(ctxSlow).Run()
huh.NewNote().Title("I'm right over here! Hit Enter to see the next spinner!").Run()
ctxFast, cancelFast := context.WithCancel(context.Background())
go func() {
// No-op
cancelFast()
}()
spinner.New().Title("I'm too fast!").Context(ctxFast).Run()
huh.NewNote().Title("There was a fast spinner here, but at least I'm not over there ->").Run()
}
(Unrelated to this issue, but I've only been using Go for ~3 weeks, if my examples are weird or I'm misusing contexts, please let me know :) )
My example gif was recorded using vhs on an M2 Macbook Air running Sonoma 14.2.1, but I've also been able to replicate this on Windows 10 machines.
A common scenario while building CLI tools is asking more/less questions based on the answer to a previous question.
For example: While generating an application, I would ask do they want to use SQL database. If they answer yes then only I would ask more questions such as which type of database, etc.
This is similar to Inquirer.js when
feature https://github.com/SBoudrias/Inquirer.js/tree/master/packages/inquirer.
It would be a nice feature to implement a similar feature in huh.
Is your feature request related to a problem? Please describe.
We want to provide multiselects to users where some of the options are disabled.
Generally speaking we can filter our disabled options but for our use-case it would feel like missing context for our users.
It would be better if they could see the state of all their options even if specific options have been disabled from selection.
Describe the solution you'd like
It would be great if we could do:
huh.NewMultiSelect[string]().
Title("Toppings").
Options(
huh.NewOption("Lettuce", "lettuce").Selected(true),
huh.NewOption("Tomatoes", "tomatoes").Disabled(true),
...
The result would be that Tomatoes may appear gray and/or strikethrough and could not be selected.
Perhaps the arrow functions would skip over its index entirely, or selection would be blocked or trigger some visual element.
I have no hard requirements as to user experience.
Is your feature request related to a problem? Please describe.
It is not possible to implement a custom field because prevField
and nextField
are private
Describe the solution you'd like
Export prevField
and nextField
Describe alternatives you've considered
I cannot think of a way to work around this since Group requires these messages to proceed to the next field or page
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.