loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: highlighted code search results (#4749)

closes #4534

<details>
<summary>Screenshots</summary>

![](https://codeberg.org/attachments/0ab8a7b0-6485-46dc-a730-c016abb1f287)
</details>

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4749
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Shiny Nematoda <snematoda.751k2@aleeas.com>
Co-committed-by: Shiny Nematoda <snematoda.751k2@aleeas.com>

authored by

Shiny Nematoda
Shiny Nematoda
and committed by
Earl Warren
06d2e90f 51763713

+214 -75
+38 -10
modules/git/grep.go
··· 1 1 // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // Copyright 2024 The Forgejo Authors. All rights reserved. 2 3 // SPDX-License-Identifier: MIT 3 4 4 5 package git ··· 19 20 ) 20 21 21 22 type GrepResult struct { 22 - Filename string 23 - LineNumbers []int 24 - LineCodes []string 23 + Filename string 24 + LineNumbers []int 25 + LineCodes []string 26 + HighlightedRanges [][3]int 25 27 } 26 28 27 29 type GrepOptions struct { ··· 33 35 PathSpec []setting.Glob 34 36 } 35 37 38 + func hasPrefixFold(s, t string) bool { 39 + if len(s) < len(t) { 40 + return false 41 + } 42 + return strings.EqualFold(s[:len(t)], t) 43 + } 44 + 36 45 func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) { 37 46 stdoutReader, stdoutWriter, err := os.Pipe() 38 47 if err != nil { ··· 53 62 2^@repo: go-gitea/gitea 54 63 */ 55 64 var results []*GrepResult 56 - cmd := NewCommand(ctx, "grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name") 65 + cmd := NewCommand(ctx, "grep", 66 + "--null", "--break", "--heading", "--column", 67 + "--fixed-strings", "--line-number", "--ignore-case", "--full-name") 57 68 cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) 58 69 if opts.MatchesPerFile > 0 { 59 70 cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile)) 60 71 } 72 + words := []string{search} 61 73 if opts.IsFuzzy { 62 - words := strings.Fields(search) 63 - for _, word := range words { 64 - cmd.AddOptionValues("-e", strings.TrimLeft(word, "-")) 65 - } 66 - } else { 67 - cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) 74 + words = strings.Fields(search) 75 + } 76 + for _, word := range words { 77 + cmd.AddOptionValues("-e", strings.TrimLeft(word, "-")) 68 78 } 69 79 70 80 // pathspec ··· 128 138 if lineNum, lineCode, ok := strings.Cut(line, "\x00"); ok { 129 139 lineNumInt, _ := strconv.Atoi(lineNum) 130 140 res.LineNumbers = append(res.LineNumbers, lineNumInt) 141 + if lineCol, lineCode2, ok := strings.Cut(lineCode, "\x00"); ok { 142 + lineColInt, _ := strconv.Atoi(lineCol) 143 + start := lineColInt - 1 144 + matchLen := len(lineCode2) 145 + for _, word := range words { 146 + if hasPrefixFold(lineCode2[start:], word) { 147 + matchLen = len(word) 148 + break 149 + } 150 + } 151 + res.HighlightedRanges = append(res.HighlightedRanges, [3]int{ 152 + len(res.LineCodes), 153 + start, 154 + start + matchLen, 155 + }) 156 + res.LineCodes = append(res.LineCodes, lineCode2) 157 + continue 158 + } 131 159 res.LineCodes = append(res.LineCodes, lineCode) 132 160 } 133 161 }
+39 -20
modules/git/grep_test.go
··· 20 20 require.NoError(t, err) 21 21 defer repo.Close() 22 22 23 - res, err := GrepSearch(context.Background(), repo, "void", GrepOptions{}) 23 + res, err := GrepSearch(context.Background(), repo, "public", GrepOptions{}) 24 24 require.NoError(t, err) 25 25 assert.Equal(t, []*GrepResult{ 26 26 { 27 27 Filename: "java-hello/main.java", 28 - LineNumbers: []int{3}, 29 - LineCodes: []string{" public static void main(String[] args)"}, 28 + LineNumbers: []int{1, 3}, 29 + LineCodes: []string{ 30 + "public class HelloWorld", 31 + " public static void main(String[] args)", 32 + }, 33 + HighlightedRanges: [][3]int{{0, 0, 6}, {1, 1, 7}}, 30 34 }, 31 35 { 32 36 Filename: "main.vendor.java", 33 - LineNumbers: []int{3}, 34 - LineCodes: []string{" public static void main(String[] args)"}, 37 + LineNumbers: []int{1, 3}, 38 + LineCodes: []string{ 39 + "public class HelloWorld", 40 + " public static void main(String[] args)", 41 + }, 42 + HighlightedRanges: [][3]int{{0, 0, 6}, {1, 1, 7}}, 35 43 }, 36 44 }, res) 37 45 38 - res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1}) 46 + res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1, ContextLineNumber: 2}) 39 47 require.NoError(t, err) 40 48 assert.Equal(t, []*GrepResult{ 41 49 { 42 50 Filename: "java-hello/main.java", 43 - LineNumbers: []int{3}, 44 - LineCodes: []string{" public static void main(String[] args)"}, 51 + LineNumbers: []int{1, 2, 3, 4, 5}, 52 + LineCodes: []string{ 53 + "public class HelloWorld", 54 + "{", 55 + " public static void main(String[] args)", 56 + " {", 57 + " System.out.println(\"Hello world!\");", 58 + }, 59 + HighlightedRanges: [][3]int{{2, 15, 19}}, 45 60 }, 46 61 }, res) 47 62 ··· 49 64 require.NoError(t, err) 50 65 assert.Equal(t, []*GrepResult{ 51 66 { 52 - Filename: "i-am-a-python.p", 53 - LineNumbers: []int{1}, 54 - LineCodes: []string{"## This is a simple file to do a hello world"}, 67 + Filename: "i-am-a-python.p", 68 + LineNumbers: []int{1}, 69 + LineCodes: []string{"## This is a simple file to do a hello world"}, 70 + HighlightedRanges: [][3]int{{0, 39, 44}}, 55 71 }, 56 72 { 57 - Filename: "java-hello/main.java", 58 - LineNumbers: []int{1}, 59 - LineCodes: []string{"public class HelloWorld"}, 73 + Filename: "java-hello/main.java", 74 + LineNumbers: []int{1}, 75 + LineCodes: []string{"public class HelloWorld"}, 76 + HighlightedRanges: [][3]int{{0, 18, 23}}, 60 77 }, 61 78 { 62 - Filename: "main.vendor.java", 63 - LineNumbers: []int{1}, 64 - LineCodes: []string{"public class HelloWorld"}, 79 + Filename: "main.vendor.java", 80 + LineNumbers: []int{1}, 81 + LineCodes: []string{"public class HelloWorld"}, 82 + HighlightedRanges: [][3]int{{0, 18, 23}}, 65 83 }, 66 84 { 67 - Filename: "python-hello/hello.py", 68 - LineNumbers: []int{1}, 69 - LineCodes: []string{"## This is a simple file to do a hello world"}, 85 + Filename: "python-hello/hello.py", 86 + LineNumbers: []int{1}, 87 + LineCodes: []string{"## This is a simple file to do a hello world"}, 88 + HighlightedRanges: [][3]int{{0, 39, 44}}, 70 89 }, 71 90 }, res) 72 91
+80 -3
modules/indexer/code/search.go
··· 12 12 "code.gitea.io/gitea/modules/highlight" 13 13 "code.gitea.io/gitea/modules/indexer/code/internal" 14 14 "code.gitea.io/gitea/modules/timeutil" 15 + "code.gitea.io/gitea/services/gitdiff" 15 16 ) 16 17 17 18 // Result a search result to display ··· 70 71 return nil 71 72 } 72 73 73 - func HighlightSearchResultCode(filename string, lineNums []int, code string) []ResultLine { 74 + const ( 75 + highlightTagStart = "<span class=\"search-highlight\">" 76 + highlightTagEnd = "</span>" 77 + ) 78 + 79 + func HighlightSearchResultCode(filename string, lineNums []int, highlightRanges [][3]int, code string) []ResultLine { 80 + hcd := gitdiff.NewHighlightCodeDiff() 81 + hcd.CollectUsedRunes(code) 82 + startTag, endTag := hcd.NextPlaceholder(), hcd.NextPlaceholder() 83 + hcd.PlaceholderTokenMap[startTag] = highlightTagStart 84 + hcd.PlaceholderTokenMap[endTag] = highlightTagEnd 85 + 74 86 // we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting 75 87 hl, _ := highlight.Code(filename, "", code) 76 - highlightedLines := strings.Split(string(hl), "\n") 88 + conv := hcd.ConvertToPlaceholders(string(hl)) 89 + convLines := strings.Split(conv, "\n") 90 + 91 + // each highlightRange is of the form [line number, start pos, end pos] 92 + for _, highlightRange := range highlightRanges { 93 + ln, start, end := highlightRange[0], highlightRange[1], highlightRange[2] 94 + line := convLines[ln] 95 + if line == "" || len(line) <= start || len(line) < end { 96 + continue 97 + } 98 + 99 + sb := strings.Builder{} 100 + count := -1 101 + isOpen := false 102 + for _, r := range line { 103 + if token, ok := hcd.PlaceholderTokenMap[r]; 104 + // token was not found 105 + !ok || 106 + // token was marked as used 107 + token == "" || 108 + // the token is not an valid html tag emited by chroma 109 + !(len(token) > 6 && (token[0:5] == "<span" || token[0:6] == "</span")) { 110 + count++ 111 + } else if !isOpen { 112 + // open the tag only after all other placeholders 113 + sb.WriteRune(r) 114 + continue 115 + } else if isOpen && count < end { 116 + // if the tag is open, but a placeholder exists in between 117 + // close the tag 118 + sb.WriteRune(endTag) 119 + // write the placeholder 120 + sb.WriteRune(r) 121 + // reopen the tag 122 + sb.WriteRune(startTag) 123 + continue 124 + } 125 + 126 + switch count { 127 + case end: 128 + // if tag is not open, no need to close 129 + if !isOpen { 130 + break 131 + } 132 + sb.WriteRune(endTag) 133 + isOpen = false 134 + case start: 135 + // if tag is open, do not open again 136 + if isOpen { 137 + break 138 + } 139 + isOpen = true 140 + sb.WriteRune(startTag) 141 + } 77 142 143 + sb.WriteRune(r) 144 + } 145 + if isOpen { 146 + sb.WriteRune(endTag) 147 + } 148 + convLines[ln] = sb.String() 149 + } 150 + conv = strings.Join(convLines, "\n") 151 + 152 + highlightedLines := strings.Split(hcd.Recover(conv), "\n") 78 153 // The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n` 79 154 lines := make([]ResultLine, min(len(highlightedLines), len(lineNums))) 80 155 for i := 0; i < len(lines); i++ { ··· 92 167 contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n") 93 168 lineNums := make([]int, 0, len(contentLines)) 94 169 index := startIndex 170 + var highlightRanges [][3]int 95 171 for i, line := range contentLines { 96 172 var err error 97 173 if index < result.EndIndex && ··· 99 175 result.StartIndex < result.EndIndex { 100 176 openActiveIndex := max(result.StartIndex-index, 0) 101 177 closeActiveIndex := min(result.EndIndex-index, len(line)) 178 + highlightRanges = append(highlightRanges, [3]int{i, openActiveIndex, closeActiveIndex}) 102 179 err = writeStrings(&formattedLinesBuffer, 103 180 line[:openActiveIndex], 104 181 line[openActiveIndex:closeActiveIndex], ··· 122 199 UpdatedUnix: result.UpdatedUnix, 123 200 Language: result.Language, 124 201 Color: result.Color, 125 - Lines: HighlightSearchResultCode(result.Filename, lineNums, formattedLinesBuffer.String()), 202 + Lines: HighlightSearchResultCode(result.Filename, lineNums, highlightRanges, formattedLinesBuffer.String()), 126 203 }, nil 127 204 } 128 205
+1 -1
routers/web/repo/search.go
··· 85 85 // UpdatedUnix: not supported yet 86 86 // Language: not supported yet 87 87 // Color: not supported yet 88 - Lines: code_indexer.HighlightSearchResultCode(r.Filename, r.LineNumbers, strings.Join(r.LineCodes, "\n")), 88 + Lines: code_indexer.HighlightSearchResultCode(r.Filename, r.LineNumbers, r.HighlightedRanges, strings.Join(r.LineCodes, "\n")), 89 89 }) 90 90 } 91 91 }
+1 -1
services/gitdiff/gitdiff.go
··· 337 337 return DiffInlineWithHighlightCode(diffSection.FileName, language, diffLine.Content, locale) 338 338 } 339 339 340 - hcd := newHighlightCodeDiff() 340 + hcd := NewHighlightCodeDiff() 341 341 diffRecord := hcd.diffWithHighlight(diffSection.FileName, language, diff1[1:], diff2[1:]) 342 342 // it seems that Gitea doesn't need the line wrapper of Chroma, so do not add them back 343 343 // if the line wrappers are still needed in the future, it can be added back by "diffToHTML(hcd.lineWrapperTags. ...)"
+28 -24
services/gitdiff/highlightdiff.go
··· 31 31 return "", "", s, true 32 32 } 33 33 34 - // highlightCodeDiff is used to do diff with highlighted HTML code. 34 + // HighlightCodeDiff is used to do diff with highlighted HTML code. 35 35 // It totally depends on Chroma's valid HTML output and its structure, do not use these functions for other purposes. 36 36 // The HTML tags and entities will be replaced by Unicode placeholders: "<span>{TEXT}</span>" => "\uE000{TEXT}\uE001" 37 37 // These Unicode placeholders are friendly to the diff. 38 38 // Then after diff, the placeholders in diff result will be recovered to the HTML tags and entities. 39 39 // It's guaranteed that the tags in final diff result are paired correctly. 40 - type highlightCodeDiff struct { 40 + type HighlightCodeDiff struct { 41 41 placeholderBegin rune 42 42 placeholderMaxCount int 43 43 placeholderIndex int 44 - placeholderTokenMap map[rune]string 44 + PlaceholderTokenMap map[rune]string 45 45 tokenPlaceholderMap map[string]rune 46 46 47 47 placeholderOverflowCount int ··· 49 49 lineWrapperTags []string 50 50 } 51 51 52 - func newHighlightCodeDiff() *highlightCodeDiff { 53 - return &highlightCodeDiff{ 52 + func NewHighlightCodeDiff() *HighlightCodeDiff { 53 + return &HighlightCodeDiff{ 54 54 placeholderBegin: rune(0x100000), // Plane 16: Supplementary Private Use Area B (U+100000..U+10FFFD) 55 55 placeholderMaxCount: 64000, 56 - placeholderTokenMap: map[rune]string{}, 56 + PlaceholderTokenMap: map[rune]string{}, 57 57 tokenPlaceholderMap: map[string]rune{}, 58 58 } 59 59 } 60 60 61 - // nextPlaceholder returns 0 if no more placeholder can be used 61 + // NextPlaceholder returns 0 if no more placeholder can be used 62 62 // the diff is done line by line, usually there are only a few (no more than 10) placeholders in one line 63 63 // so the placeholderMaxCount is impossible to be exhausted in real cases. 64 - func (hcd *highlightCodeDiff) nextPlaceholder() rune { 64 + func (hcd *HighlightCodeDiff) NextPlaceholder() rune { 65 65 for hcd.placeholderIndex < hcd.placeholderMaxCount { 66 66 r := hcd.placeholderBegin + rune(hcd.placeholderIndex) 67 67 hcd.placeholderIndex++ 68 68 // only use non-existing (not used by code) rune as placeholders 69 - if _, ok := hcd.placeholderTokenMap[r]; !ok { 69 + if _, ok := hcd.PlaceholderTokenMap[r]; !ok { 70 70 return r 71 71 } 72 72 } 73 73 return 0 // no more available placeholder 74 74 } 75 75 76 - func (hcd *highlightCodeDiff) isInPlaceholderRange(r rune) bool { 76 + func (hcd *HighlightCodeDiff) isInPlaceholderRange(r rune) bool { 77 77 return hcd.placeholderBegin <= r && r < hcd.placeholderBegin+rune(hcd.placeholderMaxCount) 78 78 } 79 79 80 - func (hcd *highlightCodeDiff) collectUsedRunes(code string) { 80 + func (hcd *HighlightCodeDiff) CollectUsedRunes(code string) { 81 81 for _, r := range code { 82 82 if hcd.isInPlaceholderRange(r) { 83 83 // put the existing rune (used by code) in map, then this rune won't be used a placeholder anymore. 84 - hcd.placeholderTokenMap[r] = "" 84 + hcd.PlaceholderTokenMap[r] = "" 85 85 } 86 86 } 87 87 } 88 88 89 - func (hcd *highlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB string) []diffmatchpatch.Diff { 90 - hcd.collectUsedRunes(codeA) 91 - hcd.collectUsedRunes(codeB) 89 + func (hcd *HighlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB string) []diffmatchpatch.Diff { 90 + hcd.CollectUsedRunes(codeA) 91 + hcd.CollectUsedRunes(codeB) 92 92 93 93 highlightCodeA, _ := highlight.Code(filename, language, codeA) 94 94 highlightCodeB, _ := highlight.Code(filename, language, codeB) 95 95 96 - convertedCodeA := hcd.convertToPlaceholders(string(highlightCodeA)) 97 - convertedCodeB := hcd.convertToPlaceholders(string(highlightCodeB)) 96 + convertedCodeA := hcd.ConvertToPlaceholders(string(highlightCodeA)) 97 + convertedCodeB := hcd.ConvertToPlaceholders(string(highlightCodeB)) 98 98 99 99 diffs := diffMatchPatch.DiffMain(convertedCodeA, convertedCodeB, true) 100 100 diffs = diffMatchPatch.DiffCleanupEfficiency(diffs) ··· 106 106 } 107 107 108 108 // convertToPlaceholders totally depends on Chroma's valid HTML output and its structure, do not use these functions for other purposes. 109 - func (hcd *highlightCodeDiff) convertToPlaceholders(htmlCode string) string { 109 + func (hcd *HighlightCodeDiff) ConvertToPlaceholders(htmlCode string) string { 110 110 var tagStack []string 111 111 res := strings.Builder{} 112 112 ··· 153 153 // remember the placeholder and token in the map 154 154 placeholder, ok := hcd.tokenPlaceholderMap[tokenInMap] 155 155 if !ok { 156 - placeholder = hcd.nextPlaceholder() 156 + placeholder = hcd.NextPlaceholder() 157 157 if placeholder != 0 { 158 158 hcd.tokenPlaceholderMap[tokenInMap] = placeholder 159 - hcd.placeholderTokenMap[placeholder] = tokenInMap 159 + hcd.PlaceholderTokenMap[placeholder] = tokenInMap 160 160 } 161 161 } 162 162 ··· 179 179 return res.String() 180 180 } 181 181 182 - func (hcd *highlightCodeDiff) recoverOneDiff(diff *diffmatchpatch.Diff) { 182 + func (hcd *HighlightCodeDiff) recoverOneDiff(diff *diffmatchpatch.Diff) { 183 + diff.Text = hcd.Recover(diff.Text) 184 + } 185 + 186 + func (hcd *HighlightCodeDiff) Recover(src string) string { 183 187 sb := strings.Builder{} 184 188 var tagStack []string 185 189 186 - for _, r := range diff.Text { 187 - token, ok := hcd.placeholderTokenMap[r] 190 + for _, r := range src { 191 + token, ok := hcd.PlaceholderTokenMap[r] 188 192 if !ok || token == "" { 189 193 sb.WriteRune(r) // if the rune is not a placeholder, write it as it is 190 194 continue ··· 218 222 } 219 223 } 220 224 221 - diff.Text = sb.String() 225 + return sb.String() 222 226 }
+11 -11
services/gitdiff/highlightdiff_test.go
··· 13 13 ) 14 14 15 15 func TestDiffWithHighlight(t *testing.T) { 16 - hcd := newHighlightCodeDiff() 16 + hcd := NewHighlightCodeDiff() 17 17 diffs := hcd.diffWithHighlight( 18 18 "main.v", "", 19 19 " run('<>')\n", ··· 28 28 output = diffToHTML(nil, diffs, DiffLineAdd) 29 29 assert.Equal(t, expected, output) 30 30 31 - hcd = newHighlightCodeDiff() 32 - hcd.placeholderTokenMap['O'] = "<span>" 33 - hcd.placeholderTokenMap['C'] = "</span>" 31 + hcd = NewHighlightCodeDiff() 32 + hcd.PlaceholderTokenMap['O'] = "<span>" 33 + hcd.PlaceholderTokenMap['C'] = "</span>" 34 34 diff := diffmatchpatch.Diff{} 35 35 36 36 diff.Text = "OC" ··· 47 47 } 48 48 49 49 func TestDiffWithHighlightPlaceholder(t *testing.T) { 50 - hcd := newHighlightCodeDiff() 50 + hcd := NewHighlightCodeDiff() 51 51 diffs := hcd.diffWithHighlight( 52 52 "main.js", "", 53 53 "a='\U00100000'", 54 54 "a='\U0010FFFD''", 55 55 ) 56 - assert.Equal(t, "", hcd.placeholderTokenMap[0x00100000]) 57 - assert.Equal(t, "", hcd.placeholderTokenMap[0x0010FFFD]) 56 + assert.Equal(t, "", hcd.PlaceholderTokenMap[0x00100000]) 57 + assert.Equal(t, "", hcd.PlaceholderTokenMap[0x0010FFFD]) 58 58 59 59 expected := fmt.Sprintf(`<span class="nx">a</span><span class="o">=</span><span class="s1">&#39;</span><span class="removed-code">%s</span>&#39;`, "\U00100000") 60 60 output := diffToHTML(hcd.lineWrapperTags, diffs, DiffLineDel) 61 61 assert.Equal(t, expected, output) 62 62 63 - hcd = newHighlightCodeDiff() 63 + hcd = NewHighlightCodeDiff() 64 64 diffs = hcd.diffWithHighlight( 65 65 "main.js", "", 66 66 "a='\U00100000'", ··· 72 72 } 73 73 74 74 func TestDiffWithHighlightPlaceholderExhausted(t *testing.T) { 75 - hcd := newHighlightCodeDiff() 75 + hcd := NewHighlightCodeDiff() 76 76 hcd.placeholderMaxCount = 0 77 77 diffs := hcd.diffWithHighlight( 78 78 "main.js", "", ··· 83 83 expected := fmt.Sprintf(`<span class="removed-code">%s#39;</span>`, "\uFFFD") 84 84 assert.Equal(t, expected, output) 85 85 86 - hcd = newHighlightCodeDiff() 86 + hcd = NewHighlightCodeDiff() 87 87 hcd.placeholderMaxCount = 0 88 88 diffs = hcd.diffWithHighlight( 89 89 "main.js", "", ··· 102 102 func TestDiffWithHighlightTagMatch(t *testing.T) { 103 103 totalOverflow := 0 104 104 for i := 0; i < 100; i++ { 105 - hcd := newHighlightCodeDiff() 105 + hcd := NewHighlightCodeDiff() 106 106 hcd.placeholderMaxCount = i 107 107 diffs := hcd.diffWithHighlight( 108 108 "main.js", "",
+10 -4
tests/integration/explore_code_test.go
··· 8 8 "code.gitea.io/gitea/modules/test" 9 9 "code.gitea.io/gitea/tests" 10 10 11 + "github.com/PuerkitoBio/goquery" 11 12 "github.com/stretchr/testify/assert" 12 13 ) 13 14 ··· 15 16 defer tests.PrepareTestEnv(t)() 16 17 defer test.MockVariableValue(&setting.Indexer.RepoIndexerEnabled, true)() 17 18 18 - req := NewRequest(t, "GET", "/explore/code") 19 + req := NewRequest(t, "GET", "/explore/code?q=file&fuzzy=true") 19 20 resp := MakeRequest(t, req, http.StatusOK) 21 + doc := NewHTMLParser(t, resp.Body).Find(".explore") 20 22 21 - doc := NewHTMLParser(t, resp.Body) 22 - msg := doc.Find(".explore").Find(".ui.container").Find(".ui.message[data-test-tag=grep]") 23 + msg := doc. 24 + Find(".ui.container"). 25 + Find(".ui.message[data-test-tag=grep]") 26 + assert.EqualValues(t, 0, msg.Length()) 23 27 24 - assert.Empty(t, msg.Nodes) 28 + doc.Find(".file-body").Each(func(i int, sel *goquery.Selection) { 29 + assert.Positive(t, sel.Find(".code-inner").Find(".search-highlight").Length(), 0) 30 + }) 25 31 }
+2 -1
tests/integration/repo_search_test.go
··· 27 27 28 28 result := make([]string, resultSelections.Length()) 29 29 resultSelections.Each(func(i int, selection *goquery.Selection) { 30 - assert.Positive(t, resultSelections.Find("div ol li").Length(), 0) 30 + assert.Positive(t, selection.Find("div ol li").Length(), 0) 31 + assert.Positive(t, selection.Find(".code-inner").Find(".search-highlight").Length(), 0) 31 32 result[i] = selection. 32 33 Find(".header"). 33 34 Find("span.file a.file-link").
+4
web_src/css/repo.css
··· 1752 1752 color: inherit; 1753 1753 } 1754 1754 1755 + .search-highlight { 1756 + background: var(--color-primary-alpha-40); 1757 + } 1758 + 1755 1759 .repository.quickstart .guide .item { 1756 1760 padding: 1em; 1757 1761 }