diff --git a/pkg/search/query.go b/pkg/search/query.go index 0419e359a..c6ba15019 100644 --- a/pkg/search/query.go +++ b/pkg/search/query.go @@ -16,7 +16,20 @@ const ( ) type Query struct { - Keywords []string + // Keywords holds the list of keywords to search for. These keywords are + // treated as individual components of a search query, and will get quoted + // as needed. This is useful when the input can be supplied as a list of + // search keywords. + // + // This field is mutually exclusive with KeywordsVerbatim. + Keywords []string + // KeywordsVerbatim holds the search keywords as a single string, and will + // be treated as is (e.g. no additional quoting). This is useful when the + // input is meant to be taken verbatim from the user. + // + // This field is mutually exclusive with Keywords. + KeywordsVerbatim string + Kind string Limit int Order string @@ -103,7 +116,12 @@ type Qualifiers struct { // issues, and it's called advanced issue search. func (q Query) StandardSearchString() string { qualifiers := formatQualifiers(q.Qualifiers, nil) - keywords := formatKeywords(q.Keywords) + var keywords []string + if q.KeywordsVerbatim != "" { + keywords = []string{q.KeywordsVerbatim} + } else if ks := formatKeywords(q.Keywords); len(ks) > 0 { + keywords = ks + } all := append(keywords, qualifiers...) return strings.TrimSpace(strings.Join(all, " ")) } @@ -124,10 +142,20 @@ func (q Query) StandardSearchString() string { // // The advanced syntax is documented at https://github.blog/changelog/2025-03-06-github-issues-projects-api-support-for-issues-advanced-search-and-more func (q Query) AdvancedIssueSearchString() string { - qualifiers := formatQualifiers(q.Qualifiers, formatAdvancedIssueSearch) - keywords := formatKeywords(q.Keywords) - all := append(keywords, qualifiers...) - return strings.TrimSpace(strings.Join(all, " ")) + qualifiers := strings.Join(formatQualifiers(q.Qualifiers, formatAdvancedIssueSearch), " ") + keywords := q.KeywordsVerbatim + if keywords == "" { + keywords = strings.Join(formatKeywords(q.Keywords), " ") + } + + if qualifiers != "" && keywords != "" { + // We should surround keywords with brackets to avoid leaking of any operators, especially "OR"s. + return fmt.Sprintf("( %s ) %s", keywords, qualifiers) + } + if qualifiers == "" { + return keywords + } + return qualifiers } func formatAdvancedIssueSearch(qualifier string, vs []string) (s []string, applicable bool) {