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  }