mirror of
https://github.com/go-gitea/gitea
synced 2025-02-07 00:57:02 +01:00
add explore/code api route
This commit is contained in:
parent
d655ff18b3
commit
1bf78712d4
@ -26,6 +26,7 @@ type SearchOptions struct {
|
||||
Language string
|
||||
|
||||
IsKeywordFuzzy bool
|
||||
IsHtmlSafe bool
|
||||
|
||||
db.Paginator
|
||||
}
|
||||
|
@ -28,18 +28,19 @@ type Result struct {
|
||||
type ResultLine struct {
|
||||
Num int
|
||||
FormattedContent template.HTML
|
||||
RawContent string
|
||||
}
|
||||
|
||||
type SearchResultLanguages = internal.SearchResultLanguages
|
||||
|
||||
type SearchOptions = internal.SearchOptions
|
||||
|
||||
func indices(content string, selectionStartIndex, selectionEndIndex int) (int, int) {
|
||||
func indices(content string, selectionStartIndex, selectionEndIndex, numLinesBuffer int) (int, int) {
|
||||
startIndex := selectionStartIndex
|
||||
numLinesBefore := 0
|
||||
for ; startIndex > 0; startIndex-- {
|
||||
if content[startIndex-1] == '\n' {
|
||||
if numLinesBefore == 1 {
|
||||
if numLinesBefore == numLinesBuffer {
|
||||
break
|
||||
}
|
||||
numLinesBefore++
|
||||
@ -50,7 +51,7 @@ func indices(content string, selectionStartIndex, selectionEndIndex int) (int, i
|
||||
numLinesAfter := 0
|
||||
for ; endIndex < len(content); endIndex++ {
|
||||
if content[endIndex] == '\n' {
|
||||
if numLinesAfter == 1 {
|
||||
if numLinesAfter == numLinesBuffer {
|
||||
break
|
||||
}
|
||||
numLinesAfter++
|
||||
@ -86,7 +87,22 @@ func HighlightSearchResultCode(filename, language string, lineNums []int, code s
|
||||
return lines
|
||||
}
|
||||
|
||||
func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Result, error) {
|
||||
func RawSearchResultCode(filename, language string, lineNums []int, code string) []*ResultLine {
|
||||
// we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
|
||||
rawLines := strings.Split(code, "\n")
|
||||
|
||||
// The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
|
||||
lines := make([]*ResultLine, min(len(rawLines), len(lineNums)))
|
||||
for i := 0; i < len(lines); i++ {
|
||||
lines[i] = &ResultLine{
|
||||
Num: lineNums[i],
|
||||
RawContent: rawLines[i],
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func searchResult(result *internal.SearchResult, startIndex, endIndex int, escapeHtml bool) (*Result, error) {
|
||||
startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n")
|
||||
|
||||
var formattedLinesBuffer bytes.Buffer
|
||||
@ -117,6 +133,13 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
|
||||
index += len(line)
|
||||
}
|
||||
|
||||
var lines []*ResultLine
|
||||
if escapeHtml {
|
||||
lines = HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String())
|
||||
} else {
|
||||
lines = RawSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String())
|
||||
}
|
||||
|
||||
return &Result{
|
||||
RepoID: result.RepoID,
|
||||
Filename: result.Filename,
|
||||
@ -124,7 +147,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
|
||||
UpdatedUnix: result.UpdatedUnix,
|
||||
Language: result.Language,
|
||||
Color: result.Color,
|
||||
Lines: HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()),
|
||||
Lines: lines,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -142,9 +165,14 @@ func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, []
|
||||
|
||||
displayResults := make([]*Result, len(results))
|
||||
|
||||
nLinesBuffer := 0
|
||||
if opts.IsHtmlSafe {
|
||||
nLinesBuffer = 1
|
||||
}
|
||||
|
||||
for i, result := range results {
|
||||
startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex)
|
||||
displayResults[i], err = searchResult(result, startIndex, endIndex)
|
||||
startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex, nLinesBuffer)
|
||||
displayResults[i], err = searchResult(result, startIndex, endIndex, opts.IsHtmlSafe)
|
||||
if err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
|
20
modules/structs/explore.go
Normal file
20
modules/structs/explore.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs // import "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
// ExploreCodeSearchItem A single search match
|
||||
// swagger:model
|
||||
type ExploreCodeSearchItem struct {
|
||||
RepoName string `json:"repoName"`
|
||||
FilePath string `json:"path"`
|
||||
LineNumber int `json:"lineNumber"`
|
||||
LineText string `json:"lineText"`
|
||||
}
|
||||
|
||||
// ExploreCodeResult all returned search results
|
||||
// swagger:model
|
||||
type ExploreCodeResult struct {
|
||||
Total int `json:"total"`
|
||||
Results []ExploreCodeSearchItem `json:"results"`
|
||||
}
|
@ -92,6 +92,7 @@ import (
|
||||
"code.gitea.io/gitea/routers/api/v1/repo"
|
||||
"code.gitea.io/gitea/routers/api/v1/settings"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/explore"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
@ -890,6 +891,14 @@ func Routes() *web.Router {
|
||||
// Misc (public accessible)
|
||||
m.Group("", func() {
|
||||
m.Get("/version", misc.Version)
|
||||
m.Group("/explore", func() {
|
||||
m.Get("/code", func(ctx *context.APIContext) {
|
||||
if unit.TypeCode.UnitGlobalDisabled() {
|
||||
ctx.NotFound("Repo unit code is disabled", nil)
|
||||
return
|
||||
}
|
||||
}, explore.Code)
|
||||
})
|
||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
||||
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
|
||||
|
117
routers/api/v1/explore/code.go
Normal file
117
routers/api/v1/explore/code.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package explore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// Code explore code
|
||||
func Code(ctx *context.APIContext) {
|
||||
if !setting.Indexer.RepoIndexerEnabled {
|
||||
ctx.NotFound("Indexer not enabled")
|
||||
return
|
||||
}
|
||||
|
||||
language := ctx.FormTrim("l")
|
||||
keyword := ctx.FormTrim("q")
|
||||
|
||||
isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true)
|
||||
|
||||
if keyword == "" {
|
||||
ctx.JSON(http.StatusInternalServerError, api.SearchError{OK: false, Error: "No keyword provided"})
|
||||
return
|
||||
}
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
var (
|
||||
repoIDs []int64
|
||||
err error
|
||||
isAdmin bool
|
||||
)
|
||||
if ctx.Doer != nil {
|
||||
isAdmin = ctx.Doer.IsAdmin
|
||||
}
|
||||
|
||||
// guest user or non-admin user
|
||||
if ctx.Doer == nil || !isAdmin {
|
||||
repoIDs, err = repo_model.FindUserCodeAccessibleRepoIDs(ctx, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindUserCodeAccessibleRepoIDs", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
total int
|
||||
searchResults []*code_indexer.Result
|
||||
repoMaps map[int64]*repo_model.Repository
|
||||
)
|
||||
|
||||
if (len(repoIDs) > 0) || isAdmin {
|
||||
total, searchResults, _, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
|
||||
RepoIDs: repoIDs,
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
IsHtmlSafe: false,
|
||||
Language: language,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.API.DefaultPagingNum,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if code_indexer.IsAvailable(ctx) {
|
||||
ctx.ServerError("SearchResults", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
loadRepoIDs := make([]int64, 0, len(searchResults))
|
||||
for _, result := range searchResults {
|
||||
var find bool
|
||||
for _, id := range loadRepoIDs {
|
||||
if id == result.RepoID {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
loadRepoIDs = append(loadRepoIDs, result.RepoID)
|
||||
}
|
||||
}
|
||||
|
||||
repoMaps, err = repo_model.GetRepositoriesMapByIDs(ctx, loadRepoIDs)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoriesMapByIDs", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(loadRepoIDs) != len(repoMaps) {
|
||||
// Remove deleted repos from search results
|
||||
cleanedSearchResults := make([]*code_indexer.Result, 0, len(repoMaps))
|
||||
for _, sr := range searchResults {
|
||||
if _, found := repoMaps[sr.RepoID]; found {
|
||||
cleanedSearchResults = append(cleanedSearchResults, sr)
|
||||
}
|
||||
}
|
||||
|
||||
searchResults = cleanedSearchResults
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToExploreCodeSearchResults(total, searchResults, repoMaps))
|
||||
}
|
@ -81,6 +81,7 @@ func Code(ctx *context.Context) {
|
||||
RepoIDs: repoIDs,
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
IsHtmlSafe: true,
|
||||
Language: language,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
|
@ -59,6 +59,7 @@ func Search(ctx *context.Context) {
|
||||
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
IsHtmlSafe: true,
|
||||
Language: language,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
|
@ -80,6 +80,7 @@ func CodeSearch(ctx *context.Context) {
|
||||
Keyword: keyword,
|
||||
IsKeywordFuzzy: isFuzzy,
|
||||
Language: language,
|
||||
IsHtmlSafe: true,
|
||||
Paginator: &db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.RepoSearchPagingNum,
|
||||
|
27
services/convert/explore.go
Normal file
27
services/convert/explore.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
func ToExploreCodeSearchResults(total int, results []*code_indexer.Result, repoMaps map[int64]*repo_model.Repository) api.ExploreCodeResult {
|
||||
out := api.ExploreCodeResult{Total: total}
|
||||
for _, res := range results {
|
||||
if repo := repoMaps[res.RepoID]; repo != nil {
|
||||
for _, r := range res.Lines {
|
||||
out.Results = append(out.Results, api.ExploreCodeSearchItem{
|
||||
RepoName: repo.Name,
|
||||
FilePath: res.Filename,
|
||||
LineNumber: r.Num,
|
||||
LineText: r.RawContent,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user