Allow editing multiline prompts with external text editor (#429)

- Adds a new `Preferences` struct to the config, initially only containing `Editor: bool (default false)`.
  This struct will be serialized to configs once there is a first tea induced change to the config (eg `tea login default <name>` or `tea login add`).
- Use external editor for all multiline prompts if preferred.

We already had a function for starting a texteditor for diff reviews; it does not really make sense to replace it with `survey.Editor`, as there is a big interface mismatch: survey expects strings as inputs, while our diff functions operate on files,

fixes #424

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/429
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
Norwin 2021-10-14 22:36:08 +08:00 committed by Lunny Xiao
parent 5b77345b03
commit 78a95f1ca4
7 changed files with 62 additions and 11 deletions

View File

@ -9,13 +9,15 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -54,7 +56,11 @@ func runAddComment(cmd *cli.Context) error {
body = strings.Join([]string{body, string(bodyStdin)}, "\n\n") body = strings.Join([]string{body, string(bodyStdin)}, "\n\n")
} }
} else if len(body) == 0 { } else if len(body) == 0 {
if body, err = interact.PromptMultiline("Content"); err != nil { if err = survey.AskOne(interact.NewMultiline(interact.Multiline{
Message: "Comment:",
Syntax: "md",
UseEditor: config.GetPreferences().Editor,
}), &body); err != nil {
return err return err
} }
} }

View File

@ -17,9 +17,16 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// Preferences that are stored in and read from the config file
type Preferences struct {
// Prefer using an external text editor over inline multiline prompts
Editor bool `yaml:"editor"`
}
// LocalConfig represents local configurations // LocalConfig represents local configurations
type LocalConfig struct { type LocalConfig struct {
Logins []Login `yaml:"logins"` Logins []Login `yaml:"logins"`
Prefs Preferences `yaml:"preferences"`
} }
var ( var (
@ -55,6 +62,11 @@ func GetConfigPath() string {
return configFilePath return configFilePath
} }
// GetPreferences returns preferences based on the config file
func GetPreferences() Preferences {
return config.Prefs
}
// loadConfig load config from file // loadConfig load config from file
func loadConfig() (err error) { func loadConfig() (err error) {
loadConfigOnce.Do(func() { loadConfigOnce.Do(func() {

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )

View File

@ -43,7 +43,12 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
} }
// description // description
promptD := &survey.Multiline{Message: "Issue description:", Default: o.Body} 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 = survey.AskOne(promptD, &o.Body); err != nil {
return err return err
} }

View File

@ -33,7 +33,11 @@ func CreateMilestone(login *config.Login, owner, repo string) error {
} }
// description // description
promptM := &survey.Multiline{Message: "Milestone description:"} promptM := NewMultiline(Multiline{
Message: "Milestone description:",
Syntax: "md",
UseEditor: config.GetPreferences().Editor,
})
if err := survey.AskOne(promptM, &description); err != nil { if err := survey.AskOne(promptM, &description); err != nil {
return err return err
} }

View File

@ -14,9 +14,26 @@ import (
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
) )
// PromptMultiline runs a textfield-style prompt and blocks until input was made. // Multiline represents options for a prompt that expects multiline input
func PromptMultiline(message string) (content string, err error) { type Multiline struct {
err = survey.AskOne(&survey.Multiline{Message: message}, &content) Message string
Default string
Syntax string
UseEditor 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,
}
} else {
prompt = &survey.Multiline{Message: opts.Message, Default: opts.Default}
}
return return
} }

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
@ -55,7 +56,11 @@ func ReviewPull(ctx *context.TeaContext, idx int64) error {
if (state == gitea.ReviewStateComment && len(codeComments) == 0) || state == gitea.ReviewStateRequestChanges { if (state == gitea.ReviewStateComment && len(codeComments) == 0) || state == gitea.ReviewStateRequestChanges {
promptOpts = survey.WithValidator(survey.Required) promptOpts = survey.WithValidator(survey.Required)
} }
err = survey.AskOne(&survey.Multiline{Message: "Concluding comment:"}, &comment, promptOpts) err = survey.AskOne(NewMultiline(Multiline{
Message: "Concluding comment:",
Syntax: "md",
UseEditor: config.GetPreferences().Editor,
}), &comment, promptOpts)
if err != nil { if err != nil {
return err return err
} }
@ -65,6 +70,7 @@ func ReviewPull(ctx *context.TeaContext, idx int64) error {
// DoDiffReview (1) fetches & saves diff in tempfile, (2) starts $VISUAL or $EDITOR to comment on diff, // DoDiffReview (1) fetches & saves diff in tempfile, (2) starts $VISUAL or $EDITOR to comment on diff,
// (3) parses resulting file into code comments. // (3) parses resulting file into code comments.
// It doesn't really make sense to use survey.Editor() here, as we'd read the file content at least twice.
func DoDiffReview(ctx *context.TeaContext, idx int64) ([]gitea.CreatePullReviewComment, error) { func DoDiffReview(ctx *context.TeaContext, idx int64) ([]gitea.CreatePullReviewComment, error) {
tmpFile, err := task.SavePullDiff(ctx, idx) tmpFile, err := task.SavePullDiff(ctx, idx)
if err != nil { if err != nil {