Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
wxiaoguang committed Apr 1, 2024
1 parent 1ef2eb5 commit 58508a7
Show file tree
Hide file tree
Showing 16 changed files with 342 additions and 7 deletions.
6 changes: 3 additions & 3 deletions modules/indexer/code/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ func writeStrings(buf *bytes.Buffer, strs ...string) error {
return nil
}

func HighlightSearchResultCode(filename string, lineNums []int, code string) []ResultLine {
func HighlightSearchResultCode(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
hl, _ := highlight.Code(filename, "", code)
hl, _ := highlight.Code(filename, language, code)
highlightedLines := strings.Split(string(hl), "\n")

// The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
Expand Down Expand Up @@ -122,7 +122,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
UpdatedUnix: result.UpdatedUnix,
Language: result.Language,
Color: result.Color,
Lines: HighlightSearchResultCode(result.Filename, lineNums, formattedLinesBuffer.String()),
Lines: HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()),
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions modules/markup/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ type processor func(ctx *RenderContext, node *html.Node)
var defaultProcessors = []processor{
fullIssuePatternProcessor,
comparePatternProcessor,
codePreviewPatternProcessor,
fullHashPatternProcessor,
shortLinkProcessor,
linkProcessor,
Expand Down
75 changes: 75 additions & 0 deletions modules/markup/html_codepreview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package markup

import (
"html/template"
"net/url"
"regexp"
"strconv"
"strings"

"code.gitea.io/gitea/modules/httplib"

"golang.org/x/net/html"
)

// codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20"
var codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`)

type RenderCodePreviewOptions struct {
FullURL string
OwnerName string
RepoName string
CommitID string
FilePath string

LineStart, LineStop int
}

func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosStop int, htm template.HTML, err error) {
m := codePreviewPattern.FindStringSubmatchIndex(node.Data)
if m == nil {
return 0, 0, "", nil
}

opts := RenderCodePreviewOptions{
FullURL: node.Data[m[0]:m[1]],
OwnerName: node.Data[m[2]:m[3]],
RepoName: node.Data[m[4]:m[5]],
CommitID: node.Data[m[6]:m[7]],
FilePath: node.Data[m[8]:m[9]],
}
if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
return 0, 0, "", nil
}
u, err := url.Parse(opts.FilePath)
if err != nil {
return 0, 0, "", err
}
opts.FilePath = strings.TrimPrefix(u.Path, "/")

lineStartStr, lineStopStr, _ := strings.Cut(node.Data[m[10]:m[11]], "-")
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
opts.LineStart, opts.LineStop = lineStart, lineStop
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx.Ctx, opts)
return m[0], m[1], h, err
}

func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
for node != nil {
urlPosStart, urlPosEnd, h, err := renderCodeBlock(ctx, node)
if err != nil || h == "" {
node = node.NextSibling
continue
}
next := node.NextSibling
nodeText := node.Data
node.Data = nodeText[:urlPosStart]
node.Parent.InsertBefore(&html.Node{Type: html.RawNode, Data: string(h)}, next)
node.Parent.InsertBefore(&html.Node{Type: html.TextNode, Data: nodeText[urlPosEnd:]}, next)
node = next
}
}
34 changes: 34 additions & 0 deletions modules/markup/html_codepreview_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package markup_test

import (
"context"
"html/template"
"strings"
"testing"

"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"

"github.com/stretchr/testify/assert"
)

func TestRenderCodePreview(t *testing.T) {
markup.Init(&markup.ProcessorHelper{
RenderRepoFileCodePreview: func(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) {
return "<div>code preview</div>", nil
},
})
test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Type: "markdown",
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
}
3 changes: 3 additions & 0 deletions modules/markup/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"errors"
"fmt"
"html/template"
"io"
"net/url"
"path/filepath"
Expand All @@ -33,6 +34,8 @@ type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool

ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute

RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
}

var DefaultProcessorHelper ProcessorHelper
Expand Down
8 changes: 7 additions & 1 deletion modules/markup/sanitizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ func createDefaultPolicy() *bluemonday.Policy {
policy := bluemonday.UGCPolicy()

// For JS code copy and Mermaid loading state
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-block( is-loading)?$`)).OnElements("pre")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-block( is-loading)?$`)).Globally()

// For code preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-preview-[-\w]+$`)).Globally()
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-num$`)).Globally()
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-code chroma$`)).Globally()
policy.AllowAttrs("data-line-number").OnElements("span")

// For color preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span")
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func Search(ctx *context.Context) {
// UpdatedUnix: not supported yet
// Language: not supported yet
// Color: not supported yet
Lines: code_indexer.HighlightSearchResultCode(r.Filename, r.LineNumbers, strings.Join(r.LineCodes, "\n")),
Lines: code_indexer.HighlightSearchResultCode(r.Filename, "", r.LineNumbers, strings.Join(r.LineCodes, "\n")),
})
}
}
Expand Down
1 change: 1 addition & 0 deletions services/contexttest/context_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
base.Locale = &translation.MockLocale{}

