@@ -283,187 +283,6 @@ func DiffInlineWithHighlightCode(fileName, language, code string) DiffInline {
283
283
return DiffInline {EscapeStatus : status , Content : template .HTML (content )}
284
284
}
285
285
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
-
467
286
// GetComputedInlineDiffFor computes inline diff for the given line.
468
287
func (diffSection * DiffSection ) GetComputedInlineDiffFor (diffLine * DiffLine ) DiffInline {
469
288
if setting .Git .DisableDiffHighlight {
0 commit comments