GithubHelp home page GithubHelp logo

huh's People

Contributors

anirudhacodes avatar ardnew avatar auvred avatar bstncartwright avatar caarlos0 avatar ddddddo avatar dependabot[bot] avatar jolheiser avatar kevm avatar maaslalani avatar meowgorithm avatar popey avatar rharshit82 avatar sgoudham avatar stefanlogue avatar theredditbandit avatar vitor-mariano avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

huh's Issues

Go on with preselection after timeout/countdown without user interaction

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

Describe alternatives you've considered

Additional context

Spinner gif in the readme doesn't loop

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) :)

Add the possibility to prefill the answer

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

Dynamic Forms

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

Input & Text ignoring any input

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.

Screenshots
Screenshot 2024-02-19 at 3 05 59 PM

Desktop (please complete the following information):

  • OS: MacOS

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. 😅

Filter options inside a select field based in the previous steps data

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 😄

GetValue does not work for Text inputs

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!

`Bug`: MultiSelect accessible mode allows selecting more options than the specified limit

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:

  1. Go to burger example of 'huh\examples\burger'
  2. Run the form with WithAccessible(true)
  3. Try to select more option than specified limit in multiselect field.
  4. The accessible mode allows you to select more options than the specified limit.

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.
image

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.

Serve Form

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

Keep a form active after submitting.

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

Standalone panic on Select when setting WithAccessible(), and then calling Run()

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

Line 290:

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):

  • Version v0.2.3

Standalone Input panic when calling WithWidth

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):

  • Version: v0.2.3

Additional context
Add any other context about the problem here.

Awkward Spinner Behavior for Fast Actions

Describe the bug

When running a Spinner with a quick action, any output following the spinner's lifecycle is consistently pushed to the side.

To Reproduce

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()
}

out

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.

Expected behavior

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()
}

out

(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 :) )

Environment

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.

Add Disabled State to Options

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.

Make Group an interface

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.

The `Key` in the `NewSelect[string]()` field is not set

Describe the bug
The Key in the NewSelect[string]() field is not set.

To Reproduce
Steps to reproduce the behavior:

  1. Take code from the example;
  2. Place into your IDE;
  3. Add the following:
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)
}
  1. See empty string in the 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):

  • OS: 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
  • Browser: Chrome latest
  • Version: huh v0.2.3

Smartphone (please complete the following information):
No need.

Additional context
No.

Predefined Validation Functions

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)
  • etc...
huh.NewInput().Title("What's your name?").Validate(huh.ValidateLength(3, 50)

Long multi-select cuts off items

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

Screenshot 2023-12-18 at 09 41 59

Desktop (please complete the following information):

  • OSX, verson: v0.2.1

Export theme.copy

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.

WithTheme doesn't seem to change the theme

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.
Screenshot 2024-01-14 at 11 08 27 PM

No wrapping on answer exceeding the terminal length

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

Enregistrement.de.l.ecran.2023-12-14.a.10.34.36.mov

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):

  • OS: macOS iTerm2 and Windows Powershell

Additional context
This may be an intended behavior but it seems not really user friendly

Cannot override `Text` `KeyMap`

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.

b6644a1 breaks custom theming

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.

GetValue return type

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

huh/field_select.go

Lines 542 to 545 in 71f31c1

// GetValue returns the value of the field.
func (s *Select[T]) GetValue() any {
return *s.value
}

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 )

Request: Hidden text input

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.

Implementing a custom field

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

Handling Overlapping Styles in `render` Function in field_note

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.

example:

Code

huh.NewNote().
	Title("").
	Description("*Jon, _Welcome to_ Joncafe*, How may we take your *order*?")

Current Behaviour:
image

In above image, you can see that the "JonCafe" should be bold but it is not.

expected behaviour:
image

To Reproduce
Steps to reproduce the behavior:

  1. create a NewNote field in a form with overlapping styles as shown in example above
  2. Then run the form

Desktop (please complete the following information):

  • Device : Laptop
  • OS : WIN 11
  • go Version : go 1.21.6 windows/amd64

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.

Allow for inline confirmations

Just like Inline(true) for inputs. We should allow Inline(true) for Confirm fields to allow for more compact layouts.

Set Tea program options

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

Additional Field Types

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:

  • checkbox
  • integer/float/string/bool lists
  • date
  • file/multi-file select
  • key value entry

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

Tab Complete for Input Fields

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!

Input Suggestions

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)

demo

Thank you for consideration.

Multi-select filtering

[Question] How to implement multi-select filter

The test found that select filter is supported, but multi-select filter is not supported

Don’t clear the prompt once confirmed

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.

feat: Progress bar in form

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.

vid.mp4

Assign this to me. (but need better design of the progress bar)

Bug: If Skippable First field is in first group of form then it is not getting Skipped on Toggling Back

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:

  1. Go to create form where 1st field is skippable but 2nd field is not skippable.
  2. The form will open with current field as 2nd field (expected behavior).
  3. Now toggle back (using shift + tab), you will see that the bottom helper command shows command for skippable field.

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):
image
Toggle back state of form: (bottom command gets change to commands of the skippable component):
image

Desktop :

Device : Laptop
OS : WIN 11
go Version : go 1.21.6 windows/amd64

feat: Set max height on a Select component

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.
Screenshot 2024-01-04 at 1 06 16 PM

If I set a fixed height, this fixes my problem for big lists:
Screenshot 2024-01-04 at 1 05 29 PM

However, for short lists, I get a UI that is too big:
Screenshot 2024-01-04 at 1 05 09 PM

Describe the solution you'd like
My ideal UI would be what survey does by default:
Screenshot 2024-01-04 at 1 06 25 PM
Screenshot 2024-01-04 at 1 06 40 PM

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:
Screenshot 2024-01-04 at 1 05 29 PM
Screenshot 2024-01-04 at 1 06 01 PM

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
}

Reduce binary size

Is your feature request related to a problem? Please describe.
While moving from survey to huh, I noticed a significant increase of my binary.

  • Before: 6.72 MB
  • After. 9.08 MB

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

Conditional Prompt based on answers to the previous questions

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.

Skippable notes

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.

Support textinput.EchoNone in huh.NewInput().Password()

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

Add Table layout for Multiselect/Select

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

  • fixed heading
  • scrollable
  • filterable

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.

image

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!

Add a default selection to `Select`

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.

Add search for select

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.

Input fields are not validating on Blur()

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:

  1. Create a form with an input field and validation function
  2. Run the form
  3. Enter invalid data into the input field
  4. Leave the input field

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
}

Shift-Tab Not Working on Windows

It does now appear to be possible to go back in a form using shift-tab on Windows (using Windows Terminal).

To Reproduce

  1. Run the BubbleTea example burger program.
  2. Try to shift-tab back through the form.

Expected behavior
Moving back to the previous form input.

Desktop:

  • OS: Windows 11
  • Windows Terminal
  • Go 1.22

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.