Skip to content

Commit

Permalink
Fixed #22 for CompactDiffBuilder and the border radius for all diff v…
Browse files Browse the repository at this point in the history
…iewers.
  • Loading branch information
Felix-CodingClimber committed Apr 5, 2024
1 parent d76dafb commit 6b4b36a
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,43 @@
private string? textA =
@"unchanged 1
unchanged 2
unchanged 3
unchanged 4
unchanged 5
unchanged 6
unchanged 7
unchanged 8
Huhu 1
test abc test1 abc
deleted
here is a unchanged line";
here is a unchanged line 1
here is a unchanged line 2
here is a unchanged line 3
here is a unchanged line 4
here is a unchanged line 5
here is a unchanged line 6";

private string? textB =
@"unchanged 1
unchanged 2
unchanged 3
unchanged 4
unchanged 5
unchanged 6
unchanged 7
unchanged 8
Huhu 2
blub abc test2 abc
new test 1
new test 2
new test 3
here is a unchanged line
here is a unchanged line 1
here is a unchanged line 2
here is a unchanged line 3
here is a unchanged line 4
here is a unchanged line 5
here is a unchanged line 6
but this is new again";

private void OnShowDiff()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
namespace DotNetElements.Core.StringDiff;
using System.Text;

namespace DotNetElements.Core.StringDiff;

public class CompactDiffBuilder
{
private readonly bool ignoreWhiteSpace;
private readonly bool ignoreCase;
private readonly int numContextLines;

private readonly LineChunker lineChunker;
private readonly WordChunker wordChunker;
Expand All @@ -13,10 +16,12 @@ public class CompactDiffBuilder
/// </summary>
/// <param name="ignoreWhiteSpace"><see langword="true"/> if ignore the white space; otherwise, <see langword="false"/>.</param>
/// <param name="ignoreCase"><see langword="true"/> if case-insensitive; otherwise, <see langword="false"/>.</param>
public CompactDiffBuilder(bool ignoreWhiteSpace = false, bool ignoreCase = false)
/// <param name="numContextLines">Number of unchanged lines visible before and after a changed line</param>
public CompactDiffBuilder(bool ignoreWhiteSpace = false, bool ignoreCase = false, int numContextLines = 2)
{
this.ignoreWhiteSpace = ignoreWhiteSpace;
this.ignoreCase = ignoreCase;
this.numContextLines = numContextLines;

lineChunker = new LineChunker();
wordChunker = new WordChunker();
Expand All @@ -35,12 +40,12 @@ public CompactDiffModel Diff(string oldText, string newText)

CompactDiffModel model = new();
DiffResult diffResult = Differ.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, lineChunker);
BuildDiffPieces(diffResult, model.Lines, ignoreWhiteSpace, ignoreCase);
BuildLineDiffPieces(diffResult, model.Lines, ignoreWhiteSpace, ignoreCase);

return model;
}

private ChangeType BuildDiffPieces(DiffResult diffResult, List<DiffPiece> pieces, bool ignoreWhiteSpace, bool ignoreCase)
private ChangeType BuildLineDiffPieces(DiffResult diffResult, List<DiffPiece> pieces, bool ignoreWhiteSpace, bool ignoreCase)
{
int aPos = 0;
int bPos = 0;
Expand All @@ -49,19 +54,27 @@ private ChangeType BuildDiffPieces(DiffResult diffResult, List<DiffPiece> pieces

foreach (DiffBlock diffBlock in diffResult.DiffBlocks)
{
int addedLinesUnchanged = 0;
while (bPos < diffBlock.InsertStartB && aPos < diffBlock.DeleteStartA)
{
pieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1) { IsOldPiece = true });
if (aPos == 0 || addedLinesUnchanged == numContextLines)
{
aPos = Math.Max(diffBlock.DeleteStartA - numContextLines, aPos);
bPos = Math.Max(diffBlock.InsertStartB - numContextLines, bPos);
}

pieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, PositionOld: aPos + 1, PositionNew: bPos + 1) { IsOldPiece = true });

addedLinesUnchanged++;
aPos++;
bPos++;
}

