From 6d6116857ca4d8167d2d4e34f65d96bc23cf6bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Piliszek?= Date: Mon, 12 Aug 2024 20:57:42 +0200 Subject: [PATCH 1/9] git-grep: support regexp --- modules/git/grep.go | 19 +++++++++++-- modules/git/grep_test.go | 31 +++++++++++++++++++++ routers/web/repo/search.go | 57 ++++++++++++++++++++++++++++++++++---- services/wiki/wiki.go | 2 +- 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/modules/git/grep.go b/modules/git/grep.go index 5572bd994f..1e34f0275c 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -27,12 +27,20 @@ type GrepResult struct { HighlightedRanges [][3]int } +type grepMode int + +const ( + FixedGrepMode grepMode = iota + FixedAnyGrepMode + RegExpGrepMode +) + type GrepOptions struct { RefName string MaxResultLimit int MatchesPerFile int ContextLineNumber int - IsFuzzy bool + Mode grepMode PathSpec []setting.Glob } @@ -75,11 +83,16 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO // -I skips binary files cmd := NewCommand(ctx, "grep", "-I", "--null", "--break", "--heading", "--column", - "--fixed-strings", "--line-number", "--ignore-case", "--full-name") + "--line-number", "--ignore-case", "--full-name") + if opts.Mode == RegExpGrepMode { + cmd.AddArguments("--perl-regexp") + } else { + cmd.AddArguments("--fixed-strings") + } cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile)) words := []string{search} - if opts.IsFuzzy { + if opts.Mode == FixedAnyGrepMode { words = strings.Fields(search) } for _, word := range words { diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go index 3ba7a6efcb..835f441b19 100644 --- a/modules/git/grep_test.go +++ b/modules/git/grep_test.go @@ -201,3 +201,34 @@ func TestGrepRefs(t *testing.T) { assert.Len(t, res, 1) assert.Equal(t, "A", res[0].LineCodes[0]) } + +func TestGrepCanHazRegexOnDemand(t *testing.T) { + tmpDir := t.TempDir() + + err := InitRepository(DefaultContext, tmpDir, false, Sha1ObjectFormat.Name()) + require.NoError(t, err) + + gitRepo, err := openRepositoryWithDefaultContext(tmpDir) + require.NoError(t, err) + defer gitRepo.Close() + + require.NoError(t, os.WriteFile(path.Join(tmpDir, "matching"), []byte("It's a match!"), 0o666)) + require.NoError(t, os.WriteFile(path.Join(tmpDir, "not-matching"), []byte("Orisitamatch?"), 0o666)) + + err = AddChanges(tmpDir, true) + require.NoError(t, err) + + err = CommitChanges(tmpDir, CommitChangesOptions{Message: "Add fixtures for regexp test"}) + require.NoError(t, err) + + // should find nothing by default... + res, err := GrepSearch(context.Background(), gitRepo, "\\bmatch\\b", GrepOptions{}) + require.NoError(t, err) + assert.Empty(t, res) + + // ... unless configured explicitly + res, err = GrepSearch(context.Background(), gitRepo, "\\bmatch\\b", GrepOptions{Mode: RegExpGrepMode}) + require.NoError(t, err) + assert.Len(t, res, 1) + assert.Equal(t, "matching", res[0].Filename) +} diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index c4f9f9afd1..9a033c0f1b 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -17,16 +17,55 @@ import ( const tplSearch base.TplName = "repo/search" +type searchMode int + +const ( + ExactSearchMode searchMode = iota + FuzzySearchMode + RegExpSearchMode +) + +func searchModeFromString(s string) searchMode { + switch s { + case "fuzzy": + return FuzzySearchMode + case "regexp": + return RegExpSearchMode + default: + return ExactSearchMode + } +} + +func (m searchMode) String() string { + switch m { + case ExactSearchMode: + return "exact" + case FuzzySearchMode: + return "fuzzy" + case RegExpSearchMode: + return "regexp" + default: + panic("cannot happen") + } +} + // Search render repository search page func Search(ctx *context.Context) { language := ctx.FormTrim("l") keyword := ctx.FormTrim("q") - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) + mode := ExactSearchMode + if modeStr := ctx.FormString("mode"); len(modeStr) > 0 { + mode = searchModeFromString(modeStr) + } else if ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) { // for backward compatibility in links + mode = FuzzySearchMode + } ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language - ctx.Data["IsFuzzy"] = isFuzzy + ctx.Data["IsFuzzy"] = mode == FuzzySearchMode + ctx.Data["IsRegExp"] = mode == RegExpSearchMode + ctx.Data["SearchMode"] = mode.String() ctx.Data["PageIsViewCode"] = true if keyword == "" { @@ -47,7 +86,7 @@ func Search(ctx *context.Context) { total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: []int64{ctx.Repo.Repository.ID}, Keyword: keyword, - IsKeywordFuzzy: isFuzzy, + IsKeywordFuzzy: mode == FuzzySearchMode, Language: language, Paginator: &db.ListOptions{ Page: page, @@ -64,11 +103,17 @@ func Search(ctx *context.Context) { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } } else { - res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ + grepOpt := git.GrepOptions{ ContextLineNumber: 1, - IsFuzzy: isFuzzy, RefName: ctx.Repo.RefName, - }) + } + switch mode { + case FuzzySearchMode: + grepOpt.Mode = git.FixedAnyGrepMode + case RegExpSearchMode: + grepOpt.Mode = git.RegExpGrepMode + } + res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, grepOpt) if err != nil { ctx.ServerError("GrepSearch", err) return diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 24779d41e0..e1b37d1e7f 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -417,7 +417,7 @@ func SearchWikiContents(ctx context.Context, repo *repo_model.Repository, keywor return git.GrepSearch(ctx, gitRepo, keyword, git.GrepOptions{ ContextLineNumber: 0, - IsFuzzy: true, + Mode: git.FixedAnyGrepMode, RefName: repo.GetWikiBranchName(), MaxResultLimit: 10, MatchesPerFile: 3, From 663e957d3d83a760e03d14c12d53c3aefa97d20e Mon Sep 17 00:00:00 2001 From: Shiny Nematoda Date: Fri, 16 Aug 2024 13:23:25 +0000 Subject: [PATCH 2/9] ui(git-grep): expose regexp mode in dropdown --- modules/git/grep.go | 4 ++-- options/locale/locale_en-US.ini | 2 ++ routers/web/explore/code.go | 13 ++++++++++++- routers/web/repo/search.go | 7 +++++-- routers/web/user/code.go | 13 ++++++++++++- templates/shared/search/code/search.tmpl | 7 ++++--- templates/shared/search/combo_multi.tmpl | 24 ++++++++++++++++++++++++ templates/shared/search/fuzzy.tmpl | 16 +++++----------- 8 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 templates/shared/search/combo_multi.tmpl diff --git a/modules/git/grep.go b/modules/git/grep.go index 1e34f0275c..3aa1442d29 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -82,12 +82,12 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO var results []*GrepResult // -I skips binary files cmd := NewCommand(ctx, "grep", - "-I", "--null", "--break", "--heading", "--column", + "-I", "--null", "--break", "--heading", "--line-number", "--ignore-case", "--full-name") if opts.Mode == RegExpGrepMode { cmd.AddArguments("--perl-regexp") } else { - cmd.AddArguments("--fixed-strings") + cmd.AddArguments("--fixed-strings", "--column") } cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile)) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1631c90ba2..625f85fac3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -173,6 +173,8 @@ union = Union union_tooltip = Include results that match any of the whitespace seperated keywords exact = Exact exact_tooltip = Include only results that match the exact search term +regexp = RegExp +regexp_tooltip = Include results that match the regular expression repo_kind = Search repos... user_kind = Search users... org_kind = Search orgs... diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index f61b832572..4db41fd726 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -35,11 +35,22 @@ func Code(ctx *context.Context) { language := ctx.FormTrim("l") keyword := ctx.FormTrim("q") - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) + isFuzzy := true + if mode := ctx.FormTrim("mode"); len(mode) > 0 { + isFuzzy = mode == "fuzzy" + } else { + isFuzzy = ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) + } ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language + ctx.Data["CodeSearchOptions"] = []string{"exact", "fuzzy"} ctx.Data["IsFuzzy"] = isFuzzy + if isFuzzy { + ctx.Data["CodeSearchMode"] = "fuzzy" + } else { + ctx.Data["CodeSearchMode"] = "exact" + } ctx.Data["PageIsViewCode"] = true if keyword == "" { diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index 9a033c0f1b..724bb82612 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -27,7 +27,7 @@ const ( func searchModeFromString(s string) searchMode { switch s { - case "fuzzy": + case "fuzzy", "union": return FuzzySearchMode case "regexp": return RegExpSearchMode @@ -65,7 +65,7 @@ func Search(ctx *context.Context) { ctx.Data["Language"] = language ctx.Data["IsFuzzy"] = mode == FuzzySearchMode ctx.Data["IsRegExp"] = mode == RegExpSearchMode - ctx.Data["SearchMode"] = mode.String() + ctx.Data["CodeSearchMode"] = mode.String() ctx.Data["PageIsViewCode"] = true if keyword == "" { @@ -102,6 +102,7 @@ func Search(ctx *context.Context) { } else { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } + ctx.Data["CodeSearchOptions"] = []string{"exact", "fuzzy"} } else { grepOpt := git.GrepOptions{ ContextLineNumber: 1, @@ -110,6 +111,7 @@ func Search(ctx *context.Context) { switch mode { case FuzzySearchMode: grepOpt.Mode = git.FixedAnyGrepMode + ctx.Data["CodeSearchMode"] = "union" case RegExpSearchMode: grepOpt.Mode = git.RegExpGrepMode } @@ -133,6 +135,7 @@ func Search(ctx *context.Context) { Lines: code_indexer.HighlightSearchResultCode(r.Filename, r.LineNumbers, r.HighlightedRanges, strings.Join(r.LineCodes, "\n")), }) } + ctx.Data["CodeSearchOptions"] = []string{"exact", "union", "regexp"} } ctx.Data["CodeIndexerDisabled"] = !setting.Indexer.RepoIndexerEnabled diff --git a/routers/web/user/code.go b/routers/web/user/code.go index e2e8f25661..9e8614cb04 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -40,11 +40,22 @@ func CodeSearch(ctx *context.Context) { language := ctx.FormTrim("l") keyword := ctx.FormTrim("q") - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) + isFuzzy := true + if mode := ctx.FormTrim("mode"); len(mode) > 0 { + isFuzzy = mode == "fuzzy" + } else { + isFuzzy = ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) + } ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language + ctx.Data["CodeSearchOptions"] = []string{"exact", "fuzzy"} ctx.Data["IsFuzzy"] = isFuzzy + if isFuzzy { + ctx.Data["CodeSearchMode"] = "fuzzy" + } else { + ctx.Data["CodeSearchMode"] = "exact" + } ctx.Data["IsCodePage"] = true if keyword == "" { diff --git a/templates/shared/search/code/search.tmpl b/templates/shared/search/code/search.tmpl index 6a52bb9462..b81c6c8f36 100644 --- a/templates/shared/search/code/search.tmpl +++ b/templates/shared/search/code/search.tmpl @@ -1,11 +1,12 @@
- {{template "shared/search/combo_fuzzy" + {{template "shared/search/combo_multi" dict "Value" .Keyword "Disabled" .CodeIndexerUnavailable - "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.code_kind") - "CodeIndexerDisabled" $.CodeIndexerDisabled}} + "CodeIndexerDisabled" $.CodeIndexerDisabled + "Selected" $.CodeSearchMode + "Options" $.CodeSearchOptions}}
diff --git a/templates/shared/search/combo_multi.tmpl b/templates/shared/search/combo_multi.tmpl new file mode 100644 index 0000000000..23c4b4d406 --- /dev/null +++ b/templates/shared/search/combo_multi.tmpl @@ -0,0 +1,24 @@ +{{/* Value - value of the search field (for search results page) */}} +{{/* Disabled (optional) - if search field/button has to be disabled */}} +{{/* Placeholder (optional) - placeholder text to be used */}} +{{/* Selected - the currently selected option */}} +{{/* Options - options available to choose from */}} +{{/* Tooltip (optional) - a tooltip to be displayed on button hover */}} +
+ {{template "shared/search/input" dict "Value" .Value "Disabled" .Disabled "Placeholder" .Placeholder}} + + {{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}} +
diff --git a/templates/shared/search/fuzzy.tmpl b/templates/shared/search/fuzzy.tmpl index 25cfc5762c..f0344c32b7 100644 --- a/templates/shared/search/fuzzy.tmpl +++ b/templates/shared/search/fuzzy.tmpl @@ -1,21 +1,15 @@ {{/* Disabled (optional) - if dropdown has to be disabled */}} {{/* IsFuzzy - state of the fuzzy search toggle */}} -