gitea源码

search.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package code
  4. import (
  5. "bytes"
  6. "context"
  7. "html/template"
  8. "strings"
  9. "code.gitea.io/gitea/modules/highlight"
  10. "code.gitea.io/gitea/modules/indexer/code/internal"
  11. "code.gitea.io/gitea/modules/timeutil"
  12. )
  13. // Result a search result to display
  14. type Result struct {
  15. RepoID int64
  16. Filename string
  17. CommitID string
  18. UpdatedUnix timeutil.TimeStamp
  19. Language string
  20. Color string
  21. Lines []*ResultLine
  22. }
  23. type ResultLine struct {
  24. Num int
  25. FormattedContent template.HTML
  26. }
  27. type SearchResultLanguages = internal.SearchResultLanguages
  28. type SearchOptions = internal.SearchOptions
  29. func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) {
  30. startIndex := selectionStartIndex
  31. numLinesBefore := 0
  32. for ; startIndex > 0; startIndex-- {
  33. if content[startIndex-1] == '\n' {
  34. if numLinesBefore == 1 {
  35. break
  36. }
  37. numLinesBefore++
  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. return startIndex, endIndex
  51. }
  52. func writeStrings(buf *bytes.Buffer, strs ...string) error {
  53. for _, s := range strs {
  54. _, err := buf.WriteString(s)
  55. if err != nil {
  56. return err
  57. }
  58. }
  59. return nil
  60. }
  61. func HighlightSearchResultCode(filename, language string, lineNums []int, code string) []*ResultLine {
  62. // we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
  63. hl, _ := highlight.Code(filename, language, code)
  64. highlightedLines := strings.Split(string(hl), "\n")
  65. // The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
  66. lines := make([]*ResultLine, min(len(highlightedLines), len(lineNums)))
  67. for i := range lines {
  68. lines[i] = &ResultLine{
  69. Num: lineNums[i],
  70. FormattedContent: template.HTML(highlightedLines[i]),
  71. }
  72. }
  73. return lines
  74. }
  75. func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Result, error) {
  76. startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n")
  77. var formattedLinesBuffer bytes.Buffer
  78. contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n")
  79. lineNums := make([]int, 0, len(contentLines))
  80. index := startIndex
  81. for i, line := range contentLines {
  82. var err error
  83. if index < result.EndIndex &&
  84. result.StartIndex < index+len(line) &&
  85. result.StartIndex < result.EndIndex {
  86. openActiveIndex := max(result.StartIndex-index, 0)
  87. closeActiveIndex := min(result.EndIndex-index, len(line))
  88. err = writeStrings(&formattedLinesBuffer,
  89. line[:openActiveIndex],
  90. line[openActiveIndex:closeActiveIndex],
  91. line[closeActiveIndex:],
  92. )
  93. } else {
  94. err = writeStrings(&formattedLinesBuffer, line)
  95. }
  96. if err != nil {
  97. return nil, err
  98. }
  99. lineNums = append(lineNums, startLineNum+i)
  100. index += len(line)
  101. }
  102. return &Result{
  103. RepoID: result.RepoID,
  104. Filename: result.Filename,
  105. CommitID: result.CommitID,
  106. UpdatedUnix: result.UpdatedUnix,
  107. Language: result.Language,
  108. Color: result.Color,
  109. Lines: HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()),
  110. }, nil
  111. }
  112. // PerformSearch perform a search on a repository
  113. func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, []*SearchResultLanguages, error) {
  114. if opts == nil || len(opts.Keyword) == 0 {
  115. return 0, nil, nil, nil
  116. }
  117. total, results, resultLanguages, err := (*globalIndexer.Load()).Search(ctx, opts)
  118. if err != nil {
  119. return 0, nil, nil, err
  120. }
  121. displayResults := make([]*Result, len(results))
  122. for i, result := range results {
  123. startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex)
  124. displayResults[i], err = searchResult(result, startIndex, endIndex)
  125. if err != nil {
  126. return 0, nil, nil, err
  127. }
  128. }
  129. return int(total), displayResults, resultLanguages, nil
  130. }