summaryrefslogtreecommitdiffstats
path: root/modules/search
diff options
context:
space:
mode:
authorEthan Koenig <ethantkoenig@gmail.com>2017-10-26 23:10:54 -0700
committerLauris BH <lauris@nix.lv>2017-10-27 09:10:54 +0300
commit5866eb23217de4d29b181e30c26cee28ebc6aedc (patch)
treef8f67462544c709e8dd6988ca4d55a22cfc3a22c /modules/search
parent762f1d7237de5727815ebda9593f7f9a20a5a077 (diff)
downloadgitea-5866eb23217de4d29b181e30c26cee28ebc6aedc.tar.gz
gitea-5866eb23217de4d29b181e30c26cee28ebc6aedc.zip
Code/repo search (#2582)
Indexed search of repository contents (for default branch only)
Diffstat (limited to 'modules/search')
-rw-r--r--modules/search/search.go128
1 files changed, 128 insertions, 0 deletions
diff --git a/modules/search/search.go b/modules/search/search.go
new file mode 100644
index 0000000000..1c13f11f8f
--- /dev/null
+++ b/modules/search/search.go
@@ -0,0 +1,128 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package search
+
+import (
+ "bytes"
+ gotemplate "html/template"
+ "strings"
+
+ "code.gitea.io/gitea/modules/highlight"
+ "code.gitea.io/gitea/modules/indexer"
+ "code.gitea.io/gitea/modules/util"
+)
+
+// Result a search result to display
+type Result struct {
+ Filename string
+ HighlightClass string
+ LineNumbers []int
+ FormattedLines gotemplate.HTML
+}
+
+func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) {
+ startIndex := selectionStartIndex
+ numLinesBefore := 0
+ for ; startIndex > 0; startIndex-- {
+ if content[startIndex-1] == '\n' {
+ if numLinesBefore == 1 {
+ break
+ }
+ numLinesBefore++
+ }
+ }
+
+ endIndex := selectionEndIndex
+ numLinesAfter := 0
+ for ; endIndex < len(content); endIndex++ {
+ if content[endIndex] == '\n' {
+ if numLinesAfter == 1 {
+ break
+ }
+ numLinesAfter++
+ }
+ }
+
+ return startIndex, endIndex
+}
+
+func writeStrings(buf *bytes.Buffer, strs ...string) error {
+ for _, s := range strs {
+ _, err := buf.WriteString(s)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func searchResult(result *indexer.RepoSearchResult, startIndex, endIndex int) (*Result, error) {
+ startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n")
+
+ var formattedLinesBuffer bytes.Buffer
+
+ contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n")
+ lineNumbers := make([]int, len(contentLines))
+ index := startIndex
+ for i, line := range contentLines {
+ var err error
+ if index < result.EndIndex &&
+ result.StartIndex < index+len(line) &&
+ result.StartIndex < result.EndIndex {
+ openActiveIndex := util.Max(result.StartIndex-index, 0)
+ closeActiveIndex := util.Min(result.EndIndex-index, len(line))
+ err = writeStrings(&formattedLinesBuffer,
+ `<li>`,
+ line[:openActiveIndex],
+ `<span class='active'>`,
+ line[openActiveIndex:closeActiveIndex],
+ `</span>`,
+ line[closeActiveIndex:],
+ `</li>`,
+ )
+ } else {
+ err = writeStrings(&formattedLinesBuffer,
+ `<li>`,
+ line,
+ `</li>`,
+ )
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ lineNumbers[i] = startLineNum + i
+ index += len(line)
+ }
+ return &Result{
+ Filename: result.Filename,
+ HighlightClass: highlight.FileNameToHighlightClass(result.Filename),
+ LineNumbers: lineNumbers,
+ FormattedLines: gotemplate.HTML(formattedLinesBuffer.String()),
+ }, nil
+}
+
+// PerformSearch perform a search on a repository
+func PerformSearch(repoID int64, keyword string, page, pageSize int) (int, []*Result, error) {
+ if len(keyword) == 0 {
+ return 0, nil, nil
+ }
+
+ total, results, err := indexer.SearchRepoByKeyword(repoID, keyword, page, pageSize)
+ if err != nil {
+ return 0, nil, err
+ }
+
+ displayResults := make([]*Result, len(results))
+
+ for i, result := range results {
+ startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex)
+ displayResults[i], err = searchResult(result, startIndex, endIndex)
+ if err != nil {
+ return 0, nil, err
+ }
+ }
+ return int(total), displayResults, nil
+}