int i = 0;
for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
{
DiffPiece oldPiece = new(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1) { IsOldPiece = true };
DiffPiece newPiece = new(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1);
DiffPiece oldPiece = new(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, PositionOld: aPos + 1) { IsOldPiece = true };
DiffPiece newPiece = new(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, PositionNew: bPos + 1);

ChangeType subChangeSummary = BuildWordDiffPieces(diffResult.PiecesOld[aPos], diffResult.PiecesNew[bPos], oldPiece.SubPieces, newPiece.SubPieces, ignoreWhiteSpace, ignoreCase);
newPiece.Type = oldPiece.Type = subChangeSummary;
Expand All @@ -76,7 +89,7 @@ private ChangeType BuildDiffPieces(DiffResult diffResult, List<DiffPiece> pieces
{
for (; i < diffBlock.DeleteCountA; i++)
{
pieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1) { IsOldPiece = true });
pieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, PositionOld: aPos + 1) { IsOldPiece = true });

aPos++;
}
Expand All @@ -85,7 +98,7 @@ private ChangeType BuildDiffPieces(DiffResult diffResult, List<DiffPiece> pieces
{
for (; i < diffBlock.InsertCountB; i++)
{
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, PositionNew: bPos + 1));

bPos++;
}
Expand All @@ -94,7 +107,7 @@ private ChangeType BuildDiffPieces(DiffResult diffResult, List<DiffPiece> pieces

while (bPos < diffResult.PiecesNew.Length && aPos < diffResult.PiecesOld.Length)
{
pieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1) { IsOldPiece = true });
pieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, PositionOld: aPos + 1, PositionNew: bPos + 1) { IsOldPiece = true });

aPos++;
bPos++;
Expand All @@ -111,70 +124,98 @@ private ChangeType BuildWordDiffPieces(string oldText, string newText, List<Diff
{
DiffResult diffResult = Differ.CreateDiffs(oldText, newText, ignoreWhiteSpace: ignoreWhiteSpace, ignoreCase, wordChunker);

return BuildSubPieces(diffResult, oldPieces, newPieces, ignoreWhiteSpace, ignoreCase);
return BuildSubPieces(diffResult, oldPieces, newPieces);
}

private static ChangeType BuildSubPieces(DiffResult diffResult, List<DiffPiece> oldPieces, List<DiffPiece> newPieces, bool ignoreWhiteSpace, bool ignoreCase)
private static ChangeType BuildSubPieces(DiffResult diffResult, List<DiffPiece> oldPieces, List<DiffPiece> newPieces)
{
int aPos = 0;
int bPos = 0;

ChangeType changeSummary = ChangeType.Unchanged;
StringBuilder sbOld = new();
StringBuilder sbNew = new();

foreach (DiffBlock diffBlock in diffResult.DiffBlocks)
{
sbOld.Clear();
sbNew.Clear();
while (bPos < diffBlock.InsertStartB && aPos < diffBlock.DeleteStartA)
{
oldPieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1));
newPieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
sbOld.Append(diffResult.PiecesOld[aPos]);
sbNew.Append(diffResult.PiecesNew[bPos]);

aPos++;
bPos++;
}

if (sbOld.Length > 0)
oldPieces.Add(new DiffPiece(sbOld.ToString(), ChangeType.Unchanged));
if (sbNew.Length > 0)
newPieces.Add(new DiffPiece(sbNew.ToString(), ChangeType.Unchanged));

int i = 0;
sbOld.Clear();
sbNew.Clear();
for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
{
DiffPiece oldPiece = new(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1);
DiffPiece newPiece = new(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1);
sbOld.Append(diffResult.PiecesOld[i + diffBlock.DeleteStartA]);
sbNew.Append(diffResult.PiecesNew[i + diffBlock.InsertStartB]);

oldPieces.Add(oldPiece);
newPieces.Add(newPiece);
aPos++;
bPos++;
}

if (sbOld.Length > 0)
oldPieces.Add(new DiffPiece(sbOld.ToString(), ChangeType.Deleted));
if (sbNew.Length > 0)
newPieces.Add(new DiffPiece(sbNew.ToString(), ChangeType.Inserted));

if (diffBlock.DeleteCountA > diffBlock.InsertCountB)
{
StringBuilder sb = new();
for (; i < diffBlock.DeleteCountA; i++)
{
oldPieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1));
newPieces.Add(new DiffPiece());
sb.Append(diffResult.PiecesOld[i + diffBlock.DeleteStartA]);

aPos++;
}

