code.gitea.io/gitea@v1.19.3/modules/indexer/code/search.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package code 5 6 import ( 7 "bytes" 8 "context" 9 "strings" 10 11 "code.gitea.io/gitea/modules/highlight" 12 "code.gitea.io/gitea/modules/timeutil" 13 "code.gitea.io/gitea/modules/util" 14 ) 15 16 // Result a search result to display 17 type Result struct { 18 RepoID int64 19 Filename string 20 CommitID string 21 UpdatedUnix timeutil.TimeStamp 22 Language string 23 Color string 24 LineNumbers []int 25 FormattedLines string 26 } 27 28 func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) { 29 startIndex := selectionStartIndex 30 numLinesBefore := 0 31 for ; startIndex > 0; startIndex-- { 32 if content[startIndex-1] == '\n' { 33 if numLinesBefore == 1 { 34 break 35 } 36 numLinesBefore++ 37 } 38 } 39 40 endIndex := selectionEndIndex 41 numLinesAfter := 0 42 for ; endIndex < len(content); endIndex++ { 43 if content[endIndex] == '\n' { 44 if numLinesAfter == 1 { 45 break 46 } 47 numLinesAfter++ 48 } 49 } 50 51 return startIndex, endIndex 52 } 53 54 func writeStrings(buf *bytes.Buffer, strs ...string) error { 55 for _, s := range strs { 56 _, err := buf.WriteString(s) 57 if err != nil { 58 return err 59 } 60 } 61 return nil 62 } 63 64 func searchResult(result *SearchResult, startIndex, endIndex int) (*Result, error) { 65 startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n") 66 67 var formattedLinesBuffer bytes.Buffer 68 69 contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n") 70 lineNumbers := make([]int, len(contentLines)) 71 index := startIndex 72 for i, line := range contentLines { 73 var err error 74 if index < result.EndIndex && 75 result.StartIndex < index+len(line) && 76 result.StartIndex < result.EndIndex { 77 openActiveIndex := util.Max(result.StartIndex-index, 0) 78 closeActiveIndex := util.Min(result.EndIndex-index, len(line)) 79 err = writeStrings(&formattedLinesBuffer, 80 line[:openActiveIndex], 81 line[openActiveIndex:closeActiveIndex], 82 line[closeActiveIndex:], 83 ) 84 } else { 85 err = writeStrings(&formattedLinesBuffer, 86 line, 87 ) 88 } 89 if err != nil { 90 return nil, err 91 } 92 93 lineNumbers[i] = startLineNum + i 94 index += len(line) 95 } 96 97 highlighted, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String()) 98 99 return &Result{ 100 RepoID: result.RepoID, 101 Filename: result.Filename, 102 CommitID: result.CommitID, 103 UpdatedUnix: result.UpdatedUnix, 104 Language: result.Language, 105 Color: result.Color, 106 LineNumbers: lineNumbers, 107 FormattedLines: highlighted, 108 }, nil 109 } 110 111 // PerformSearch perform a search on a repository 112 func PerformSearch(ctx context.Context, repoIDs []int64, language, keyword string, page, pageSize int, isMatch bool) (int, []*Result, []*SearchResultLanguages, error) { 113 if len(keyword) == 0 { 114 return 0, nil, nil, nil 115 } 116 117 total, results, resultLanguages, err := indexer.Search(ctx, repoIDs, language, keyword, page, pageSize, isMatch) 118 if err != nil { 119 return 0, nil, nil, err 120 } 121 122 displayResults := make([]*Result, len(results)) 123 124 for i, result := range results { 125 startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex) 126 displayResults[i], err = searchResult(result, startIndex, endIndex) 127 if err != nil { 128 return 0, nil, nil, err 129 } 130 } 131 return int(total), displayResults, resultLanguages, nil 132 }