You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
3.2 KiB

  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package search
  5. import (
  6. "bytes"
  7. "html"
  8. gotemplate "html/template"
  9. "strings"
  10. "code.gitea.io/gitea/modules/highlight"
  11. "code.gitea.io/gitea/modules/indexer"
  12. "code.gitea.io/gitea/modules/util"
  13. )
  14. // Result a search result to display
  15. type Result struct {
  16. RepoID int64
  17. Filename string
  18. HighlightClass string
  19. LineNumbers []int
  20. FormattedLines gotemplate.HTML
  21. }
  22. func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) {
  23. startIndex := selectionStartIndex
  24. numLinesBefore := 0
  25. for ; startIndex > 0; startIndex-- {
  26. if content[startIndex-1] == '\n' {
  27. if numLinesBefore == 1 {
  28. break
  29. }
  30. numLinesBefore++
  31. }
  32. }
  33. endIndex := selectionEndIndex
  34. numLinesAfter := 0
  35. for ; endIndex < len(content); endIndex++ {
  36. if content[endIndex] == '\n' {
  37. if numLinesAfter == 1 {
  38. break
  39. }
  40. numLinesAfter++
  41. }
  42. }
  43. return startIndex, endIndex
  44. }
  45. func writeStrings(buf *bytes.Buffer, strs ...string) error {
  46. for _, s := range strs {
  47. _, err := buf.WriteString(s)
  48. if err != nil {
  49. return err
  50. }
  51. }
  52. return nil
  53. }
  54. func searchResult(result *indexer.RepoSearchResult, startIndex, endIndex int) (*Result, error) {
  55. startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n")
  56. var formattedLinesBuffer bytes.Buffer
  57. contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n")
  58. lineNumbers := make([]int, len(contentLines))
  59. index := startIndex
  60. for i, line := range contentLines {
  61. var err error
  62. if index < result.EndIndex &&
  63. result.StartIndex < index+len(line) &&
  64. result.StartIndex < result.EndIndex {
  65. openActiveIndex := util.Max(result.StartIndex-index, 0)
  66. closeActiveIndex := util.Min(result.EndIndex-index, len(line))
  67. err = writeStrings(&formattedLinesBuffer,
  68. `<li>`,
  69. html.EscapeString(line[:openActiveIndex]),
  70. `<span class='active'>`,
  71. html.EscapeString(line[openActiveIndex:closeActiveIndex]),
  72. `</span>`,
  73. html.EscapeString(line[closeActiveIndex:]),
  74. `</li>`,
  75. )
  76. } else {
  77. err = writeStrings(&formattedLinesBuffer,
  78. `<li>`,
  79. html.EscapeString(line),
  80. `</li>`,
  81. )
  82. }
  83. if err != nil {
  84. return nil, err
  85. }
  86. lineNumbers[i] = startLineNum + i
  87. index += len(line)
  88. }
  89. return &Result{
  90. RepoID: result.RepoID,
  91. Filename: result.Filename,
  92. HighlightClass: highlight.FileNameToHighlightClass(result.Filename),
  93. LineNumbers: lineNumbers,
  94. FormattedLines: gotemplate.HTML(formattedLinesBuffer.String()),
  95. }, nil
  96. }
  97. // PerformSearch perform a search on a repository
  98. func PerformSearch(repoIDs []int64, keyword string, page, pageSize int) (int, []*Result, error) {
  99. if len(keyword) == 0 {
  100. return 0, nil, nil
  101. }
  102. total, results, err := indexer.SearchRepoByKeyword(repoIDs, keyword, page, pageSize)
  103. if err != nil {
  104. return 0, nil, err
  105. }
  106. displayResults := make([]*Result, len(results))
  107. for i, result := range results {
  108. startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex)
  109. displayResults[i], err = searchResult(result, startIndex, endIndex)
  110. if err != nil {
  111. return 0, nil, err
  112. }
  113. }
  114. return int(total), displayResults, nil
  115. }