if (sb.Length > 0)
oldPieces.Add(new DiffPiece(sb.ToString(), ChangeType.Deleted));
}
else
{
StringBuilder sb = new();
for (; i < diffBlock.InsertCountB; i++)
{
newPieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
oldPieces.Add(new DiffPiece());
sb.Append(diffResult.PiecesNew[i + diffBlock.InsertStartB]);

bPos++;
}

if (sb.Length > 0)
newPieces.Add(new DiffPiece(sb.ToString(), ChangeType.Inserted));
}
}

sbOld.Clear();
sbNew.Clear();
while (bPos < diffResult.PiecesNew.Length && aPos < diffResult.PiecesOld.Length)
{
oldPieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1));
newPieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
sbOld.Append(diffResult.PiecesOld[aPos]);
sbNew.Append(diffResult.PiecesNew[bPos]);

aPos++;
bPos++;
}

if (sbOld.Length > 0)
oldPieces.Add(new DiffPiece(sbOld.ToString(), ChangeType.Unchanged));
if (sbNew.Length > 0)
newPieces.Add(new DiffPiece(sbNew.ToString(), ChangeType.Unchanged));


// Consider the whole diff as "modified" if we found any change, otherwise we consider it unchanged
if (oldPieces.Any(x => x.Type is ChangeType.Modified or ChangeType.Inserted or ChangeType.Deleted))
changeSummary = ChangeType.Modified;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ private static void BuildDiffPieces(DiffResult diffResult, List<DiffPiece> piece
foreach (var diffBlock in diffResult.DiffBlocks)
{
for (; bPos < diffBlock.InsertStartB; bPos++)
pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, PositionNew: bPos + 1));

int i = 0;
for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
pieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted));

for (i = 0; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
{
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, PositionNew: bPos + 1));
bPos++;
}

Expand All @@ -70,13 +70,13 @@ private static void BuildDiffPieces(DiffResult diffResult, List<DiffPiece> piece
{
for (; i < diffBlock.InsertCountB; i++)
{
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, PositionNew: bPos + 1));
bPos++;
}
}
}

for (; bPos < diffResult.PiecesNew.Length; bPos++)
pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, PositionNew: bPos + 1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,8 @@ public enum ChangeType
Modified
}

public class DiffPiece : IEquatable<DiffPiece>
public record struct DiffPiece(string? Text, ChangeType Type, int? PositionOld = null, int? PositionNew = null)
{
public ChangeType Type { get; set; }
public int? Position { get; set; }
public string? Text { get; set; }
public List<DiffPiece> SubPieces { get; set; } = [];

public List<DiffPiece> SubPieces { get; private init; } = [];
public bool IsOldPiece { get; set; }

public DiffPiece(string? text, ChangeType type, int? position = null)
{
Text = text;
Position = position;
Type = type;
}

public DiffPiece() : this(null, ChangeType.Imaginary)
{
}

public override bool Equals(object? obj)
{
return Equals(obj as DiffPiece);
}

public bool Equals(DiffPiece? other)
{
return other != null
&& Type == other.Type
&& EqualityComparer<int?>.Default.Equals(Position, other.Position)
&& Text == other.Text
&& SubPiecesEqual(other);
}

public override int GetHashCode()
{
ArgumentNullException.ThrowIfNull(Position);
ArgumentNullException.ThrowIfNull(Text);
ArgumentNullException.ThrowIfNull(SubPieces);

var hashCode = 1688038063;
hashCode = hashCode * -1521134295 + Type.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<int?>.Default.GetHashCode(Position);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Text);
hashCode = hashCode * -1521134295 + EqualityComparer<int?>.Default.GetHashCode(SubPieces.Count);
return hashCode;
}

private bool SubPiecesEqual(DiffPiece other)
{
if (SubPieces is null)
return other.SubPieces is null;
else if (other.SubPieces is null)
return false;

if (SubPieces.Count != other.SubPieces.Count)
return false;

for (int i = 0; i < SubPieces.Count; i++)
{
if (!Equals(SubPieces[i], other.SubPieces[i]))
return false;
}

return true;
}

public override string ToString() => $"DiffPiece {{ {nameof(Position)} = {Position}, {nameof(Type)} = {Type}, {nameof(Text)} = {Text}, {nameof(SubPieces)} = List<{nameof(DiffPiece)}>(Count = {SubPieces?.Count})";
}
Loading

0 comments on commit 6b4b36a

Please # to comment.