ctx := context.NewWebContext(base, opt.Render, nil)
ctx.AppendContextValue(context.WebContextKey, ctx)
ctx.PageData = map[string]any{}
ctx.Data["PageStartTime"] = time.Now()
chiCtx := chi.NewRouteContext()
Expand Down
2 changes: 1 addition & 1 deletion services/markup/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ import (

func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
FixtureFiles: []string{"user.yml"},
FixtureFiles: []string{"user.yml", "repository.yml", "access.yml", "repo_unit.yml"},
})
}
2 changes: 2 additions & 0 deletions services/markup/processorhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
func ProcessorHelper() *markup.ProcessorHelper {
return &markup.ProcessorHelper{
ElementDir: "auto", // set dir="auto" for necessary (eg: <p>, <h?>, etc) tags

RenderRepoFileCodePreview: renderRepoFileCodePreview,
IsUsernameMentionable: func(ctx context.Context, username string) bool {
mentionedUser, err := user.GetUserByName(ctx, username)
if err != nil {
Expand Down
106 changes: 106 additions & 0 deletions services/markup/processorhelper_codepreview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package markup

import (
"bufio"
"context"
"fmt"
"html/template"
"strings"

"code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/repository/files"
)

func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) {
opts.LineStop = max(opts.LineStop, 1)
lineCount := opts.LineStop - opts.LineStart + 1
if lineCount <= 0 || lineCount > 140 /* GitHub at most show 140 lines */ {
lineCount = 10
opts.LineStop = opts.LineStart + lineCount
}

dbRepo, err := repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName)
if err != nil {
return "", err
}

webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context)
if !ok {
return "", fmt.Errorf("context is not a web context")
}
doer := webCtx.Doer

perms, err := access.GetUserRepoPermission(ctx, dbRepo, doer)
if err != nil {
return "", err
}
if !perms.CanRead(unit.TypeCode) {
return "", fmt.Errorf("no permission")
}

gitRepo, err := gitrepo.OpenRepository(ctx, dbRepo)
if err != nil {
return "", err
}
defer gitRepo.Close()

commit, err := gitRepo.GetCommit(opts.CommitID)
if err != nil {
return "", err
}

language, _ := files.TryGetContentLanguage(gitRepo, opts.CommitID, opts.FilePath)
blob, err := commit.GetBlobByPath(opts.FilePath)
if err != nil {
return "", err
}

if blob.Size() > setting.UI.MaxDisplayFileSize {
return "", fmt.Errorf("file is too large")
}

dataRc, err := blob.DataAsync()
if err != nil {
return "", err
}
defer dataRc.Close()

reader := bufio.NewReader(dataRc)

for i := 1; i < opts.LineStart; i++ {
if _, err = reader.ReadBytes('\n'); err != nil {
return "", err
}
}

lineNums := make([]int, 0, lineCount)
lineCodes := make([]string, 0, lineCount)
for i := opts.LineStart; i <= opts.LineStop; i++ {
if line, err := reader.ReadString('\n'); err != nil {
break
} else {
lineNums = append(lineNums, i)
lineCodes = append(lineCodes, line)
}
}
highlightLines := code.HighlightSearchResultCode(opts.FilePath, language, lineNums, strings.Join(lineCodes, ""))
return webCtx.RenderToHTML("base/markup_codepreview", map[string]any{
"FullURL": opts.FullURL,
"FilePath": opts.FilePath,
"LineStart": opts.LineStart,
"LineStop": opts.LineStop,
"RepoLink": dbRepo.Link(),
"CommitID": opts.CommitID,
"HighlightLines": highlightLines,
})
}
60 changes: 60 additions & 0 deletions services/markup/processorhelper_codepreview_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package markup

import (
"testing"

"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/contexttest"

"github.com/stretchr/testify/assert"
)

func TestProcessorHelperCodePreview(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
htm, err := renderRepoFileCodePreview(ctx, markup.RenderCodePreviewOptions{
FullURL: "http://full",
OwnerName: "user2",
RepoName: "repo1",
CommitID: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
FilePath: "/README.md",
LineStart: 1,
LineStop: 10,
})
assert.NoError(t, err)
assert.Equal(t, `<div class="code-preview-container">
<div class="code-preview-header">
<a href="http://full" class="muted" rel="nofollow">/README.md</a>
Lines 1 to 10 in
<a href="/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow">65f1bf27bc</a>
</div>
<table>
<tbody><tr>
<td class="lines-num"><span data-line-number="1"></span></td>
<td class="lines-code chroma"><span class="gh"># repo1</td>
</tr><tr>
<td class="lines-num"><span data-line-number="2"></span></td>
<td class="lines-code chroma"></span><span class="gh"></span></td>
</tr></tbody>
</table>
</div>
`, string(htm))

ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
_, err = renderRepoFileCodePreview(ctx, markup.RenderCodePreviewOptions{
FullURL: "http://full",
OwnerName: "user15",
RepoName: "big_test_private_1",
CommitID: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
FilePath: "/README.md",
LineStart: 1,
LineStop: 10,
})
assert.ErrorContains(t, err, "no permission")
}
17 changes: 17 additions & 0 deletions templates/base/markup_codepreview.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="code-preview-container">
<div class="code-preview-header">
<a href="{{.FullURL}}" class="muted" rel="nofollow">{{.FilePath}}</a>
Lines {{.LineStart}} to {{.LineStop}} in
<a href="{{.RepoLink}}/src/commit/{{.CommitID}}" rel="nofollow">{{.CommitID | ShortSha}}</a>
</div>
<table>
<tbody>
{{- range .HighlightLines -}}
<tr>
<td class="lines-num"><span data-line-number="{{.Num}}"></span></td>
<td class="lines-code chroma">{{.FormattedContent}}</td>
</tr>
{{- end -}}
</tbody>
</table>
</div>
Loading

0 comments on commit 58508a7

Please sign in to comment.