Improved diff syntax highlighting fix (#12455)

Make previous fix from #12238 more robust since I saw a case where a diff changes only a single character in a chroma class instead of the entire thing. Add another more complicated test to match.

Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
mrsdizzie 2020-08-08 06:17:02 -04:00 committed by GitHub
parent 96add8c319
commit 0171dda728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 20 deletions

View File

@ -16,6 +16,7 @@ import (
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -180,55 +181,61 @@ var (
removedCodePrefix = []byte(`<span class="removed-code">`) removedCodePrefix = []byte(`<span class="removed-code">`)
codeTagSuffix = []byte(`</span>`) codeTagSuffix = []byte(`</span>`)
) )
var addSpanRegex = regexp.MustCompile(`<span class="[a-z]*$`)
func diffToHTML(fileName string, diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML { func diffToHTML(fileName string, diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
var addSpan bool var addSpan string
for i := range diffs { for i := range diffs {
switch { switch {
case diffs[i].Type == diffmatchpatch.DiffEqual: case diffs[i].Type == diffmatchpatch.DiffEqual:
// Looking for the case where our 3rd party diff library previously detected a string difference // Looking for the case where our 3rd party diff library previously detected a string difference
// in the middle of a span class because we highlight them first. This happens when added/deleted code // in the middle of a span class because we highlight them first. This happens when added/deleted code
// also changes the chroma class name. If found, just move the openining span code forward into the next section // also changes the chroma class name, either partially or fully. If found, just move the openining span code forward into the next section
if addSpan { // see TestDiffToHTML for examples
diffs[i].Text = "<span class=\"" + diffs[i].Text if len(addSpan) > 0 {
diffs[i].Text = addSpan + diffs[i].Text
addSpan = ""
} }
if strings.HasSuffix(diffs[i].Text, "<span class=\"") { m := addSpanRegex.FindStringSubmatchIndex(diffs[i].Text)
addSpan = true if m != nil {
buf.WriteString(strings.TrimSuffix(diffs[i].Text, "<span class=\"")) addSpan = diffs[i].Text[m[0]:m[1]]
buf.WriteString(strings.TrimSuffix(diffs[i].Text, addSpan))
} else { } else {
addSpan = false addSpan = ""
buf.WriteString(getLineContent(diffs[i].Text)) buf.WriteString(getLineContent(diffs[i].Text))
} }
case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd: case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd:
if addSpan { if len(addSpan) > 0 {
addSpan = false diffs[i].Text = addSpan + diffs[i].Text
diffs[i].Text = "<span class=\"" + diffs[i].Text addSpan = ""
} }
// Print existing closing span first before opening added-code span so it doesn't unintentionally close it // Print existing closing span first before opening added-code span so it doesn't unintentionally close it
if strings.HasPrefix(diffs[i].Text, "</span>") { if strings.HasPrefix(diffs[i].Text, "</span>") {
buf.WriteString("</span>") buf.WriteString("</span>")
diffs[i].Text = strings.TrimPrefix(diffs[i].Text, "</span>") diffs[i].Text = strings.TrimPrefix(diffs[i].Text, "</span>")
} }
if strings.HasSuffix(diffs[i].Text, "<span class=\"") { m := addSpanRegex.FindStringSubmatchIndex(diffs[i].Text)
addSpan = true if m != nil {
diffs[i].Text = strings.TrimSuffix(diffs[i].Text, "<span class=\"") addSpan = diffs[i].Text[m[0]:m[1]]
diffs[i].Text = strings.TrimSuffix(diffs[i].Text, addSpan)
} }
buf.Write(addedCodePrefix) buf.Write(addedCodePrefix)
buf.WriteString(getLineContent(diffs[i].Text)) buf.WriteString(getLineContent(diffs[i].Text))
buf.Write(codeTagSuffix) buf.Write(codeTagSuffix)
case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel: case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel:
if addSpan { if len(addSpan) > 0 {
addSpan = false diffs[i].Text = addSpan + diffs[i].Text
diffs[i].Text = "<span class=\"" + diffs[i].Text addSpan = ""
} }
if strings.HasPrefix(diffs[i].Text, "</span>") { if strings.HasPrefix(diffs[i].Text, "</span>") {
buf.WriteString("</span>") buf.WriteString("</span>")
diffs[i].Text = strings.TrimPrefix(diffs[i].Text, "</span>") diffs[i].Text = strings.TrimPrefix(diffs[i].Text, "</span>")
} }
if strings.HasSuffix(diffs[i].Text, "<span class=\"") { m := addSpanRegex.FindStringSubmatchIndex(diffs[i].Text)
addSpan = true if m != nil {
diffs[i].Text = strings.TrimSuffix(diffs[i].Text, "<span class=\"") addSpan = diffs[i].Text[m[0]:m[1]]
diffs[i].Text = strings.TrimSuffix(diffs[i].Text, addSpan)
} }
buf.Write(removedCodePrefix) buf.Write(removedCodePrefix)
buf.WriteString(getLineContent(diffs[i].Text)) buf.WriteString(getLineContent(diffs[i].Text))

View File

@ -50,6 +50,16 @@ func TestDiffToHTML(t *testing.T) {
{Type: dmp.DiffInsert, Text: "</span> <span class=\"o\">||</span> <span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nx\">GuessLanguage</span><span class=\"p\">)"}, {Type: dmp.DiffInsert, Text: "</span> <span class=\"o\">||</span> <span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nx\">GuessLanguage</span><span class=\"p\">)"},
{Type: dmp.DiffEqual, Text: "</span> <span class=\"p\">{</span>"}, {Type: dmp.DiffEqual, Text: "</span> <span class=\"p\">{</span>"},
}, DiffLineAdd)) }, DiffLineAdd))
assertEqual(t, "<span class=\"nx\">tagURL</span> <span class=\"o\">:=</span> <span class=\"removed-code\"><span class=\"nx\">fmt</span><span class=\"p\">.</span><span class=\"nf\">Sprintf</span><span class=\"p\">(</span><span class=\"s\">&#34;## [%s](%s/%s/%s/%s?q=&amp;type=all&amp;state=closed&amp;milestone=%d) - %s&#34;</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">Milestone\"</span></span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">BaseURL</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">Owner</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">Repo</span><span class=\"p\">,</span> <span class=\"nx\"><span class=\"removed-code\">from</span><span class=\"p\">,</span> <span class=\"nx\">milestoneID</span><span class=\"p\">,</span> <span class=\"nx\">time</span><span class=\"p\">.</span><span class=\"nf\">Now</span><span class=\"p\">(</span><span class=\"p\">)</span><span class=\"p\">.</span><span class=\"nf\">Format</span><span class=\"p\">(</span><span class=\"s\">&#34;2006-01-02&#34;</span><span class=\"p\">)</span></span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
{Type: dmp.DiffEqual, Text: "<span class=\"nx\">tagURL</span> <span class=\"o\">:=</span> <span class=\"n"},
{Type: dmp.DiffDelete, Text: "x\">fmt</span><span class=\"p\">.</span><span class=\"nf\">Sprintf</span><span class=\"p\">(</span><span class=\"s\">&#34;## [%s](%s/%s/%s/%s?q=&amp;type=all&amp;state=closed&amp;milestone=%d) - %s&#34;</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">Milestone\""},
{Type: dmp.DiffInsert, Text: "f\">getGiteaTagURL</span><span class=\"p\">(</span><span class=\"nx\">client"},
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">BaseURL</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">Owner</span><span class=\"p\">,</span> <span class=\"nx\">ge</span><span class=\"p\">.</span><span class=\"nx\">Repo</span><span class=\"p\">,</span> <span class=\"nx\">"},
{Type: dmp.DiffDelete, Text: "from</span><span class=\"p\">,</span> <span class=\"nx\">milestoneID</span><span class=\"p\">,</span> <span class=\"nx\">time</span><span class=\"p\">.</span><span class=\"nf\">Now</span><span class=\"p\">(</span><span class=\"p\">)</span><span class=\"p\">.</span><span class=\"nf\">Format</span><span class=\"p\">(</span><span class=\"s\">&#34;2006-01-02&#34;</span><span class=\"p\">)"},
{Type: dmp.DiffInsert, Text: "ge</span><span class=\"p\">.</span><span class=\"nx\">Milestone</span><span class=\"p\">,</span> <span class=\"nx\">from</span><span class=\"p\">,</span> <span class=\"nx\">milestoneID"},
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">)</span>"},
}, DiffLineDel))
} }
func TestParsePatch(t *testing.T) { func TestParsePatch(t *testing.T) {