Add setting to restrict count of lines being displayed & only highlight those lines

This commit is contained in:
Mai-Lapyst 2024-03-25 16:05:01 +01:00
parent 069d87b80f
commit 2b6546adc9
No known key found for this signature in database
GPG key ID: F88D929C09E239F8
9 changed files with 117 additions and 28 deletions

View file

@ -2338,6 +2338,8 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits) ;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 5000 ;MERMAID_MAX_SOURCE_CHARACTERS = 5000
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
;FILEPREVIEW_MAX_LINES = 50
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -4,6 +4,7 @@
package markup package markup
import ( import (
"bufio"
"bytes" "bytes"
"html/template" "html/template"
"regexp" "regexp"
@ -12,6 +13,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
@ -31,9 +33,15 @@ type FilePreview struct {
filePath string filePath string
start int start int
end int end int
isTruncated bool
} }
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview { func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
if (setting.FilePreviewMaxLines == 0) {
// Feature is disabled
return nil
}
preview := &FilePreview{} preview := &FilePreview{}
m := filePreviewPattern.FindStringSubmatchIndex(node.Data) m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
@ -63,18 +71,20 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.end = m[1] preview.end = m[1]
projPathSegments := strings.Split(projPath, "/") projPathSegments := strings.Split(projPath, "/")
fileContent, err := DefaultProcessorHelper.GetRepoFileContent( var language string
fileBlob, err := DefaultProcessorHelper.GetRepoFileBlob(
ctx.Ctx, ctx.Ctx,
projPathSegments[len(projPathSegments)-2], projPathSegments[len(projPathSegments)-2],
projPathSegments[len(projPathSegments)-1], projPathSegments[len(projPathSegments)-1],
commitSha, preview.filePath, commitSha, preview.filePath,
&language,
) )
if err != nil { if err != nil {
return nil return nil
} }
lineSpecs := strings.Split(hash, "-") lineSpecs := strings.Split(hash, "-")
lineCount := len(fileContent) // lineCount := len(fileContent)
commitLinkBuffer := new(bytes.Buffer) commitLinkBuffer := new(bytes.Buffer)
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black")) err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
@ -82,28 +92,31 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
log.Error("failed to render commitLink: %v", err) log.Error("failed to render commitLink: %v", err)
} }
if len(lineSpecs) == 1 { var startLine, endLine int
line, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
if line < 1 || line > lineCount {
return nil
}
preview.fileContent = fileContent[line-1 : line] if len(lineSpecs) == 1 {
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
endLine = startLine
// if line < 1 || line > lineCount {
// return nil
// }
// preview.fileContent = fileContent[line-1 : line]
preview.subTitle = locale.Tr( preview.subTitle = locale.Tr(
"markup.filepreview.line", line, "markup.filepreview.line", startLine,
template.HTML(commitLinkBuffer.String()), template.HTML(commitLinkBuffer.String()),
) )
preview.lineOffset = line - 1 preview.lineOffset = startLine - 1
} else { } else {
startLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L")) startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
endLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L")) endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine { // if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
return nil // return nil
} // }
preview.fileContent = fileContent[startLine-1 : endLine] // preview.fileContent = fileContent[startLine-1 : endLine]
preview.subTitle = locale.Tr( preview.subTitle = locale.Tr(
"markup.filepreview.lines", startLine, endLine, "markup.filepreview.lines", startLine, endLine,
template.HTML(commitLinkBuffer.String()), template.HTML(commitLinkBuffer.String()),
@ -112,6 +125,50 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
preview.lineOffset = startLine - 1 preview.lineOffset = startLine - 1
} }
lineCount := endLine - (startLine-1)
if startLine < 1 || endLine < 1 || lineCount < 1 {
return nil
}
if setting.FilePreviewMaxLines > 0 && lineCount > setting.FilePreviewMaxLines {
preview.isTruncated = true
lineCount = setting.FilePreviewMaxLines
}
dataRc, err := fileBlob.DataAsync()
if err != nil {
return nil
}
defer dataRc.Close()
reader := bufio.NewReader(dataRc)
// skip all lines until we find our startLine
for i := 1; i < startLine; i++ {
_, err := reader.ReadBytes('\n')
if err != nil {
return nil
}
}
// capture the lines we're interested in
lineBuffer := new(bytes.Buffer)
for i := 0; i < lineCount; i++ {
buf, err := reader.ReadBytes('\n')
if err != nil {
break;
}
lineBuffer.Write(buf)
}
// highlight the file...
fileContent, _, err := highlight.File(fileBlob.Name(), language, lineBuffer.Bytes())
if err != nil {
log.Error("highlight.File failed, fallback to plain text: %v", err)
fileContent = highlight.PlainText(lineBuffer.Bytes())
}
preview.fileContent = fileContent
return preview return preview
} }
@ -258,6 +315,20 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}}, Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
} }
node.AppendChild(header) node.AppendChild(header)
if (p.isTruncated) {
warning := &html.Node{
Type: html.ElementNode,
Data: atom.Div.String(),
Attr: []html.Attribute{{Key: "class", Val: "ui warning message tw-text-left"}},
}
warning.AppendChild(&html.Node{
Type: html.TextNode,
Data: locale.TrString("markup.filepreview.truncated"),
})
node.AppendChild(warning)
}
node.AppendChild(twrapper) node.AppendChild(twrapper)
return node return node

