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.

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