Skip to content

Commit 7de7ca7

Browse files
committed
Line differ and word differ are just specializations of TextSpanDiffer
1 parent a998769 commit 7de7ca7

File tree

3 files changed

+141
-197
lines changed

3 files changed

+141
-197
lines changed
Lines changed: 9 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,27 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Text;
4+
using System.Collections.Immutable;
5+
using Microsoft.AspNetCore.Razor.PooledObjects;
56
using Microsoft.CodeAnalysis.Text;
67

78
namespace Microsoft.CodeAnalysis.Razor.TextDifferencing;
89

910
internal partial class SourceTextDiffer
1011
{
11-
private class LineDiffer : SourceTextDiffer
12+
private sealed class LineDiffer(SourceText oldText, SourceText newText)
13+
: TextSpanDiffer(oldText, newText)
1214
{
13-
private readonly TextLineCollection _oldLines;
14-
private readonly TextLineCollection _newLines;
15-
16-
private char[] _oldLineBuffer;
17-
private char[] _newLineBuffer;
18-
private char[] _appendBuffer;
19-
20-
protected override int OldSourceLength { get; }
21-
protected override int NewSourceLength { get; }
22-
23-
public LineDiffer(SourceText oldText, SourceText newText)
24-
: base(oldText, newText)
25-
{
26-
_oldLineBuffer = RentArray(1024);
27-
_newLineBuffer = RentArray(1024);
28-
_appendBuffer = RentArray(1024);
29-
30-
_oldLines = oldText.Lines;
31-
_newLines = newText.Lines;
32-
33-
OldSourceLength = _oldLines.Count;
34-
NewSourceLength = _newLines.Count;
35-
}
36-
37-
public override void Dispose()
15+
protected override ImmutableArray<TextSpan> Tokenize(SourceText text)
3816
{
39-
ReturnArray(_oldLineBuffer);
40-
ReturnArray(_newLineBuffer);
41-
ReturnArray(_appendBuffer);
42-
}
43-
44-
protected override bool SourceEqual(int oldSourceIndex, int newSourceIndex)
45-
{
46-
var oldLine = _oldLines[oldSourceIndex];
47-
var newLine = _newLines[newSourceIndex];
48-
49-
var oldSpan = oldLine.SpanIncludingLineBreak;
50-
var newSpan = newLine.SpanIncludingLineBreak;
51-
52-
if (oldSpan.Length != newSpan.Length)
53-
{
54-
return false;
55-
}
56-
57-
var length = oldSpan.Length;
17+
using var builder = new PooledArrayBuilder<TextSpan>();
5818

59-
// Simple case: Both lines are empty.
60-
if (length == 0)
19+
foreach (var line in text.Lines)
6120
{
62-
return true;
63-
}
64-
65-
// Copy the text into char arrays for comparison. Note: To avoid allocation,
66-
// we try to reuse the same char buffers and only grow them when a longer
67-
// line is encountered.
68-
var oldChars = EnsureBuffer(ref _oldLineBuffer, length);
69-
var newChars = EnsureBuffer(ref _newLineBuffer, length);
70-
71-
OldText.CopyTo(oldSpan.Start, oldChars, 0, length);
72-
NewText.CopyTo(newSpan.Start, newChars, 0, length);
73-
74-
for (var i = 0; i < length; i++)
75-
{
76-
if (oldChars[i] != newChars[i])
77-
{
78-
return false;
79-
}
80-
}
81-
82-
return true;
83-
}
84-
85-
protected override int GetEditPosition(DiffEdit edit)
86-
=> _oldLines[edit.Position].Start;
87-
88-
protected override int AppendEdit(DiffEdit edit, StringBuilder builder)
89-
{
90-
if (edit.Kind == DiffEditKind.Insert)
91-
{
92-
Assumes.NotNull(edit.NewTextPosition);
93-
var newTextPosition = edit.NewTextPosition.GetValueOrDefault();
94-
95-
for (var i = 0; i < edit.Length; i++)
96-
{
97-
var newLine = _newLines[newTextPosition + i];
98-
99-
var newSpan = newLine.SpanIncludingLineBreak;
100-
if (newSpan.Length > 0)
101-
{
102-
var buffer = EnsureBuffer(ref _appendBuffer, newSpan.Length);
103-
NewText.CopyTo(newSpan.Start, buffer, 0, newSpan.Length);
104-
105-
builder.Append(buffer, 0, newSpan.Length);
106-
}
107-
}
108-
109-
return _oldLines[edit.Position].Start;
21+
builder.Add(line.SpanIncludingLineBreak);
11022
}
11123

112-
return _oldLines[edit.Position + edit.Length - 1].EndIncludingLineBreak;
24+
return builder.ToImmutableAndClear();
11325
}
11426
}
11527
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
using System.Text;
6+
using Microsoft.CodeAnalysis.Text;
7+
8+
namespace Microsoft.CodeAnalysis.Razor.TextDifferencing;
9+
10+
internal partial class SourceTextDiffer
11+
{
12+
private abstract class TextSpanDiffer : SourceTextDiffer
13+
{
14+
private readonly ImmutableArray<TextSpan> _oldLines = [];
15+
private readonly ImmutableArray<TextSpan> _newLines = [];
16+
17+
private char[] _oldLineBuffer;
18+
private char[] _newLineBuffer;
19+
private char[] _appendBuffer;
20+
21+
protected override int OldSourceLength { get; }
22+
protected override int NewSourceLength { get; }
23+
24+
public TextSpanDiffer(SourceText oldText, SourceText newText)
25+
: base(oldText, newText)
26+
{
27+
_oldLineBuffer = RentArray(1024);
28+
_newLineBuffer = RentArray(1024);
29+
_appendBuffer = RentArray(1024);
30+
31+
OldSourceLength = _oldLines.Length;
32+
NewSourceLength = _newLines.Length;
33+
34+
if (OldSourceLength > 0)
35+
{
36+
_oldLines = Tokenize(oldText);
37+
}
38+
39+
if (NewSourceLength > 0)
40+
{
41+
_newLines = Tokenize(newText);
42+
}
43+
}
44+
45+
protected abstract ImmutableArray<TextSpan> Tokenize(SourceText text);
46+
47+
public override void Dispose()
48+
{
49+
ReturnArray(_oldLineBuffer);
50+
ReturnArray(_newLineBuffer);
51+
ReturnArray(_appendBuffer);
52+
}
53+
54+
protected override bool SourceEqual(int oldSourceIndex, int newSourceIndex)
55+
{
56+
var oldLine = _oldLines[oldSourceIndex];
57+
var newLine = _newLines[newSourceIndex];
58+
59+
if (oldLine.Length != newLine.Length)
60+
{
61+
return false;
62+
}
63+
64+
var length = oldLine.Length;
65+
66+
// Simple case: Both lines are empty.
67+
if (length == 0)
68+
{
69+
return true;
70+
}
71+
72+
// Copy the text into char arrays for comparison. Note: To avoid allocation,
73+
// we try to reuse the same char buffers and only grow them when a longer
74+
// line is encountered.
75+
var oldChars = EnsureBuffer(ref _oldLineBuffer, length);
76+
var newChars = EnsureBuffer(ref _newLineBuffer, length);
77+
78+
OldText.CopyTo(oldLine.Start, oldChars, 0, length);
79+
NewText.CopyTo(newLine.Start, newChars, 0, length);
80+
81+
for (var i = 0; i < length; i++)
82+
{
83+
if (oldChars[i] != newChars[i])
84+
{
85+
return false;
86+
}
87+
}
88+
89+
return true;
90+
}
91+
92+
protected override int GetEditPosition(DiffEdit edit)
93+
=> _oldLines[edit.Position].Start;
94+
95+
protected override int AppendEdit(DiffEdit edit, StringBuilder builder)
96+
{
97+
if (edit.Kind == DiffEditKind.Insert)
98+
{
99+
Assumes.NotNull(edit.NewTextPosition);
100+
var newTextPosition = edit.NewTextPosition.GetValueOrDefault();
101+
102+
for (var i = 0; i < edit.Length; i++)
103+
{
104+
var newLine = _newLines[newTextPosition + i];
105+
106+
if (newLine.Length > 0)
107+
{
108+
var buffer = EnsureBuffer(ref _appendBuffer, newLine.Length);
109+
NewText.CopyTo(newLine.Start, buffer, 0, newLine.Length);
110+
111+
builder.Append(buffer, 0, newLine.Length);
112+
}
113+
}
114+
115+
return _oldLines[edit.Position].Start;
116+
}
117+
118+
return _oldLines[edit.Position + edit.Length - 1].End;
119+
}
120+
}
121+
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/TextDifferencing/SourceTextDiffer.WordDiffer.cs

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,18 @@
33

44
using System.Collections.Immutable;
55
using System.Diagnostics;
6-
using System.Text;
76
using Microsoft.AspNetCore.Razor.PooledObjects;
87
using Microsoft.CodeAnalysis.Text;
98

109
namespace Microsoft.CodeAnalysis.Razor.TextDifferencing;
1110

1211
internal partial class SourceTextDiffer
1312
{
14-
private sealed class WordDiffer : SourceTextDiffer
13+
private sealed class WordDiffer(SourceText oldText, SourceText newText)
14+
: TextSpanDiffer(oldText, newText)
1515
{
16-
private readonly ImmutableArray<TextSpan> _oldWords;
17-
private readonly ImmutableArray<TextSpan> _newWords;
18-
19-
private char[] _oldBuffer;
20-
private char[] _newBuffer;
21-
private char[] _appendBuffer;
22-
23-
protected override int OldSourceLength { get; }
24-
protected override int NewSourceLength { get; }
25-
26-
public WordDiffer(SourceText oldText, SourceText newText) : base(oldText, newText)
27-
{
28-
_oldBuffer = RentArray(1024);
29-
_newBuffer = RentArray(1024);
30-
_appendBuffer = RentArray(1024);
31-
32-
_oldWords = TokenizeWords(oldText);
33-
_newWords = TokenizeWords(newText);
34-
35-
OldSourceLength = _oldWords.Length;
36-
NewSourceLength = _newWords.Length;
37-
}
38-
39-
public override void Dispose()
40-
{
41-
ReturnArray(_oldBuffer);
42-
ReturnArray(_newBuffer);
43-
ReturnArray(_appendBuffer);
44-
}
45-
46-
private static ImmutableArray<TextSpan> TokenizeWords(SourceText text)
16+
protected override ImmutableArray<TextSpan> Tokenize(SourceText text)
4717
{
48-
if (text.Length == 0)
49-
{
50-
return [];
51-
}
52-
5318
using var builder = new PooledArrayBuilder<TextSpan>();
5419

5520
var currentSpanStart = 0;
@@ -76,72 +41,18 @@ private static ImmutableArray<TextSpan> TokenizeWords(SourceText text)
7641
builder.Add(TextSpan.FromBounds(currentSpanStart, text.Length));
7742

7843
return builder.ToImmutableAndClear();
79-
80-
// The type of classification doesn't matter as long as its unique and equatible
81-
static int Classify(char c)
82-
=> c switch
83-
{
84-
'/' => 0,
85-
'"' => 1,
86-
_ when char.IsWhiteSpace(c) => 2,
87-
_ => 3,
88-
};
89-
}
90-
91-
protected override bool SourceEqual(int oldSourceIndex, int newSourceIndex)
92-
{
93-
var oldWord = _oldWords[oldSourceIndex];
94-
var newWord = _newWords[newSourceIndex];
95-
if (oldWord.Length != newWord.Length)
96-
{
97-
return false;
98-
}
99-
100-
var length = oldWord.Length;
101-
102-
// Copy the text into char arrays for comparison. Note: To avoid allocation,
103-
// we try to reuse the same char buffers and only grow them when a longer
104-
// line is encountered.
105-
var oldChars = EnsureBuffer(ref _oldBuffer, oldWord.Length);
106-
var newChars = EnsureBuffer(ref _newBuffer, newWord.Length);
107-
108-
OldText.CopyTo(oldWord.Start, oldChars, 0, length);
109-
NewText.CopyTo(newWord.Start, newChars, 0, length);
110-
111-
for (var i = 0; i < length; i++)
112-
{
113-
if (oldChars[i] != newChars[i])
114-
{
115-
return false;
116-
}
117-
}
118-
119-
return true;
12044
}
12145

122-
protected override int GetEditPosition(DiffEdit edit)
123-
=> _oldWords[edit.Position].Start;
124-
125-
protected override int AppendEdit(DiffEdit edit, StringBuilder builder)
46+
private static int Classify(char c)
12647
{
127-
if (edit.Kind == DiffEditKind.Insert)
48+
// The type of classification doesn't matter as long as its unique and equatible
49+
return c switch
12850
{
129-
Assumes.NotNull(edit.NewTextPosition);
130-
var newWordIndex = edit.NewTextPosition.GetValueOrDefault();
131-
132-
for (var i = 0; i < edit.Length; i++)
133-
{
134-
var word = _newWords[newWordIndex + i];
135-
var buffer = EnsureBuffer(ref _appendBuffer, word.Length);
136-
NewText.CopyTo(word.Start, buffer, 0, word.Length);
137-
138-
builder.Append(buffer, 0, word.Length);
139-
}
140-
141-
return _oldWords[edit.Position].Start;
142-
}
143-
144-
return _oldWords[edit.Position + edit.Length - 1].End;
51+
'/' => 0,
52+
'"' => 1,
53+
_ when char.IsWhiteSpace(c) => 2,
54+
_ => 3,
55+
};
14556
}
14657
}
14758
}

0 commit comments

Comments
 (0)