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

@ -5,6 +5,7 @@ package cmd
import (
stdctx "context"
"errors"
"fmt"
"io"
"strings"
@ -14,10 +15,11 @@ import (
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/theme"
"code.gitea.io/tea/modules/utils"
"code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/huh"
"github.com/urfave/cli/v3"
)
@ -56,17 +58,22 @@ func runAddComment(_ stdctx.Context, cmd *cli.Command) error {
body = strings.Join([]string{body, string(bodyStdin)}, "\n\n")
}
} else if len(body) == 0 {
if err = survey.AskOne(interact.NewMultiline(interact.Multiline{
Message: "Comment:",
Syntax: "md",
UseEditor: config.GetPreferences().Editor,
}), &body); err != nil {
if err := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("Comment(markdown):").
ExternalEditor(config.GetPreferences().Editor).
EditorExtension("md").
Value(&body),
),
).WithTheme(theme.GetTheme()).
Run(); err != nil {
return err
}
}
if len(body) == 0 {
return fmt.Errorf("No comment body provided")
return errors.New("no comment content provided")
}
client := ctx.Login.Client()

View File

@ -30,7 +30,11 @@ func runIssuesCreate(_ stdctx.Context, cmd *cli.Command) error {
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
if ctx.NumFlags() == 0 {
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
err := interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
if err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}
opts, err := flags.GetIssuePRCreateFlags(ctx)

View File

@ -53,6 +53,9 @@ func runIssuesEdit(_ stdctx.Context, cmd *cli.Command) error {
var err error
opts, err = interact.EditIssue(*ctx, opts.Index)
if err != nil {
if interact.IsQuitting(err) {
return nil // user quit
}
return err
}
}

View File

@ -5,6 +5,7 @@ package login
import (
"context"
"fmt"
"code.gitea.io/tea/modules/auth"
"code.gitea.io/tea/modules/interact"
@ -112,7 +113,10 @@ var CmdLoginAdd = cli.Command{
func runLoginAdd(_ context.Context, cmd *cli.Command) error {
// if no args create login interactive
if cmd.NumFlags() == 0 {
return interact.CreateLogin()
if err := interact.CreateLogin(); err != nil && !interact.IsQuitting(err) {
return fmt.Errorf("error adding login: %w", err)
}
return nil
}
// if OAuth flag is provided, use OAuth2 PKCE flow

View File

@ -68,7 +68,10 @@ func runMilestonesCreate(_ stdctx.Context, cmd *cli.Command) error {
}
if ctx.NumFlags() == 0 {
return interact.CreateMilestone(ctx.Login, ctx.Owner, ctx.Repo)
if err := interact.CreateMilestone(ctx.Login, ctx.Owner, ctx.Repo); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}
return task.CreateMilestone(

View File

@ -47,5 +47,8 @@ func runPullsCheckout(_ stdctx.Context, cmd *cli.Command) error {
return err
}
return task.PullCheckout(ctx.Login, ctx.Owner, ctx.Repo, ctx.Bool("branch"), idx, interact.PromptPassword)
if err := task.PullCheckout(ctx.Login, ctx.Owner, ctx.Repo, ctx.Bool("branch"), idx, interact.PromptPassword); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}

View File

@ -43,5 +43,8 @@ func runPullsClean(_ stdctx.Context, cmd *cli.Command) error {
return err
}
return task.PullClean(ctx.Login, ctx.Owner, ctx.Repo, idx, ctx.Bool("ignore-sha"), interact.PromptPassword)
if err := task.PullClean(ctx.Login, ctx.Owner, ctx.Repo, idx, ctx.Bool("ignore-sha"), interact.PromptPassword); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}

View File

@ -44,7 +44,10 @@ func runPullsCreate(_ stdctx.Context, cmd *cli.Command) error {
// no args -> interactive mode
if ctx.NumFlags() == 0 {
return interact.CreatePull(ctx)
if err := interact.CreatePull(ctx); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}
// else use args to create PR

View File

@ -46,7 +46,10 @@ var CmdPullsMerge = cli.Command{
if ctx.Args().Len() != 1 {
// If no PR index is provided, try interactive mode
return interact.MergePull(ctx)
if err := interact.MergePull(ctx); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}
idx, err := utils.ArgToIndex(ctx.Args().First())

View File

@ -26,7 +26,7 @@ var CmdPullsReview = cli.Command{
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
if ctx.Args().Len() != 1 {
return fmt.Errorf("Must specify a PR index")
return fmt.Errorf("must specify a PR index")
}
idx, err := utils.ArgToIndex(ctx.Args().First())
@ -34,7 +34,10 @@ var CmdPullsReview = cli.Command{
return err
}
return interact.ReviewPull(ctx, idx)
if err := interact.ReviewPull(ctx, idx); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
},
Flags: flags.AllDefaultFlags,
}

View File

@ -10,7 +10,7 @@ import (
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/huh"
"github.com/urfave/cli/v3"
)
@ -53,7 +53,6 @@ func runRepoDelete(_ stdctx.Context, cmd *cli.Command) error {
var owner string
if ctx.IsSet("owner") {
owner = ctx.String("owner")
} else {
owner = ctx.Login.User
}
@ -64,15 +63,16 @@ func runRepoDelete(_ stdctx.Context, cmd *cli.Command) error {
if !ctx.Bool("force") {
var enteredRepoSlug string
promptRepoName := &survey.Input{
Message: fmt.Sprintf("Confirm the deletion of the repository '%s' by typing its name: ", repoSlug),
}
if err := survey.AskOne(promptRepoName, &enteredRepoSlug, survey.WithValidator(survey.Required)); err != nil {
if err := huh.NewInput().
Title(fmt.Sprintf("Confirm the deletion of the repository '%s' by typing its name: ", repoSlug)).
Validate(huh.ValidateNotEmpty()).
Value(&enteredRepoSlug).
Run(); err != nil {
return err
}
if enteredRepoSlug != repoSlug {
return fmt.Errorf("Entered wrong repository name '%s', expected '%s'", enteredRepoSlug, repoSlug)
return fmt.Errorf("entered wrong repository name '%s', expected '%s'", enteredRepoSlug, repoSlug)
}
}