@@ -15,7 +15,6 @@ import (
15
15
"io"
16
16
"net/url"
17
17
"os"
18
- "regexp"
19
18
"sort"
20
19
"strings"
21
20
"time"
@@ -40,7 +39,7 @@ import (
40
39
"golang.org/x/text/transform"
41
40
)
42
41
43
- // DiffLineType represents the type of a DiffLine.
42
+ // DiffLineType represents the type of DiffLine.
44
43
type DiffLineType uint8
45
44
46
45
// DiffLineType possible values.
@@ -51,7 +50,7 @@ const (
51
50
DiffLineSection
52
51
)
53
52
54
- // DiffFileType represents the type of a DiffFile.
53
+ // DiffFileType represents the type of DiffFile.
55
54
type DiffFileType uint8
56
55
57
56
// DiffFileType possible values.
@@ -100,12 +99,12 @@ type DiffLineSectionInfo struct {
100
99
// BlobExcerptChunkSize represent max lines of excerpt
101
100
const BlobExcerptChunkSize = 20
102
101
103
- // GetType returns the type of a DiffLine.
102
+ // GetType returns the type of DiffLine.
104
103
func (d * DiffLine ) GetType () int {
105
104
return int (d .Type )
106
105
}
107
106
108
- // CanComment returns whether or not a line can get commented
107
+ // CanComment returns whether a line can get commented
109
108
func (d * DiffLine ) CanComment () bool {
110
109
return len (d .Comments ) == 0 && d .Type != DiffLineSection
111
110
}
@@ -191,287 +190,13 @@ var (
191
190
codeTagSuffix = []byte (`</span>` )
192
191
)
193
192
194
- var (
195
- unfinishedtagRegex = regexp .MustCompile (`<[^>]*$` )
196
- trailingSpanRegex = regexp .MustCompile (`<span\s*[[:alpha:]="]*?[>]?$` )
197
- entityRegex = regexp .MustCompile (`&[#]*?[0-9[:alpha:]]*$` )
198
- )
199
-
200
- // shouldWriteInline represents combinations where we manually write inline changes
201
- func shouldWriteInline (diff diffmatchpatch.Diff , lineType DiffLineType ) bool {
202
- if true &&
203
- diff .Type == diffmatchpatch .DiffEqual ||
204
- diff .Type == diffmatchpatch .DiffInsert && lineType == DiffLineAdd ||
205
- diff .Type == diffmatchpatch .DiffDelete && lineType == DiffLineDel {
206
- return true
207
- }
208
- return false
209
- }
210
-
211
- func fixupBrokenSpans (diffs []diffmatchpatch.Diff ) []diffmatchpatch.Diff {
212
- // Create a new array to store our fixed up blocks
213
- fixedup := make ([]diffmatchpatch.Diff , 0 , len (diffs ))
214
-
215
- // semantically label some numbers
216
- const insert , delete , equal = 0 , 1 , 2
217
-
218
- // record the positions of the last type of each block in the fixedup blocks
219
- last := []int {- 1 , - 1 , - 1 }
220
- operation := []diffmatchpatch.Operation {diffmatchpatch .DiffInsert , diffmatchpatch .DiffDelete , diffmatchpatch .DiffEqual }
221
-
222
- // create a writer for insert and deletes
223
- toWrite := []strings.Builder {
224
- {},
225
- {},
226
- }
227
-
228
- // make some flags for insert and delete
229
- unfinishedTag := []bool {false , false }
230
- unfinishedEnt := []bool {false , false }
231
-
232
- // store stores the provided text in the writer for the typ
233
- store := func (text string , typ int ) {
234
- (& (toWrite [typ ])).WriteString (text )
235
- }
236
-
237
- // hasStored returns true if there is stored content
238
- hasStored := func (typ int ) bool {
239
- return (& toWrite [typ ]).Len () > 0
240
- }
241
-
242
- // stored will return that content
243
- stored := func (typ int ) string {
244
- return (& toWrite [typ ]).String ()
245
- }
246
-
247
- // empty will empty the stored content
248
- empty := func (typ int ) {
249
- (& toWrite [typ ]).Reset ()
250
- }
251
-
252
- // pop will remove the stored content appending to a diff block for that typ
253
- pop := func (typ int , fixedup []diffmatchpatch.Diff ) []diffmatchpatch.Diff {
254
- if hasStored (typ ) {
255
- if last [typ ] > last [equal ] {
256
- fixedup [last [typ ]].Text += stored (typ )
257
- } else {
258
- fixedup = append (fixedup , diffmatchpatch.Diff {
259
- Type : operation [typ ],
260
- Text : stored (typ ),
261
- })
262
- }
263
- empty (typ )
264
- }
265
- return fixedup
266
- }
267
-
268
- // Now we walk the provided diffs and check the type of each block in turn
269
- for _ , diff := range diffs {
270
-
271
- typ := delete // flag for handling insert or delete typs
272
- switch diff .Type {
273
- case diffmatchpatch .DiffEqual :
274
- // First check if there is anything stored
275
- if hasStored (insert ) || hasStored (delete ) {
276
- // There are two reasons for storing content:
277
- // 1. Unfinished Entity <- Could be more efficient here by not doing this if we're looking for a tag
278
- if unfinishedEnt [insert ] || unfinishedEnt [delete ] {
279
- // we look for a ';' to finish an entity
280
- idx := strings .IndexRune (diff .Text , ';' )
281
- if idx >= 0 {
282
- // if we find a ';' store the preceding content to both insert and delete
283
- store (diff .Text [:idx + 1 ], insert )
284
- store (diff .Text [:idx + 1 ], delete )
285
-
286
- // and remove it from this block
287
- diff .Text = diff .Text [idx + 1 :]
288
-
289
- // reset the ent flags
290
- unfinishedEnt [insert ] = false
291
- unfinishedEnt [delete ] = false
292
- } else {
293
- // otherwise store it all on insert and delete
294
- store (diff .Text , insert )
295
- store (diff .Text , delete )
296
- // and empty this block
297
- diff .Text = ""
298
- }
299
- }
300
- // 2. Unfinished Tag
301
- if unfinishedTag [insert ] || unfinishedTag [delete ] {
302
- // we look for a '>' to finish a tag
303
- idx := strings .IndexRune (diff .Text , '>' )
304
- if idx >= 0 {
305
- store (diff .Text [:idx + 1 ], insert )
306
- store (diff .Text [:idx + 1 ], delete )
307
- diff .Text = diff .Text [idx + 1 :]
308
- unfinishedTag [insert ] = false
309
- unfinishedTag [delete ] = false
310
- } else {
311
- store (diff .Text , insert )
312
- store (diff .Text , delete )
313
- diff .Text = ""
314
- }
315
- }
316
-
317
- // If we've completed the required tag/entities
318
- if ! (unfinishedTag [insert ] || unfinishedTag [delete ] || unfinishedEnt [insert ] || unfinishedEnt [delete ]) {
319
- // pop off the stack
320
- fixedup = pop (insert , fixedup )
321
- fixedup = pop (delete , fixedup )
322
- }
323
-
324
- // If that has left this diff block empty then shortcut
325
- if len (diff .Text ) == 0 {
326
- continue
327
- }
328
- }
329
-
330
- // check if this block ends in an unfinished tag?
331
- idx := unfinishedtagRegex .FindStringIndex (diff .Text )
332
- if idx != nil {
333
- unfinishedTag [insert ] = true
334
- unfinishedTag [delete ] = true
335
- } else {
336
- // otherwise does it end in an unfinished entity?
337
- idx = entityRegex .FindStringIndex (diff .Text )
338
- if idx != nil {
339
- unfinishedEnt [insert ] = true
340
- unfinishedEnt [delete ] = true
341
- }
342
- }
343
-
344
- // If there is an unfinished component
345
- if idx != nil {
346
- // Store the fragment
347
- store (diff .Text [idx [0 ]:], insert )
348
- store (diff .Text [idx [0 ]:], delete )
349
- // and remove it from this block
350
- diff .Text = diff .Text [:idx [0 ]]
351
- }
352
-
353
- // If that hasn't left the block empty
354
- if len (diff .Text ) > 0 {
355
- // store the position of the last equal block and store it in our diffs
356
- last [equal ] = len (fixedup )
357
- fixedup = append (fixedup , diff )
358
- }
359
- continue
360
- case diffmatchpatch .DiffInsert :
361
- typ = insert
362
- fallthrough
363
- case diffmatchpatch .DiffDelete :
364
- // First check if there is anything stored for this type
365
- if hasStored (typ ) {
366
- // if there is prepend it to this block, empty the storage and reset our flags
367
- diff .Text = stored (typ ) + diff .Text
368
- empty (typ )
369
- unfinishedEnt [typ ] = false
370
- unfinishedTag [typ ] = false
371
- }
372
-
373
- // check if this block ends in an unfinished tag
374
- idx := unfinishedtagRegex .FindStringIndex (diff .Text )
375
- if idx != nil {
376
- unfinishedTag [typ ] = true
377
- } else {
378
- // otherwise does it end in an unfinished entity
379
- idx = entityRegex .FindStringIndex (diff .Text )
380
- if idx != nil {
381
- unfinishedEnt [typ ] = true
382
- }
383
- }
384
-
385
- // If there is an unfinished component
386
- if idx != nil {
387
- // Store the fragment
388
- store (diff .Text [idx [0 ]:], typ )
389
- // and remove it from this block
390
- diff .Text = diff .Text [:idx [0 ]]
391
- }
392
-
393
- // If that hasn't left the block empty
394
- if len (diff .Text ) > 0 {
395
- // if the last block of this type was after the last equal block
396
- if last [typ ] > last [equal ] {
397
- // store this blocks content on that block
398
- fixedup [last [typ ]].Text += diff .Text
399
- } else {
400
- // otherwise store the position of the last block of this type and store the block
401
- last [typ ] = len (fixedup )
402
- fixedup = append (fixedup , diff )
403
- }
404
- }
405
- continue
406
- }
407
- }
408
-
409
- // pop off any remaining stored content
410
- fixedup = pop (insert , fixedup )
411
- fixedup = pop (delete , fixedup )
412
-
413
- return fixedup
414
- }
415
-
416
- func diffToHTML (fileName string , diffs []diffmatchpatch.Diff , lineType DiffLineType ) DiffInline {
193
+ func diffToHTML (lineWrapperTags []string , diffs []diffmatchpatch.Diff , lineType DiffLineType ) string {
417
194
buf := bytes .NewBuffer (nil )
418
- match := ""
419
-
420
- diffs = fixupBrokenSpans ( diffs )
421
-
195
+ // restore the line wrapper tags <span class="line"> and <span class="cl">, if necessary
196
+ for _ , tag := range lineWrapperTags {
197
+ buf . WriteString ( tag )
198
+ }
422
199
for _ , diff := range diffs {
423
- if shouldWriteInline (diff , lineType ) {
424
- if len (match ) > 0 {
425
- diff .Text = match + diff .Text
426
- match = ""
427
- }
428
- // Chroma HTML syntax highlighting is done before diffing individual lines in order to maintain consistency.
429
- // Since inline changes might split in the middle of a chroma span tag or HTML entity, make we manually put it back together
430
- // before writing so we don't try insert added/removed code spans in the middle of one of those
431
- // and create broken HTML. This is done by moving incomplete HTML forward until it no longer matches our pattern of
432
- // a line ending with an incomplete HTML entity or partial/opening <span>.
433
-
434
- // EX:
435
- // diffs[{Type: dmp.DiffDelete, Text: "language</span><span "},
436
- // {Type: dmp.DiffEqual, Text: "c"},
437
- // {Type: dmp.DiffDelete, Text: "lass="p">}]
438
-
439
- // After first iteration
440
- // diffs[{Type: dmp.DiffDelete, Text: "language</span>"}, //write out
441
- // {Type: dmp.DiffEqual, Text: "<span c"},
442
- // {Type: dmp.DiffDelete, Text: "lass="p">,</span>}]
443
-
444
- // After second iteration
445
- // {Type: dmp.DiffEqual, Text: ""}, // write out
446
- // {Type: dmp.DiffDelete, Text: "<span class="p">,</span>}]
447
-
448
- // Final
449
- // {Type: dmp.DiffDelete, Text: "<span class="p">,</span>}]
450
- // end up writing <span class="removed-code"><span class="p">,</span></span>
451
- // Instead of <span class="removed-code">lass="p",</span></span>
452
-
453
- m := trailingSpanRegex .FindStringSubmatchIndex (diff .Text )
454
- if m != nil {
455
- match = diff .Text [m [0 ]:m [1 ]]
456
- diff .Text = strings .TrimSuffix (diff .Text , match )
457
- }
458
- m = entityRegex .FindStringSubmatchIndex (diff .Text )
459
- if m != nil {
460
- match = diff .Text [m [0 ]:m [1 ]]
461
- diff .Text = strings .TrimSuffix (diff .Text , match )
462
- }
463
- // Print an existing closing span first before opening added/remove-code span so it doesn't unintentionally close it
464
- if strings .HasPrefix (diff .Text , "</span>" ) {
465
- buf .WriteString ("</span>" )
466
- diff .Text = strings .TrimPrefix (diff .Text , "</span>" )
467
- }
468
- // If we weren't able to fix it then this should avoid broken HTML by not inserting more spans below
469
- // The previous/next diff section will contain the rest of the tag that is missing here
470
- if strings .Count (diff .Text , "<" ) != strings .Count (diff .Text , ">" ) {
471
- buf .WriteString (diff .Text )
472
- continue
473
- }
474
- }
475
200
switch {
476
201
case diff .Type == diffmatchpatch .DiffEqual :
477
202
buf .WriteString (diff .Text )
@@ -485,7 +210,10 @@ func diffToHTML(fileName string, diffs []diffmatchpatch.Diff, lineType DiffLineT
485
210
buf .Write (codeTagSuffix )
486
211
}
487
212
}
488
- return DiffInlineWithUnicodeEscape (template .HTML (buf .String ()))
213
+ for range lineWrapperTags {
214
+ buf .WriteString ("</span>" )
215
+ }
216
+ return buf .String ()
489
217
}
490
218
491
219
// GetLine gets a specific line by type (add or del) and file line number
@@ -597,10 +325,12 @@ func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) Dif
597
325
return DiffInlineWithHighlightCode (diffSection .FileName , language , diffLine .Content )
598
326
}
599
327
600
- diffRecord := diffMatchPatch .DiffMain (highlight .Code (diffSection .FileName , language , diff1 [1 :]), highlight .Code (diffSection .FileName , language , diff2 [1 :]), true )
601
- diffRecord = diffMatchPatch .DiffCleanupEfficiency (diffRecord )
602
-
603
- return diffToHTML (diffSection .FileName , diffRecord , diffLine .Type )
328
+ hcd := newHighlightCodeDiff ()
329
+ diffRecord := hcd .diffWithHighlight (diffSection .FileName , language , diff1 [1 :], diff2 [1 :])
330
+ // it seems that Gitea doesn't need the line wrapper of Chroma, so do not add them back
331
+ // if the line wrappers are still needed in the future, it can be added back by "diffToHTML(hcd.lineWrapperTags. ...)"
332
+ diffHTML := diffToHTML (nil , diffRecord , diffLine .Type )
333
+ return DiffInlineWithUnicodeEscape (template .HTML (diffHTML ))
604
334
}
605
335
606
336
// DiffFile represents a file diff.
@@ -1289,7 +1019,7 @@ func readFileName(rd *strings.Reader) (string, bool) {
1289
1019
if char == '"' {
1290
1020
fmt .Fscanf (rd , "%q " , & name )
1291
1021
if len (name ) == 0 {
1292
- log .Error ("Reader has no file name: % v" , rd )
1022
+ log .Error ("Reader has no file name: reader=%+ v" , rd )
1293
1023
return "" , true
1294
1024
}
1295
1025
@@ -1311,7 +1041,7 @@ func readFileName(rd *strings.Reader) (string, bool) {
1311
1041
}
1312
1042
}
1313
1043
if len (name ) < 2 {
1314
- log .Error ("Unable to determine name from reader: % v" , rd )
1044
+ log .Error ("Unable to determine name from reader: reader=%+ v" , rd )
1315
1045
return "" , true
1316
1046
}
1317
1047
return name [2 :], ambiguity
0 commit comments