@@ -283,187 +283,6 @@ func DiffInlineWithHighlightCode(fileName, language, code string) DiffInline {
283283 return DiffInline {EscapeStatus : status , Content : template .HTML (content )}
284284}
285285
286- // highlightCodeDiff is used to do diff with highlighted HTML code.
287- // It totally depends on Chroma's valid HTML output and its structure, do not use these functions for other purposes.
288- // The HTML tags will be replaced by Unicode placeholders: "<span>{TEXT}</span>" => "\uE000{TEXT}\uE001"
289- // These Unicode placeholders are friendly to the diff.
290- // Then after diff, the placeholders in diff result will be recovered to the HTML tags.
291- // It's guaranteed that the tags in final diff result are paired correctly.
292- type highlightCodeDiff struct {
293- placeholderBegin rune
294- placeholderMaxCount int
295- placeholderIndex int
296- placeholderTagMap map [rune ]string
297- tagPlaceholderMap map [string ]rune
298-
299- placeholderOverflowCount int
300-
301- lineWrapperTags []string
302- }
303-
304- func newHighlightCodeDiff () * highlightCodeDiff {
305- return & highlightCodeDiff {
306- placeholderBegin : rune (0xE000 ), // Private Use Unicode: U+E000..U+F8FF, BMP(0), 6400
307- placeholderMaxCount : 6400 ,
308- placeholderTagMap : map [rune ]string {},
309- tagPlaceholderMap : map [string ]rune {},
310- }
311- }
312-
313- // nextPlaceholder returns 0 if no more placeholder can be used
314- // the diff is done line by line, usually there are only a few (no more than 10) placeholders in one line
315- // so the placeholderMaxCount is impossible to be exhausted in real cases.
316- func (hcd * highlightCodeDiff ) nextPlaceholder () rune {
317- for hcd .placeholderIndex < hcd .placeholderMaxCount {
318- r := hcd .placeholderBegin + rune (hcd .placeholderIndex )
319- hcd .placeholderIndex ++
320- // only use non-existing (not used by code) rune as placeholders
321- if _ , ok := hcd .placeholderTagMap [r ]; ! ok {
322- return r
323- }
324- }
325- return 0 // no more available placeholder
326- }
327-
328- func (hcd * highlightCodeDiff ) isInPlaceholderRange (r rune ) bool {
329- return hcd .placeholderBegin <= r && r < hcd .placeholderBegin + rune (hcd .placeholderMaxCount )
330- }
331-
332- func (hcd * highlightCodeDiff ) collectUsedRunes (code string ) {
333- for _ , r := range code {
334- if hcd .isInPlaceholderRange (r ) {
335- // put the existing rune (used by code) in map, then this rune won't be used a placeholder anymore.
336- hcd .placeholderTagMap [r ] = ""
337- }
338- }
339- }
340-
341- func (hcd * highlightCodeDiff ) diffWithHighlight (filename , language , codeA , codeB string ) []diffmatchpatch.Diff {
342- hcd .collectUsedRunes (codeA )
343- hcd .collectUsedRunes (codeB )
344-
345- highlightCodeA := highlight .Code (filename , language , codeA )
346- highlightCodeB := highlight .Code (filename , language , codeB )
347-
348- highlightCodeA = hcd .convertToPlaceholders (highlightCodeA )
349- highlightCodeB = hcd .convertToPlaceholders (highlightCodeB )
350-
351- diffs := diffMatchPatch .DiffMain (highlightCodeA , highlightCodeB , true )
352- diffs = diffMatchPatch .DiffCleanupEfficiency (diffs )
353-
354- for i := range diffs {
355- hcd .recoverOneDiff (& diffs [i ])
356- }
357- return diffs
358- }
359-
360- func (hcd * highlightCodeDiff ) convertToPlaceholders (htmlCode string ) string {
361- var tagStack []string
362- res := strings.Builder {}
363-
364- firstRunForLineTags := hcd .lineWrapperTags == nil
365-
366- // the standard chroma highlight HTML is "<span class="line [hl]"><span class="cl"> ... </span></span>"
367- for {
368- // find the next HTML tag
369- pos1 := strings .IndexByte (htmlCode , '<' )
370- pos2 := strings .IndexByte (htmlCode , '>' )
371- if pos1 == - 1 || pos2 == - 1 || pos2 < pos1 {
372- break
373- }
374- tag := htmlCode [pos1 : pos2 + 1 ]
375-
376- // write the content before the tag into result string, and consume the tag in the string
377- res .WriteString (htmlCode [:pos1 ])
378- htmlCode = htmlCode [pos2 + 1 :]
379-
380- // the line wrapper tags should be removed before diff
381- if strings .HasPrefix (tag , `<span class="line` ) || strings .HasPrefix (tag , `<span class="cl"` ) {
382- if firstRunForLineTags {
383- // if this is the first run for converting, save the line wrapper tags for later use, they should be added back
384- hcd .lineWrapperTags = append (hcd .lineWrapperTags , tag )
385- }
386- htmlCode = strings .TrimSuffix (htmlCode , "</span>" )
387- continue
388- }
389-
390- var tagInMap string
391- if tag [1 ] == '/' { // for closed tag
392- if len (tagStack ) == 0 {
393- break // invalid diff result, no open tag but see close tag
394- }
395- // make sure the closed tag in map is related to the open tag, to make the diff algorithm can match the open/closed tags
396- // the closed tag will be recorded in the map by key "</span><!-- <span the-open> -->" for "<span the-open>"
397- tagInMap = tag + "<!-- " + tagStack [len (tagStack )- 1 ] + "-->"
398- tagStack = tagStack [:len (tagStack )- 1 ]
399- } else { // for open tag
400- tagInMap = tag
401- tagStack = append (tagStack , tag )
402- }
403-
404- // remember the placeholder and tag in the map
405- placeholder , ok := hcd .tagPlaceholderMap [tagInMap ]
406- if ! ok {
407- placeholder = hcd .nextPlaceholder ()
408- if placeholder != 0 {
409- hcd .tagPlaceholderMap [tagInMap ] = placeholder
410- hcd .placeholderTagMap [placeholder ] = tagInMap
411- }
412- }
413-
414- if placeholder != 0 {
415- res .WriteRune (placeholder ) // use the placeholder to replace the tag
416- } else {
417- // unfortunately, all private use runes has been exhausted, no more placeholder could be used, no more converting
418- // usually, the exhausting won't occur in real cases, the magnitude of used placeholders is not larger than that of the CSS classes outputted by chroma.
419- hcd .placeholderOverflowCount ++
420- }
421- }
422- // write the remaining string
423- res .WriteString (htmlCode )
424- return res .String ()
425- }
426-
427- func (hcd * highlightCodeDiff ) recoverOneDiff (diff * diffmatchpatch.Diff ) {
428- sb := strings.Builder {}
429- var tagStack []string
430-
431- for _ , r := range diff .Text {
432- tag , ok := hcd .placeholderTagMap [r ]
433- if ! ok || tag == "" {
434- sb .WriteRune (r ) // if the rune is not a placeholder, write it as it is
435- continue
436- }
437- var tagToRecover string
438- if tag [1 ] == '/' { // Closing tag
439- // only get the tag itself, ignore the trailing comment (for how the comment is generated, see the code in `convert` function)
440- tagToRecover = tag [:strings .IndexByte (tag , '>' )+ 1 ]
441- if len (tagStack ) == 0 {
442- continue // if no open tag in stack yet, skip the closed tag
443- }
444- tagStack = tagStack [:len (tagStack )- 1 ]
445- } else {
446- tagToRecover = tag
447- tagStack = append (tagStack , tag )
448- }
449- sb .WriteString (tagToRecover )
450- }
451-
452- if len (tagStack ) > 0 {
453- // close all open tags
454- for i := len (tagStack ) - 1 ; i >= 0 ; i -- {
455- tagToClose := tagStack [i ]
456- // get the closed tag "</span>" from "<span class=...>" or "<span>"
457- pos := strings .IndexAny (tagToClose , " >" )
458- if pos != - 1 {
459- sb .WriteString ("</" + tagToClose [1 :pos ] + ">" )
460- } // else: impossible. every tag was pushed into the stack by the code above and is valid HTML open tag
461- }
462- }
463-
464- diff .Text = sb .String ()
465- }
466-
467286// GetComputedInlineDiffFor computes inline diff for the given line.
468287func (diffSection * DiffSection ) GetComputedInlineDiffFor (diffLine * DiffLine ) DiffInline {
469288 if setting .Git .DisableDiffHighlight {
0 commit comments