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:
Lunny Xiao
2025-08-11 18:23:52 +00:00
parent c0eb30af03
commit 4c00b8b571
27 changed files with 553 additions and 318 deletions

View File

@ -4,19 +4,28 @@
package interact
import (
"strings"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/task"
"code.gitea.io/tea/modules/theme"
"github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/huh"
)
// IsQuitting checks if the user has aborted the interactive prompt
func IsQuitting(err error) bool {
return err == huh.ErrUserAborted
}
// CreateIssue interactively creates an issue
func CreateIssue(login *config.Login, owner, repo string) error {
owner, repo, err := promptRepoSlug(owner, repo)
if err != nil {
return err
}
printTitleAndContent("Target repo:", owner+"/"+repo)
var opts gitea.CreateIssueOption
if err := promptIssueProperties(login, owner, repo, &opts); err != nil {
@ -28,29 +37,36 @@ func CreateIssue(login *config.Login, owner, repo string) error {
func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.CreateIssueOption) error {
var milestoneName string
var labels []string
var err error
selectableChan := make(chan (issueSelectables), 1)
go fetchIssueSelectables(login, owner, repo, selectableChan)
// title
promptOpts := survey.WithValidator(survey.Required)
promptI := &survey.Input{Message: "Issue title:", Default: o.Title}
if err = survey.AskOne(promptI, &o.Title, promptOpts); err != nil {
if err := huh.NewInput().
Title("Issue title:").
Value(&o.Title).
Validate(huh.ValidateNotEmpty()).
WithTheme(theme.GetTheme()).
Run(); err != nil {
return err
}
printTitleAndContent("Issue title:", o.Title)
// description
promptD := NewMultiline(Multiline{
Message: "Issue description:",
Default: o.Body,
Syntax: "md",
UseEditor: config.GetPreferences().Editor,
})
if err = survey.AskOne(promptD, &o.Body); err != nil {
if err := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("Issue description(markdown):").
ExternalEditor(config.GetPreferences().Editor).
EditorExtension("md").
Value(&o.Body),
),
).WithTheme(theme.GetTheme()).
Run(); err != nil {
return err
}
printTitleAndContent("Issue description(markdown):", o.Body)
// wait until selectables are fetched
selectables := <-selectableChan
@ -67,6 +83,7 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
if o.Assignees, err = promptMultiSelect("Assignees:", selectables.Assignees, "[other]"); err != nil {
return err
}
printTitleAndContent("Assignees:", strings.Join(o.Assignees, "\n"))
// milestone
if len(selectables.MilestoneList) != 0 {
@ -74,24 +91,40 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
return err
}
o.Milestone = selectables.MilestoneMap[milestoneName]
printTitleAndContent("Milestone:", milestoneName)
}
// labels
if len(selectables.LabelList) != 0 {
promptL := &survey.MultiSelect{Message: "Labels:", Options: selectables.LabelList, VimMode: true, Default: o.Labels}
if err := survey.AskOne(promptL, &labels); err != nil {
options := make([]huh.Option[int64], 0, len(selectables.LabelList))
labelsMap := make(map[int64]string, len(selectables.LabelList))
for _, l := range selectables.LabelList {
options = append(options, huh.Option[int64]{Key: l, Value: selectables.LabelMap[l]})
labelsMap[selectables.LabelMap[l]] = l
}
if err := huh.NewMultiSelect[int64]().
Title("Labels:").
Options(options...).
Value(&o.Labels).
Run(); err != nil {
return err
}
o.Labels = make([]int64, len(labels))
for i, l := range labels {
o.Labels[i] = selectables.LabelMap[l]
var labels []string
for _, labelID := range o.Labels {
labels = append(labels, labelsMap[labelID])
}
printTitleAndContent("Labels:", strings.Join(labels, "\n"))
}
// deadline
if o.Deadline, err = promptDatetime("Due date:"); err != nil {
return err
}
deadlineStr := "No due date"
if o.Deadline != nil && !o.Deadline.IsZero() {
deadlineStr = o.Deadline.Format("2006-01-02")
}
printTitleAndContent("Due date:", deadlineStr)
return nil
}