Files
Gitea/services/gitdiff/gitdiff_excerpt.go
T
wxiaoguang a7df0d5f41 fix: git diff blob excerpt (#38808)
1. refactor the legacy code and add more comments, remove the "+1/-1"
tricks, clarify the BuildBlobExcerptDiffSection behavior
2. fix a line-counting bug (see screenshot below)
2026-08-07 02:44:55 +00:00

133 lines
4.6 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitdiff
import (
"bufio"
"bytes"
"fmt"
"html/template"
"io"
"gitea.dev/modules/setting"
"github.com/alecthomas/chroma/v2"
)
type BlobExcerptOptions struct {
// More details in DiffLineSectionInfo struct
LastLeft int
LastRight int
LeftIndex int
RightIndex int
LeftHunkSize int
RightHunkSize int
Direction string
Language string
}
func (diffSection *DiffSection) fillExcerptLines(reader io.Reader, leftStart, rightStart, chunkSize int) error {
buf := &bytes.Buffer{}
scanner := bufio.NewScanner(reader)
var diffLines []*DiffLine
for rightLineIdx := 1; rightLineIdx < rightStart+chunkSize; rightLineIdx++ {
if ok := scanner.Scan(); !ok {
break
}
lineText := scanner.Text()
if buf.Len()+len(lineText) < int(setting.UI.MaxDisplayFileSize) {
buf.WriteString(lineText)
buf.WriteByte('\n')
}
if rightLineIdx < rightStart {
continue
}
diffLine := &DiffLine{
LeftIdx: leftStart + (rightLineIdx - rightStart),
RightIdx: rightLineIdx,
Type: DiffLinePlain,
Content: " " + lineText,
}
diffLines = append(diffLines, diffLine)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("fillExcerptLines scan: %w", err)
}
diffSection.Lines = diffLines
// DiffLinePlain always uses right lines
diffSection.highlightedRightLines.value = highlightCodeLines(diffSection.FileName, diffSection.language.value, []*DiffSection{diffSection}, false /* right */, buf.Bytes())
return nil
}
func BuildBlobExcerptDiffSection(filePath string, reader io.Reader, opts BlobExcerptOptions) (*DiffSection, error) {
lastLeft, lastRight, idxLeft, idxRight := opts.LastLeft, opts.LastRight, opts.LeftIndex, opts.RightIndex
leftHunkSize, rightHunkSize, direction := opts.LeftHunkSize, opts.RightHunkSize, opts.Direction
expandLimit := BlobExcerptChunkSize
section := &DiffSection{
language: &diffVarMutable[string]{value: opts.Language},
highlightLexer: &diffVarMutable[chroma.Lexer]{},
highlightedLeftLines: &diffVarMutable[map[int]template.HTML]{},
highlightedRightLines: &diffVarMutable[map[int]template.HTML]{},
FileName: filePath,
}
var err error
remainingLines := idxRight - lastRight
if direction == "up" && remainingLines > expandLimit {
idxLeft -= expandLimit
idxRight -= expandLimit
leftHunkSize += expandLimit
rightHunkSize += expandLimit
err = section.fillExcerptLines(reader, idxLeft, idxRight, expandLimit)
} else if direction == "down" && remainingLines > expandLimit {
err = section.fillExcerptLines(reader, lastLeft+1, lastRight+1, expandLimit)
lastLeft += expandLimit
lastRight += expandLimit
} else /* "single" or [ ("up" or "down") and (remainingLines <= expandLimit) ] */ {
if direction == "up" || direction == "single" {
// if the direction is "up" or "single":
// * top: last=0, idx=11, chunk=11: line 11 is already rendered, line 0 can be considered as a "virtually rendered line"
// * then need to expand line 10 lines (1-10), so "-1".
// * middle: last=100, idx=106, chunk=6: line 100 and 106 are both already rendered
// * then need to expand 5 lines (101-105), so "-1".
expandLimit = remainingLines - 1
} else {
// if the direction is "down": either the hidden lines are too many in the middle (otherwise "single"), or are at the bottom
// * "last" line is already rendered, so just render the remaining lines from the next line
expandLimit = remainingLines
}
err = section.fillExcerptLines(reader, lastLeft+1, lastRight+1, expandLimit)
// now, the hidden lines are fewer than "expand limit", after expand, no hidden lines anymore,
// no need to show new "expand buttons" (setting them to 0 will make GetExpandDirection returns "no direction")
leftHunkSize, rightHunkSize, idxLeft, idxRight = 0, 0, 0, 0
}
if err != nil {
return nil, err
}
newLineSection := &DiffLine{
Type: DiffLineSection,
SectionInfo: &DiffLineSectionInfo{
language: &diffVarMutable[string]{value: opts.Language},
Path: filePath,
LastLeftIdx: lastLeft,
LastRightIdx: lastRight,
LeftIdx: idxLeft,
RightIdx: idxRight,
LeftHunkSize: leftHunkSize,
RightHunkSize: rightHunkSize,
},
}
if newLineSection.GetExpandDirection() != "" {
newLineSection.Content = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize)
switch direction {
case "up":
section.Lines = append([]*DiffLine{newLineSection}, section.Lines...)
case "down":
section.Lines = append(section.Lines, newLineSection)
}
}
return section, nil
}