mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
109 lines
3.7 KiB
Go
109 lines
3.7 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package terminal
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"charm.land/huh/v2"
|
|
)
|
|
|
|
// ReviewCommentDraft is an inline comment collected during interactive review.
|
|
type ReviewCommentDraft struct {
|
|
Path, Body string
|
|
OldLine, NewLine int64
|
|
}
|
|
|
|
// ReviewFormChoices describes the pull request under review and how its review is collected.
|
|
type ReviewFormChoices struct {
|
|
// Header introduces the form, such as `Reviewing #1 "Add one.txt" (feat/one → main) in alice/x`.
|
|
Header string
|
|
// Author leaves out approving and requesting changes, which Gitea refuses on one's own pull request.
|
|
Author bool
|
|
// Body seeds the review body.
|
|
Body string
|
|
// Editor is the configured editor command that ctrl+e opens on the body; empty disables it.
|
|
Editor string
|
|
// ReviewDiff, when non-nil, offers to annotate the diff in the editor after the body.
|
|
ReviewDiff func(context.Context) ([]ReviewCommentDraft, error)
|
|
}
|
|
|
|
// ReviewFormValues holds an interactively collected review: its API state, body and inline comments.
|
|
type ReviewFormValues struct {
|
|
Event string
|
|
Body string
|
|
Comments []ReviewCommentDraft
|
|
}
|
|
|
|
// ReviewForm collects interactive pull review input.
|
|
type ReviewForm interface {
|
|
CreateReview(context.Context, ReviewFormChoices) (ReviewFormValues, error)
|
|
}
|
|
|
|
// errEmptyComment rejects a comment review with neither a body nor inline comments.
|
|
var errEmptyComment = errors.New("a comment review needs a body or inline comments")
|
|
|
|
// CreateReview asks for the review type, then the body, then whether to annotate the diff, on stderr.
|
|
func (p *FormPrompter) CreateReview(ctx context.Context, choices ReviewFormChoices) (ReviewFormValues, error) {
|
|
if choices.Header != "" {
|
|
fmt.Fprintln(p.Streams.ErrOut, SafeLine(choices.Header))
|
|
}
|
|
options := []huh.Option[string]{huh.NewOption("Comment", "COMMENT")}
|
|
if !choices.Author {
|
|
options = append([]huh.Option[string]{huh.NewOption("Approve", "APPROVED"), huh.NewOption("Request changes", "REQUEST_CHANGES")}, options...)
|
|
}
|
|
values := ReviewFormValues{Event: options[0].Value, Body: choices.Body}
|
|
if err := p.runForm(ctx, huh.NewForm(huh.NewGroup(
|
|
huh.NewSelect[string]().Title("Review type:").Options(options...).Value(&values.Event),
|
|
))); err != nil {
|
|
return values, err
|
|
}
|
|
|
|
// A comment can consist of inline comments alone, which are collected after the body.
|
|
required := values.Event == "REQUEST_CHANGES" || (values.Event == "COMMENT" && choices.ReviewDiff == nil)
|
|
title := "Body (markdown, optional):"
|
|
if required {
|
|
title = "Body (markdown, required):"
|
|
}
|
|
args, editor := commandArgs(choices.Editor)
|
|
if editor {
|
|
title = strings.TrimSuffix(title, "):") + ", ctrl+e opens your editor):"
|
|
}
|
|
body := bodyField(title, &values.Body)
|
|
if editor {
|
|
body = body.ExternalEditor(true).Editor(args...).EditorExtension("md")
|
|
}
|
|
if required {
|
|
body = body.Validate(notBlank)
|
|
}
|
|
if err := p.runForm(ctx, huh.NewForm(huh.NewGroup(body))); err != nil {
|
|
return values, err
|
|
}
|
|
values.Body = strings.TrimSpace(values.Body)
|
|
|
|
if choices.ReviewDiff != nil {
|
|
annotate := false
|
|
if err := p.runForm(ctx, huh.NewForm(huh.NewGroup(
|
|
huh.NewConfirm().Title("Annotate the diff in your editor?").Value(&annotate),
|
|
))); err != nil {
|
|
return values, err
|
|
}
|
|
if annotate {
|
|
comments, err := choices.ReviewDiff(ctx)
|
|
if err != nil {
|
|
return values, fmt.Errorf("diff review failed: %w", err)
|
|
}
|
|
values.Comments = comments
|
|
fmt.Fprintf(p.Streams.ErrOut, "Found %d inline comments in the annotated diff\n", len(comments))
|
|
}
|
|
}
|
|
if values.Event == "COMMENT" && values.Body == "" && len(values.Comments) == 0 {
|
|
return values, errEmptyComment
|
|
}
|
|
return values, nil
|
|
}
|