diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index 2085251f1c..a17e7192ac 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "html/template" + "slices" "strings" "forgejo.org/modules/highlight" @@ -46,6 +47,19 @@ const ( SearchModeFuzzy = internal.CodeSearchModeFuzzy ) +type Results []*Result + +// Get the set of repo IDs from a list of search results +func (res Results) RepoIDs() []int64 { + ids := make([]int64, len(res)) + for _, r := range res { + if !slices.Contains(ids, r.RepoID) { + ids = append(ids, r.RepoID) + } + } + return ids +} + func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) { startIndex := selectionStartIndex numLinesBefore := 0 @@ -218,7 +232,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res } // PerformSearch perform a search on a repository -func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, []*SearchResultLanguages, error) { +func PerformSearch(ctx context.Context, opts *SearchOptions) (int, Results, []*SearchResultLanguages, error) { if opts == nil || len(opts.Keyword) == 0 { return 0, nil, nil, nil } diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 82b889f875..95602d0dd0 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -233,21 +233,19 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } var keywords []string - if len(options.Tokens) != 0 { - for _, token := range options.Tokens { - if !token.Fuzzy { - // to make it a phrase search, we have to quote the keyword(s) - // https://www.meilisearch.com/docs/reference/api/search#phrase-search - token.Term = doubleQuoteKeyword(token.Term) - } - - // internal.BoolOptShould (Default, requires no modifications) - // internal.BoolOptMust (Not supported by meilisearch) - if token.Kind == internal.BoolOptNot { - token.Term = "-" + token.Term - } - keywords = append(keywords, token.Term) + for _, token := range options.Tokens { + if !token.Fuzzy { + // to make it a phrase search, we have to quote the keyword(s) + // https://www.meilisearch.com/docs/reference/api/search#phrase-search + token.Term = doubleQuoteKeyword(token.Term) } + + // internal.BoolOptShould (Default, requires no modifications) + // internal.BoolOptMust (Not supported by meilisearch) + if token.Kind == internal.BoolOptNot { + token.Term = "-" + token.Term + } + keywords = append(keywords, token.Term) } searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()). diff --git a/routers/common/search.go b/routers/common/search.go new file mode 100644 index 0000000000..40189baee1 --- /dev/null +++ b/routers/common/search.go @@ -0,0 +1,55 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + code_indexer "forgejo.org/modules/indexer/code" + "forgejo.org/modules/setting" + "forgejo.org/services/context" +) + +type CodeSearchOptions struct { + Language, Keyword, Path string +} + +// Parses the common code search options from the context +// This functions takes care of the following ctx.Data fields +// - Keyword +// - Language +// - CodeSearchPath +func InitCodeSearchOptions(ctx *context.Context) (opts CodeSearchOptions) { + opts.Language = ctx.FormTrim("l") + opts.Keyword = ctx.FormTrim("q") + opts.Path = ctx.FormTrim("path") + + ctx.Data["Keyword"] = opts.Keyword + ctx.Data["Language"] = opts.Language + ctx.Data["CodeSearchPath"] = opts.Path + + return opts +} + +// Returns the indexer mode to be used by the code indexer +// Also sets the ctx.Data fields "CodeSearchMode" and "CodeSearchOptions" +// +// NOTE: +// This is seperate from `InitCodeSearchOptions` +// since this is specific the indexer and only used +// where git-grep is not available. +func CodeSearchIndexerMode(ctx *context.Context) (mode code_indexer.SearchMode) { + mode = code_indexer.SearchModeExact + if m := ctx.FormTrim("mode"); m == "union" { + mode = code_indexer.SearchModeUnion + } else if m == "fuzzy" || ctx.FormBool("fuzzy") { + if setting.Indexer.RepoIndexerEnableFuzzy { + mode = code_indexer.SearchModeFuzzy + } else { + mode = code_indexer.SearchModeUnion + } + } + ctx.Data["CodeSearchOptions"] = code_indexer.CodeSearchOptions + ctx.Data["CodeSearchMode"] = mode.String() + + return mode +} diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 2e11f70585..d9ff05cfce 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -1,4 +1,5 @@ // Copyright 2021 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package explore @@ -11,6 +12,7 @@ import ( "forgejo.org/modules/base" code_indexer "forgejo.org/modules/indexer/code" "forgejo.org/modules/setting" + "forgejo.org/routers/common" "forgejo.org/services/context" ) @@ -32,29 +34,12 @@ func Code(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExploreCode"] = true - - language := ctx.FormTrim("l") - keyword := ctx.FormTrim("q") - path := ctx.FormTrim("path") - - mode := code_indexer.SearchModeExact - if m := ctx.FormTrim("mode"); m == "union" { - mode = code_indexer.SearchModeUnion - } else if m == "fuzzy" || ctx.FormBool("fuzzy") { - if setting.Indexer.RepoIndexerEnableFuzzy { - mode = code_indexer.SearchModeFuzzy - } else { - mode = code_indexer.SearchModeUnion - } - } - - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["CodeSearchOptions"] = code_indexer.CodeSearchOptions - ctx.Data["CodeSearchMode"] = mode.String() ctx.Data["PageIsViewCode"] = true - if keyword == "" { + opts := common.InitCodeSearchOptions(ctx) + mode := common.CodeSearchIndexerMode(ctx) + + if opts.Keyword == "" { ctx.HTML(http.StatusOK, tplExploreCode) return } @@ -84,17 +69,17 @@ func Code(ctx *context.Context) { var ( total int - searchResults []*code_indexer.Result + searchResults code_indexer.Results searchResultLanguages []*code_indexer.SearchResultLanguages ) if (len(repoIDs) > 0) || isAdmin { total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: repoIDs, - Keyword: keyword, + Keyword: opts.Keyword, Mode: mode, - Language: language, - Filename: path, + Language: opts.Language, + Filename: opts.Path, Paginator: &db.ListOptions{ Page: page, PageSize: setting.UI.RepoSearchPagingNum, @@ -110,20 +95,7 @@ func Code(ctx *context.Context) { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } - loadRepoIDs := make([]int64, 0, len(searchResults)) - for _, result := range searchResults { - var find bool - for _, id := range loadRepoIDs { - if id == result.RepoID { - find = true - break - } - } - if !find { - loadRepoIDs = append(loadRepoIDs, result.RepoID) - } - } - + loadRepoIDs := searchResults.RepoIDs() repoMaps, err := repo_model.GetRepositoriesMapByIDs(ctx, loadRepoIDs) if err != nil { ctx.ServerError("GetRepositoriesMapByIDs", err) diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index c3b4d07fa0..03da55a9ec 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -1,4 +1,5 @@ // Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package repo @@ -12,6 +13,7 @@ import ( "forgejo.org/modules/git" code_indexer "forgejo.org/modules/indexer/code" "forgejo.org/modules/setting" + "forgejo.org/routers/common" "forgejo.org/services/context" ) @@ -62,10 +64,7 @@ func (m searchMode) ToGitGrep() git.GrepMode { // Search render repository search page func Search(ctx *context.Context) { - language := ctx.FormTrim("l") - keyword := ctx.FormTrim("q") - - path := ctx.FormTrim("path") + opts := common.InitCodeSearchOptions(ctx) mode := ExactSearchMode if modeStr := ctx.FormString("mode"); len(modeStr) > 0 { mode = searchModeFromString(modeStr) @@ -73,9 +72,6 @@ func Search(ctx *context.Context) { mode = UnionSearchMode } - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["CodeSearchPath"] = path ctx.Data["PageIsViewCode"] = true ctx.Data["CodeIndexerDisabled"] = !setting.Indexer.RepoIndexerEnabled if setting.Indexer.RepoIndexerEnabled { @@ -84,7 +80,7 @@ func Search(ctx *context.Context) { ctx.Data["CodeSearchOptions"] = git.GrepSearchOptions } - if keyword == "" { + if opts.Keyword == "" { ctx.HTML(http.StatusOK, tplSearch) return } @@ -104,10 +100,10 @@ func Search(ctx *context.Context) { var err error total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: []int64{ctx.Repo.Repository.ID}, - Keyword: keyword, + Keyword: opts.Keyword, Mode: m, - Language: language, - Filename: path, + Language: opts.Language, + Filename: opts.Path, Paginator: &db.ListOptions{ Page: page, PageSize: setting.UI.RepoSearchPagingNum, @@ -126,10 +122,10 @@ func Search(ctx *context.Context) { m := mode.ToGitGrep() ctx.Data["CodeSearchMode"] = m.String() - res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ + res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, opts.Keyword, git.GrepOptions{ ContextLineNumber: 1, RefName: ctx.Repo.RefName, - Filename: path, + Filename: opts.Path, Mode: m, }) if err != nil { diff --git a/routers/web/user/code.go b/routers/web/user/code.go index 5c69d72d51..a76ac62d03 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -1,4 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package user @@ -11,6 +12,7 @@ import ( "forgejo.org/modules/base" code_indexer "forgejo.org/modules/indexer/code" "forgejo.org/modules/setting" + "forgejo.org/routers/common" shared_user "forgejo.org/routers/web/shared/user" "forgejo.org/services/context" ) @@ -35,30 +37,13 @@ func CodeSearch(ctx *context.Context) { ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled + ctx.Data["IsCodePage"] = true ctx.Data["Title"] = ctx.Tr("explore.code") - language := ctx.FormTrim("l") - keyword := ctx.FormTrim("q") - path := ctx.FormTrim("path") + opts := common.InitCodeSearchOptions(ctx) + mode := common.CodeSearchIndexerMode(ctx) - mode := code_indexer.SearchModeExact - if m := ctx.FormTrim("mode"); m == "union" { - mode = code_indexer.SearchModeUnion - } else if m == "fuzzy" || ctx.FormBool("fuzzy") { - if setting.Indexer.RepoIndexerEnableFuzzy { - mode = code_indexer.SearchModeFuzzy - } else { - mode = code_indexer.SearchModeUnion - } - } - - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["CodeSearchOptions"] = code_indexer.CodeSearchOptions - ctx.Data["CodeSearchMode"] = mode.String() - ctx.Data["IsCodePage"] = true - - if keyword == "" { + if opts.Keyword == "" { ctx.HTML(http.StatusOK, tplUserCode) return } @@ -81,17 +66,17 @@ func CodeSearch(ctx *context.Context) { var ( total int - searchResults []*code_indexer.Result + searchResults code_indexer.Results searchResultLanguages []*code_indexer.SearchResultLanguages ) if len(repoIDs) > 0 { total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: repoIDs, - Keyword: keyword, + Keyword: opts.Keyword, Mode: mode, - Language: language, - Filename: path, + Language: opts.Language, + Filename: opts.Path, Paginator: &db.ListOptions{ Page: page, PageSize: setting.UI.RepoSearchPagingNum, @@ -107,20 +92,7 @@ func CodeSearch(ctx *context.Context) { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } - loadRepoIDs := make([]int64, 0, len(searchResults)) - for _, result := range searchResults { - var find bool - for _, id := range loadRepoIDs { - if id == result.RepoID { - find = true - break - } - } - if !find { - loadRepoIDs = append(loadRepoIDs, result.RepoID) - } - } - + loadRepoIDs := searchResults.RepoIDs() repoMaps, err := repo_model.GetRepositoriesMapByIDs(ctx, loadRepoIDs) if err != nil { ctx.ServerError("GetRepositoriesMapByIDs", err)