@@ -181,64 +181,60 @@ var (
181181 removedCodePrefix = []byte (`<span class="removed-code">` )
182182 codeTagSuffix = []byte (`</span>` )
183183)
184- var addSpanRegex = regexp .MustCompile (`<span [class="[a-z]*]*$` )
184+ var trailingSpanRegex = regexp .MustCompile (`<span\s*[[:alpha:]="]*?[>]?$` )
185+
186+ // shouldWriteInline represents combinations where we manually write inline changes
187+ func shouldWriteInline (diff diffmatchpatch.Diff , lineType DiffLineType ) bool {
188+ if true &&
189+ diff .Type == diffmatchpatch .DiffEqual ||
190+ diff .Type == diffmatchpatch .DiffInsert && lineType == DiffLineAdd ||
191+ diff .Type == diffmatchpatch .DiffDelete && lineType == DiffLineDel {
192+ return true
193+ }
194+ return false
195+ }
185196
186197func diffToHTML (fileName string , diffs []diffmatchpatch.Diff , lineType DiffLineType ) template.HTML {
187198 buf := bytes .NewBuffer (nil )
188- var addSpan string
189- for i := range diffs {
190- switch {
191- case diffs [i ].Type == diffmatchpatch .DiffEqual :
192- // Looking for the case where our 3rd party diff library previously detected a string difference
193- // in the middle of a span class because we highlight them first. This happens when added/deleted code
194- // also changes the chroma class name, either partially or fully. If found, just move the openining span code forward into the next section
195- // see TestDiffToHTML for examples
196- if len (addSpan ) > 0 {
197- diffs [i ].Text = addSpan + diffs [i ].Text
198- addSpan = ""
199+ match := ""
200+
201+ for _ , diff := range diffs {
202+ if shouldWriteInline (diff , lineType ) {
203+ if len (match ) > 0 {
204+ diff .Text = match + diff .Text
205+ match = ""
199206 }
200- m := addSpanRegex .FindStringSubmatchIndex (diffs [i ].Text )
207+ // Chroma HTML syntax highlighting is done before diffing individual lines in order to maintain consistency.
208+ // Since inline changes might split in the middle of a chroma span tag, make we manually put it back together
209+ // before writing so we don't try insert added/removed code spans in the middle of an existing chroma span
210+ // and create broken HTML.
211+ m := trailingSpanRegex .FindStringSubmatchIndex (diff .Text )
201212 if m != nil {
202- addSpan = diffs [i ].Text [m [0 ]:m [1 ]]
203- buf .WriteString (strings .TrimSuffix (diffs [i ].Text , addSpan ))
204- } else {
205- addSpan = ""
206- buf .WriteString (getLineContent (diffs [i ].Text ))
207- }
208- case diffs [i ].Type == diffmatchpatch .DiffInsert && lineType == DiffLineAdd :
209- if len (addSpan ) > 0 {
210- diffs [i ].Text = addSpan + diffs [i ].Text
211- addSpan = ""
213+ match = diff .Text [m [0 ]:m [1 ]]
214+ diff .Text = strings .TrimSuffix (diff .Text , match )
212215 }
213- // Print existing closing span first before opening added-code span so it doesn't unintentionally close it
214- if strings .HasPrefix (diffs [ i ] .Text , "</span>" ) {
216+ // Print an existing closing span first before opening added/remove -code span so it doesn't unintentionally close it
217+ if strings .HasPrefix (diff .Text , "</span>" ) {
215218 buf .WriteString ("</span>" )
216- diffs [ i ] .Text = strings .TrimPrefix (diffs [ i ] .Text , "</span>" )
219+ diff .Text = strings .TrimPrefix (diff .Text , "</span>" )
217220 }
218- m := addSpanRegex .FindStringSubmatchIndex (diffs [i ].Text )
219- if m != nil {
220- addSpan = diffs [i ].Text [m [0 ]:m [1 ]]
221- diffs [i ].Text = strings .TrimSuffix (diffs [i ].Text , addSpan )
221+ // If we weren't able to fix it then this should avoid broken HTML by not inserting more spans below
222+ // The previous/next diff section will contain the rest of the tag that is missing here
223+ if strings .Count (diff .Text , "<" ) != strings .Count (diff .Text , ">" ) {
224+ buf .WriteString (diff .Text )
225+ continue
222226 }
227+ }
228+ switch {
229+ case diff .Type == diffmatchpatch .DiffEqual :
230+ buf .WriteString (diff .Text )
231+ case diff .Type == diffmatchpatch .DiffInsert && lineType == DiffLineAdd :
223232 buf .Write (addedCodePrefix )
224- buf .WriteString (getLineContent ( diffs [ i ] .Text ) )
233+ buf .WriteString (diff .Text )
225234 buf .Write (codeTagSuffix )
226- case diffs [i ].Type == diffmatchpatch .DiffDelete && lineType == DiffLineDel :
227- if len (addSpan ) > 0 {
228- diffs [i ].Text = addSpan + diffs [i ].Text
229- addSpan = ""
230- }
231- if strings .HasPrefix (diffs [i ].Text , "</span>" ) {
232- buf .WriteString ("</span>" )
233- diffs [i ].Text = strings .TrimPrefix (diffs [i ].Text , "</span>" )
234- }
235- m := addSpanRegex .FindStringSubmatchIndex (diffs [i ].Text )
236- if m != nil {
237- addSpan = diffs [i ].Text [m [0 ]:m [1 ]]
238- diffs [i ].Text = strings .TrimSuffix (diffs [i ].Text , addSpan )
239- }
235+ case diff .Type == diffmatchpatch .DiffDelete && lineType == DiffLineDel :
240236 buf .Write (removedCodePrefix )
241- buf .WriteString (getLineContent ( diffs [ i ] .Text ) )
237+ buf .WriteString (diff .Text )
242238 buf .Write (codeTagSuffix )
243239 }
244240 }
0 commit comments