mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 01:05:26 +01:00 
			
		
		
		
	Use bubbletea instead of survey for interacting with TUI (#786)
Fix #772 Reviewed-on: https://gitea.com/gitea/tea/pulls/786 Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
This commit is contained in:
		| @@ -8,42 +8,19 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/tea/modules/theme" | ||||
| 	"code.gitea.io/tea/modules/utils" | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	"github.com/araddon/dateparse" | ||||
| 	"github.com/charmbracelet/huh" | ||||
| ) | ||||
|  | ||||
| // Multiline represents options for a prompt that expects multiline input | ||||
| type Multiline struct { | ||||
| 	Message             string | ||||
| 	Default             string | ||||
| 	Syntax              string | ||||
| 	UseEditor           bool | ||||
| 	EditorAppendDefault bool | ||||
| 	EditorHideDefault   bool | ||||
| } | ||||
|  | ||||
| // NewMultiline creates a prompt that switches between the inline multiline text | ||||
| // and a texteditor based prompt | ||||
| func NewMultiline(opts Multiline) (prompt survey.Prompt) { | ||||
| 	if opts.UseEditor { | ||||
| 		prompt = &survey.Editor{ | ||||
| 			Message:       opts.Message, | ||||
| 			Default:       opts.Default, | ||||
| 			FileName:      "*." + opts.Syntax, | ||||
| 			AppendDefault: opts.EditorAppendDefault, | ||||
| 			HideDefault:   opts.EditorHideDefault, | ||||
| 		} | ||||
| 	} else { | ||||
| 		prompt = &survey.Multiline{Message: opts.Message, Default: opts.Default} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // PromptPassword asks for a password and blocks until input was made. | ||||
| func PromptPassword(name string) (pass string, err error) { | ||||
| 	promptPW := &survey.Password{Message: name + " password:"} | ||||
| 	err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required)) | ||||
| 	err = huh.NewInput(). | ||||
| 		Title(name + " password:"). | ||||
| 		Validate(huh.ValidateNotEmpty()).EchoMode(huh.EchoModePassword). | ||||
| 		Value(&pass). | ||||
| 		WithTheme(theme.GetTheme()). | ||||
| 		Run() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| @@ -60,28 +37,21 @@ func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err e | ||||
|  | ||||
| 	owner = defaultOwner | ||||
| 	repo = defaultRepo | ||||
| 	repoSlug = defaultVal | ||||
|  | ||||
| 	err = survey.AskOne( | ||||
| 		&survey.Input{ | ||||
| 			Message: prompt, | ||||
| 			Default: defaultVal, | ||||
| 		}, | ||||
| 		&repoSlug, | ||||
| 		survey.WithValidator(func(input interface{}) error { | ||||
| 			if str, ok := input.(string); ok { | ||||
| 				if !required && len(str) == 0 { | ||||
| 					return nil | ||||
| 				} | ||||
| 				split := strings.Split(str, "/") | ||||
| 				if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 { | ||||
| 					return fmt.Errorf("must follow the <owner>/<repo> syntax") | ||||
| 				} | ||||
| 			} else { | ||||
| 				return fmt.Errorf("invalid result type") | ||||
| 	err = huh.NewInput(). | ||||
| 		Title(prompt). | ||||
| 		Value(&repoSlug). | ||||
| 		Validate(func(str string) error { | ||||
| 			if !required && len(str) == 0 { | ||||
| 				return nil | ||||
| 			} | ||||
| 			split := strings.Split(str, "/") | ||||
| 			if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 { | ||||
| 				return fmt.Errorf("must follow the <owner>/<repo> syntax") | ||||
| 			} | ||||
| 			return nil | ||||
| 		}), | ||||
| 	) | ||||
| 		}).WithTheme(theme.GetTheme()).Run() | ||||
|  | ||||
| 	if err == nil && len(repoSlug) != 0 { | ||||
| 		repoSlugSplit := strings.Split(repoSlug, "/") | ||||
| @@ -94,38 +64,39 @@ func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err e | ||||
| // promptDatetime prompts for a date or datetime string. | ||||
| // Supports all formats understood by araddon/dateparse. | ||||
| func promptDatetime(prompt string) (val *time.Time, err error) { | ||||
| 	var input string | ||||
| 	err = survey.AskOne( | ||||
| 		&survey.Input{Message: prompt}, | ||||
| 		&input, | ||||
| 		survey.WithValidator(func(input interface{}) error { | ||||
| 			if str, ok := input.(string); ok { | ||||
| 				if len(str) == 0 { | ||||
| 					return nil | ||||
| 				} | ||||
| 				t, err := dateparse.ParseAny(str) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				val = &t | ||||
| 			} else { | ||||
| 				return fmt.Errorf("invalid result type") | ||||
| 	var date string | ||||
| 	if err := huh.NewInput(). | ||||
| 		Title(prompt). | ||||
| 		Placeholder("YYYY-MM-DD"). | ||||
| 		Validate(func(s string) error { | ||||
| 			if s == "" { | ||||
| 				return nil | ||||
| 			} | ||||
| 			return nil | ||||
| 		}), | ||||
| 	) | ||||
| 	return | ||||
| 			_, err := time.Parse("2006-01-02", s) | ||||
| 			return err | ||||
| 		}). | ||||
| 		Value(&date). | ||||
| 		WithTheme(theme.GetTheme()). | ||||
| 		Run(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if date == "" { | ||||
| 		return nil, nil // no date | ||||
| 	} | ||||
| 	t, _ := time.Parse("2006-01-02", date) | ||||
| 	return &t, nil | ||||
| } | ||||
|  | ||||
| // promptSelect creates a generic multiselect prompt, with processing of custom values. | ||||
| func promptMultiSelect(prompt string, options []string, customVal string) ([]string, error) { | ||||
| 	var selection []string | ||||
| 	promptA := &survey.MultiSelect{ | ||||
| 		Message: prompt, | ||||
| 		Options: makeSelectOpts(options, customVal, ""), | ||||
| 		VimMode: true, | ||||
| 	} | ||||
| 	if err := survey.AskOne(promptA, &selection); err != nil { | ||||
| 	if err := huh.NewMultiSelect[string](). | ||||
| 		Title(prompt). | ||||
| 		Options(huh.NewOptions(makeSelectOpts(options, customVal, "")...)...). | ||||
| 		Value(&selection). | ||||
| 		WithTheme(theme.GetTheme()). | ||||
| 		Run(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return promptCustomVal(prompt, customVal, selection) | ||||
| @@ -136,14 +107,13 @@ func promptSelectV2(prompt string, options []string) (string, error) { | ||||
| 	if len(options) == 0 { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	var selection string | ||||
| 	promptA := &survey.Select{ | ||||
| 		Message: prompt, | ||||
| 		Options: options, | ||||
| 		VimMode: true, | ||||
| 		Default: options[0], | ||||
| 	} | ||||
| 	if err := survey.AskOne(promptA, &selection); err != nil { | ||||
| 	selection := options[0] | ||||
| 	if err := huh.NewSelect[string](). | ||||
| 		Title(prompt). | ||||
| 		Options(huh.NewOptions(options...)...). | ||||
| 		Value(&selection). | ||||
| 		WithTheme(theme.GetTheme()). | ||||
| 		Run(); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return selection, nil | ||||
| @@ -154,17 +124,18 @@ func promptSelect(prompt string, options []string, customVal, noneVal, defaultVa | ||||
| 	var selection string | ||||
| 	if defaultVal == "" && noneVal != "" { | ||||
| 		defaultVal = noneVal | ||||
| 	} | ||||
|  | ||||
| 	} | ||||
| 	promptA := &survey.Select{ | ||||
| 		Message: prompt, | ||||
| 		Options: makeSelectOpts(options, customVal, noneVal), | ||||
| 		VimMode: true, | ||||
| 		Default: defaultVal, | ||||
| 	} | ||||
| 	if err := survey.AskOne(promptA, &selection); err != nil { | ||||
| 	selection = defaultVal | ||||
| 	if err := huh.NewSelect[string](). | ||||
| 		Title(prompt). | ||||
| 		Options(huh.NewOptions(makeSelectOpts(options, customVal, noneVal)...)...). | ||||
| 		Value(&selection). | ||||
| 		WithTheme(theme.GetTheme()). | ||||
| 		Run(); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if noneVal != "" && selection == noneVal { | ||||
| 		return "", nil | ||||
| 	} | ||||
| @@ -193,11 +164,14 @@ func makeSelectOpts(opts []string, customVal, noneVal string) []string { | ||||
| // for custom input to add to the selection instead. | ||||
| func promptCustomVal(prompt, customVal string, selection []string) ([]string, error) { | ||||
| 	// check for custom value & prompt again with text input | ||||
| 	// HACK until https://github.com/AlecAivazis/survey/issues/339 is implemented | ||||
| 	if otherIndex := utils.IndexOf(selection, customVal); otherIndex != -1 { | ||||
| 		var customAssignees string | ||||
| 		promptA := &survey.Input{Message: prompt, Help: "comma separated list"} | ||||
| 		if err := survey.AskOne(promptA, &customAssignees); err != nil { | ||||
| 		if err := huh.NewInput(). | ||||
| 			Title(prompt). | ||||
| 			Description("comma separated list"). | ||||
| 			Value(&customAssignees). | ||||
| 			WithTheme(theme.GetTheme()). | ||||
| 			Run(); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		selection = append(selection[:otherIndex], selection[otherIndex+1:]...) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Lunny Xiao
					Lunny Xiao