enhance: truncate but show long lines in diffs (#39279)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Sergio Benitez
2026-09-12 13:56:25 +02:00
committed by GitHub
parent da37b7916b
commit 1e13badb39
13 changed files with 430 additions and 222 deletions
+12 -5
View File
@@ -8,6 +8,7 @@ import (
"io"
"strings"
"gitea.dev/modules/htmlutil"
"gitea.dev/modules/setting"
"gitea.dev/modules/translation"
)
@@ -27,13 +28,19 @@ func EscapeOptionsForView() EscapeOptions {
}
}
func EscapeControlHTMLTo(html template.HTML, locale translation.Locale, w htmlutil.HTMLWriter, opts ...EscapeOptions) *EscapeStatus {
if !setting.UI.AmbiguousUnicodeDetection {
w.WriteHTML(html)
return &EscapeStatus{}
}
escaped, _ := EscapeControlReader(strings.NewReader(string(html)), w.OriginWriter(), locale, opts...)
return escaped
}
// EscapeControlHTML escapes the Unicode control sequences in a provided html document
func EscapeControlHTML(html template.HTML, locale translation.Locale, opts ...EscapeOptions) (escaped *EscapeStatus, output template.HTML) {
if !setting.UI.AmbiguousUnicodeDetection {
return &EscapeStatus{}, html
}
sb := &strings.Builder{}
escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, opts...) // err has been handled in EscapeControlReader
sb, w := htmlutil.NewHTMLStringWriter()
escaped = EscapeControlHTMLTo(html, locale, w, opts...)
return escaped, template.HTML(sb.String())
}
+9 -1
View File
@@ -140,6 +140,14 @@ func isHeader(lof string, inHunk bool) bool {
return strings.HasPrefix(lof, cmdDiffHead) || (!inHunk && (strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")))
}
func NewGitDiffScanner(r io.Reader) *bufio.Scanner {
// TODO: GIT-DIFF-PARSE-LONG-LINE: ideally it shouldn't use bufio.Scanner which has a limit.
// It will cause errors if a line is very long.
scanner := bufio.NewScanner(r)
scanner.Buffer(nil, max(512*1024, int(setting.UI.MaxDisplayFileSize/16)))
return scanner
}
// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
// it also recalculates hunks and adds the appropriate headers to the new diff.
// Warning: Only one-file diffs are allowed.
@@ -149,7 +157,7 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
return "", nil
}
scanner := bufio.NewScanner(originalDiff)
scanner := NewGitDiffScanner(originalDiff)
hunk := make([]string, 0)
// begin is the start of the hunk containing searched line
+7 -2
View File
@@ -93,7 +93,7 @@ type HTMLWriter interface {
OriginWriter() io.Writer
WriteString(s string) HTMLWriter
WriteHTML(s template.HTML) HTMLWriter
WriteFormat(fmt template.HTML, args ...any) HTMLWriter
WriteFormatf(fmt template.HTML, args ...any) HTMLWriter
Err() error
}
@@ -120,7 +120,7 @@ func (h *htmlWriter) WriteHTML(s template.HTML) HTMLWriter {
return h
}
func (h *htmlWriter) WriteFormat(fmt template.HTML, args ...any) HTMLWriter {
func (h *htmlWriter) WriteFormatf(fmt template.HTML, args ...any) HTMLWriter {
if _, err := HTMLPrintf(h.w, fmt, args...); err != nil {
h.errs = append(h.errs, err)
}
@@ -135,6 +135,11 @@ func NewHTMLWriter(w io.Writer) HTMLWriter {
return &htmlWriter{w: w}
}
func NewHTMLStringWriter() (*strings.Builder, HTMLWriter) {
sb := &strings.Builder{}
return sb, &htmlWriter{w: sb}
}
type HTMLBuilder struct {
sb strings.Builder
}
+1 -1
View File
@@ -34,7 +34,7 @@ func TestHTMLBuilder(t *testing.T) {
func TestHTMLWriter(t *testing.T) {
sb := new(strings.Builder)
w := NewHTMLWriter(sb)
w.WriteString("<").WriteHTML("<hr>").WriteFormat("<span>%s%s</span>", ">", EscapeString(">"))
w.WriteString("<").WriteHTML("<hr>").WriteFormatf("<span>%s%s</span>", ">", EscapeString(">"))
assert.Equal(t, "&lt;<hr><span>&gt;&gt;</span>", sb.String())
assert.NoError(t, w.Err())
}
+13 -13
View File
@@ -39,18 +39,18 @@ type mimeHandler struct {
}
func renderCellCodeOutputTextPlain(w htmlutil.HTMLWriter, text string) error {
w.WriteFormat(`<div class="cell-output-text"><pre>%s</pre></div>`, text)
w.WriteFormatf(`<div class="cell-output-text"><pre>%s</pre></div>`, text)
return w.Err()
}
func renderCellCodeOutputUnsupported(w htmlutil.HTMLWriter, message string) error {
w.WriteFormat(`<div class="cell-output-unsupported">%s</div>`, message)
w.WriteFormatf(`<div class="cell-output-unsupported">%s</div>`, message)
return w.Err()
}
var dataMimeHandlers = sync.OnceValue(func() []mimeHandler {
renderImage := func(w htmlutil.HTMLWriter, subtype, payload string) error {
w.WriteFormat(`<div class="cell-output-image"><img src="data:image/%s;base64,%s"></div>`, subtype, payload)
w.WriteFormatf(`<div class="cell-output-image"><img src="data:image/%s;base64,%s"></div>`, subtype, payload)
return w.Err()
}
renderUnsupportedOutput := func(message string) func(htmlutil.HTMLWriter, string) error {
@@ -75,11 +75,11 @@ var dataMimeHandlers = sync.OnceValue(func() []mimeHandler {
// To future developers: don't allow custom CSS classes or attributes,
// because ".link-action" or "data-fetch-xxx" can send POST requests and lead to XSS.
// If you'd really like to support more, do remember to correctly sanitize the values.
w.WriteFormat(`<div class="cell-output-html">%s</div>`, markup.Sanitize(d))
w.WriteFormatf(`<div class="cell-output-html">%s</div>`, markup.Sanitize(d))
return w.Err()
}},
{"text/latex", func(w htmlutil.HTMLWriter, d string) error {
w.WriteFormat(`<div class="cell-output-latex"><pre><code class="language-math display">%s</code></pre></div>`, trimMathDelimiters(d))
w.WriteFormatf(`<div class="cell-output-latex"><pre><code class="language-math display">%s</code></pre></div>`, trimMathDelimiters(d))
return w.Err()
}},
{"text/plain", renderCellCodeOutputTextPlain},
@@ -142,14 +142,14 @@ func (renderer) Render(ctx *markup.RenderContext, input io.Reader, outputWriter
// the size is (should be) checked and/or limited by the caller to avoid OOM
var notebook Notebook
if err := json.NewDecoder(input).Decode(&notebook); err != nil {
htmlWriter.WriteFormat(`<div class="ui error message">Failed to parse notebook JSON: %v</div>`, err)
htmlWriter.WriteFormatf(`<div class="ui error message">Failed to parse notebook JSON: %v</div>`, err)
return htmlWriter.Err()
}
// Check nbformat version
if notebook.Nbformat < 4 {
msg := htmlutil.HTMLFormat("This notebook uses an older format (nbformat %d). Only nbformat 4+ is supported for rendering. Please upgrade the notebook in Jupyter or view the raw JSON.", notebook.Nbformat)
htmlWriter.WriteFormat(`<div class="file-not-rendered-prompt">%s</div>`, msg)
htmlWriter.WriteFormatf(`<div class="file-not-rendered-prompt">%s</div>`, msg)
return htmlWriter.Err()
}
@@ -205,7 +205,7 @@ func renderCellCode(output htmlutil.HTMLWriter, cell Cell, language string) erro
output.WriteHTML(`<div class="cell-line">`)
{
if executionCount != nil {
output.WriteFormat(`<div class="cell-left cell-prompt">In [%d]:</div>`, *executionCount)
output.WriteFormatf(`<div class="cell-left cell-prompt">In [%d]:</div>`, *executionCount)
} else {
output.WriteHTML(`<div class="cell-left cell-prompt">In [ ]:</div>`)
}
@@ -213,7 +213,7 @@ func renderCellCode(output htmlutil.HTMLWriter, cell Cell, language string) erro
// Highlight code
preAttrs, codeAttrs := highlight.CodeBlockAttributes(language)
lexer := highlight.DetectChromaLexerByFileName("", language)
output.WriteFormat(`<div class="cell-right cell-input"><pre %s><code %s>`, preAttrs, codeAttrs)
output.WriteFormatf(`<div class="cell-right cell-input"><pre %s><code %s>`, preAttrs, codeAttrs)
output.WriteHTML(highlight.RenderCodeByLexer(lexer, source))
output.WriteHTML("</code></pre></div>")
}
@@ -232,7 +232,7 @@ func renderCellCode(output htmlutil.HTMLWriter, cell Cell, language string) erro
output.WriteHTML(`<div class="cell-line">`)
{
if hasExecutionResult && executionCount != nil {
output.WriteFormat(`<div class="cell-left cell-prompt">Out [%d]:</div>`, *executionCount)
output.WriteFormatf(`<div class="cell-left cell-prompt">Out [%d]:</div>`, *executionCount)
} else {
output.WriteHTML(`<div class="cell-left cell-prompt"></div>`)
}
@@ -250,7 +250,7 @@ func renderCellCode(output htmlutil.HTMLWriter, cell Cell, language string) erro
}
func renderCellPrompt(output htmlutil.HTMLWriter, left, right template.HTML) {
output.WriteFormat(`
output.WriteFormatf(`
<div class="notebook-cell">
<div class="cell-line">
<div class="cell-left cell-prompt">%s</div>
@@ -335,7 +335,7 @@ func renderCellCodeOutput(output htmlutil.HTMLWriter, out Output) {
// Stream output
if out.OutputType == "stream" && out.Text != nil {
streamName := util.Iif(out.Name == "stderr", "stderr", "stdout")
output.WriteFormat(`<pre class="cell-output-stream stream-%s">%s</pre>`, streamName, joinSource(out.Text))
output.WriteFormatf(`<pre class="cell-output-stream stream-%s">%s</pre>`, streamName, joinSource(out.Text))
return
}
@@ -352,7 +352,7 @@ func renderCellCodeOutput(output htmlutil.HTMLWriter, out Output) {
if traceback == "" && out.Ename != "" {
traceback = fmt.Sprintf("%s: %s", out.Ename, out.Evalue)
}
output.WriteFormat(`<pre class="cell-output-error">%s</pre>`, traceback)
output.WriteFormatf(`<pre class="cell-output-error">%s</pre>`, traceback)
return
}