View file

@ -1059,7 +1059,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.Metas == nil {
return return
} }
if DefaultProcessorHelper.GetRepoFileContent == nil { if DefaultProcessorHelper.GetRepoFileBlob == nil {
return return
} }

View file

@ -8,7 +8,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"html/template" // "html/template"
"io" "io"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -32,7 +32,7 @@ const (
type ProcessorHelper struct { type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool IsUsernameMentionable func(ctx context.Context, username string) bool
GetRepoFileContent func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error) GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
} }

View file

@ -135,6 +135,7 @@ func createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span") policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
policy.AllowAttrs("data-tooltip-content").OnElements("span") policy.AllowAttrs("data-tooltip-content").OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a") policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
// Allow generally safe attributes // Allow generally safe attributes
generalSafeAttrs := []string{ generalSafeAttrs := []string{

View file

@ -15,6 +15,7 @@ var (
ExternalMarkupRenderers []*MarkupRenderer ExternalMarkupRenderers []*MarkupRenderer
ExternalSanitizerRules []MarkupSanitizerRule ExternalSanitizerRules []MarkupSanitizerRule
MermaidMaxSourceCharacters int MermaidMaxSourceCharacters int
FilePreviewMaxLines int
) )
const ( const (
@ -62,6 +63,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "markdown", &Markdown) mustMapSetting(rootCfg, "markdown", &Markdown)
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000) MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10) ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10) ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)

View file

@ -3711,3 +3711,4 @@ submodule = Submodule
[markup] [markup]
filepreview.line = Line %[1]d in %[2]s filepreview.line = Line %[1]d in %[2]s
filepreview.lines = Lines %[1]d to %[2]d in %[3]s filepreview.lines = Lines %[1]d to %[2]d in %[3]s
filepreview.truncated = Preview has been truncated

View file

@ -6,15 +6,17 @@ package markup
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"io" // "html/template"
// "io"
"code.gitea.io/gitea/models/perm/access" "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/highlight" // "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
gitea_context "code.gitea.io/gitea/services/context" gitea_context "code.gitea.io/gitea/services/context"
@ -39,7 +41,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
// when using gitea context (web context), use user's visibility and user's permission to check // when using gitea context (web context), use user's visibility and user's permission to check
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer) return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
}, },
GetRepoFileContent: func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error) { GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName) repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -70,17 +72,21 @@ func ProcessorHelper() *markup.ProcessorHelper {
return nil, err return nil, err
} }
language, err := file_service.TryGetContentLanguage(gitRepo, commitSha, filePath) if language != nil {
*language, err = file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
if err != nil { if err != nil {
log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err) log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
} }
}
blob, err := commit.GetBlobByPath(filePath) blob, err := commit.GetBlobByPath(filePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dataRc, err := blob.DataAsync() return blob, nil
/*dataRc, err := blob.DataAsync()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -97,7 +103,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
fileContent = highlight.PlainText(buf) fileContent = highlight.PlainText(buf)
} }
return fileContent, nil return fileContent, nil*/
}, },
} }
} }

View file

@ -25,6 +25,12 @@
background: var(--color-box-header); background: var(--color-box-header);
} }
.markup .file-preview-box .warning {
border-radius: 0;
margin: 0;
padding: .5rem .5rem .5rem 1rem;
}
.markup .file-preview-box .header > a { .markup .file-preview-box .header > a {
display: block; display: block;
} }