Files
gitea-tea/internal/app/discussion_search.go
T
bircni 2826726557 refactor(issue,pr): trim help, restating comments and single-use helpers
Cut issue, pr, label, milestone and comment examples to at most three, drop
doc comments that only restate the name, fold the comment and attachment
argument lookups into one closure each, and inline label helpers with one
caller.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 20:48:00 +02:00

126 lines
5.5 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package app
import (
"errors"
"fmt"
"strings"
"gitea.dev/tea/internal/clierr"
"gitea.dev/tea/internal/services"
"gitea.dev/tea/internal/terminal"
"github.com/spf13/cobra"
)
func (a *application) discussionSearchCommand(pulls bool) *cobra.Command {
noun, nouns, alias := "issue", "issues", "issues"
if pulls {
noun, nouns, alias = "pr", "pull requests", "prs"
}
var opts services.DiscussionSearchOptions
var labels []string
var updated timeRange
opts.Pulls = pulls
example := fmt.Sprintf(`$ tea search %[1]s "login error"
# Search one repository; its labels and milestones are checked
$ tea search %[1]s crash -R alice/project --label bug --milestone v1.0`, noun)
if pulls {
example += "\n$ tea search pr --review-requested"
}
command := &cobra.Command{Use: noun + " [<query>...]", Aliases: []string{alias}, Short: "Search " + nouns, Long: fmt.Sprintf(`Search the %[1]s of every repository you can access on the instance, or of one
repository with --repo. Words of the query match titles and bodies.`, nouns), Example: example, RunE: func(cmd *cobra.Command, args []string) error {
if opts.Author != "" && opts.Authored {
return clierr.Wrap(clierr.Usage, errors.New("--author and --authored are mutually exclusive"))
}
var err error
if opts.Since, opts.Before, err = updated.parse(a.deps.Now()); err != nil {
return err
}
if err := requireNames("label", "", labels); err != nil {
return err
}
milestones, err := nameFilter("milestone", opts.Milestones)
if err != nil {
return err
}
target, err := a.resolve(cmd.Context(), false)
if err != nil {
return err
}
if target.Repo != "" && opts.Owner != "" && !strings.EqualFold(strings.SplitN(target.Repo, "/", 2)[0], opts.Owner) {
return clierr.Wrap(clierr.Usage, errors.New("--owner conflicts with selected repository"))
}
if opts.Team != "" && opts.Owner == "" {
return clierr.Wrap(clierr.Usage, errors.New("--team requires --owner"))
}
if (opts.Team != "" || opts.ReviewRequested || opts.Reviewed) && target.Repo != "" {
return clierr.Wrap(clierr.Usage, errors.New("--team, --review-requested and --reviewed require instance-wide search; omit --repo or TEA_REPO"))
}
opts.Author = target.expandMe(opts.Author)
if target.Repo != "" {
if opts.Labels, opts.Milestones, err = issueFilters(cmd, target, labels, milestones); err != nil {
return err
}
} else if len(labels)+len(milestones) > 0 {
opts.Labels, opts.Milestones = strings.Join(labels, ","), strings.Join(milestones, ",")
if _, err := fmt.Fprintln(cmd.ErrOrStderr(), "warning: instance-wide search ignores --label and --milestone values that match nothing; pass --repo to check them"); err != nil {
return err
}
}
opts.Query = strings.Join(args, " ")
page, err := (services.Issues{Client: target.client, Repository: target.Repo}).SearchPage(cmd.Context(), opts)
if err != nil {
return err
}
where := "on " + strings.TrimPrefix(strings.TrimPrefix(target.Host, "https://"), "http://")
if target.Repo != "" {
where = "in " + target.Repo
}
if opts.Query != "" {
where = fmt.Sprintf("matching %q %s", opts.Query, where)
}
what := nouns
if opts.State != "all" {
what = opts.State + " " + nouns
}
return writeTable(a, cmd, page.Items, listSpec[services.SearchItem]{
Columns: []terminal.Column{{Header: "Repo#ID"}, {Header: "Title", Flex: true}, {Header: "State"}, {Header: "Author"}, {Header: "Updated"}},
Row: searchRow, Empty: "no " + what + " " + where, Context: what + " " + where,
}, servicePage(page, opts.Page))
}}
flags := command.Flags()
pageFlags(command, &opts.Limit, &opts.Page)
enumFlag(command, &opts.State, "state", "s", "open", "Filter by state", "open", "closed", "all")
flags.StringVar(&opts.Owner, "owner", "", "Search only the repositories of this `owner`")
flags.StringVar(&opts.Team, "team", "", "Search only the repositories of this organization `team` (requires --owner)")
flags.StringVarP(&opts.Author, "author", "A", "", "Filter by author; @me for yourself")
flags.StringSliceVarP(&labels, "label", "l", nil, "Filter by label `names`")
flags.StringVarP(&opts.Milestones, "milestone", "m", "", "Filter by milestone `titles`, comma-separated")
updated.register(command, "since", "Updated since a date, timestamp, or duration ago", "until", "Updated before a date, timestamp, or duration ago")
flagAliases(command, map[string]string{"before": "until"})
flags.BoolVar(&opts.Assigned, "assigned", false, "Assigned to you")
flags.BoolVar(&opts.Authored, "authored", false, "Created by you")
flags.BoolVar(&opts.Mentioned, "mentioned", false, "Mentioning you")
if pulls {
flags.BoolVar(&opts.ReviewRequested, "review-requested", false, "Requesting your review (instance-wide search)")
flags.BoolVar(&opts.Reviewed, "reviewed", false, "Reviewed by you (instance-wide search)")
}
_ = command.RegisterFlagCompletionFunc("label", a.completeLabels())
_ = command.RegisterFlagCompletionFunc("milestone", commaSeparated(a.completeMilestones()))
_ = command.RegisterFlagCompletionFunc("author", a.completeUsers())
_ = command.RegisterFlagCompletionFunc("owner", a.completeOrgs())
return command
}
func searchRow(item services.SearchItem) []terminal.Cell {
return []terminal.Cell{
{Text: fmt.Sprintf("%s#%d", item.Repository, item.Number), Link: item.URL, Color: stateColors[item.State]},
{Text: item.Title},
{Text: item.State, Color: stateColors[item.State]},
{Text: item.Author},
{Time: terminal.ParseTime(item.UpdatedAt)},
}
}