diff --git a/DiffPlex.App/DiffPlex.App.csproj b/DiffPlex.App/DiffPlex.App.csproj
index b900706f..00ce8295 100644
--- a/DiffPlex.App/DiffPlex.App.csproj
+++ b/DiffPlex.App/DiffPlex.App.csproj
@@ -3,16 +3,16 @@
WinExe
net9.0-windows10.0.19041.0
10.0.17763.0
- 10.0.19041.48
+ 10.0.19041.57
DiffPlex.UI
DiffPlex.App
app.manifest
- 1.2.0
+ 2.0.0
diff
Diff files and text.
13.0
- 1.2.0.0
- 1.2.0.0
+ 2.0.0.0
+ 2.0.0.0
x86;x64;arm64
win-x86;win-x64;win-arm64
win-$(Platform).pubxml
@@ -44,7 +44,7 @@
-
+
diff --git a/DiffPlex.App/Package.appxmanifest b/DiffPlex.App/Package.appxmanifest
index 0e39fd02..e17e5f32 100644
--- a/DiffPlex.App/Package.appxmanifest
+++ b/DiffPlex.App/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="2.0.0.0" />
DiffPlex
diff --git a/DiffPlex.Windows/Converters.cs b/DiffPlex.Windows/Converters.cs
index 5c2e96ee..4c6e81b0 100644
--- a/DiffPlex.Windows/Converters.cs
+++ b/DiffPlex.Windows/Converters.cs
@@ -38,7 +38,7 @@ public object Convert(object value, Type targetType, object parameter, string la
{
if (targetType == typeof(IEnumerable))
{
- if (value is not List sub)
+ if (value is not IEnumerable sub)
{
if (value is not DiffPiece diffPiece) return null;
sub = diffPiece.SubPieces;
@@ -144,7 +144,7 @@ public class DiffTextHighlighterConverter(ChangeType defaultChangeType) : IValue
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is FrameworkElement element) value = element.DataContext;
- if (value is not List sub)
+ if (value is not IEnumerable sub)
{
if (value is DiffPiece p) sub = p.SubPieces;
else return null;
diff --git a/DiffPlex.Windows/DiffPlex.Windows.csproj b/DiffPlex.Windows/DiffPlex.Windows.csproj
index 7cf2c9a2..282398b1 100644
--- a/DiffPlex.Windows/DiffPlex.Windows.csproj
+++ b/DiffPlex.Windows/DiffPlex.Windows.csproj
@@ -7,13 +7,13 @@
DiffPlex.Windows
win-x86;win-x64;win-arm64
true
- 1.2.0
+ 2.0.0
diff
diffplex_icon.png
DiffPlex.Windows is a Windows App SDK control library that allows you to programatically render visual text diffs in your application.
13.0
- 1.2.0.0
- 1.2.0.0
+ 2.0.0.0
+ 2.0.0.0
Kingcean Tuan; Matthew Manela
Copyright (c) 2022 Matthew Manela. All rights reserved.
../DiffPlex.ico
@@ -35,10 +35,10 @@
-
-
-
-
+
+
+
+
diff --git a/DiffPlex.Windows/DiffTextView.xaml.cs b/DiffPlex.Windows/DiffTextView.xaml.cs
index e74e4ef7..2d17915d 100644
--- a/DiffPlex.Windows/DiffTextView.xaml.cs
+++ b/DiffPlex.Windows/DiffTextView.xaml.cs
@@ -149,7 +149,7 @@ public sealed partial class DiffTextView : UserControl
private readonly DiffTextViewReference reference;
private List sideBySide;
- private List inlines;
+ private IReadOnlyList inlines;
private bool skipRefresh = true;
///
@@ -815,8 +815,8 @@ private void RefreshSplitView(bool forceToUpdate = false)
if (SplitElement.ItemsSource != null && !forceToUpdate) return;
sideBySide = null;
var diff = SideBySideDiffBuilder.Diff(OldText ?? string.Empty, NewText ?? string.Empty, IgnoreWhiteSpace, !IsCaseSensitive);
- var left = diff?.OldText?.Lines ?? new();
- var right = diff?.NewText?.Lines ?? new();
+ var left = diff?.OldText?.Lines ?? new List();
+ var right = diff?.NewText?.Lines ?? new List();
var count = Math.Max(left.Count, right.Count);
var col = new List();
var add = 0;
diff --git a/DiffPlex.Windows/Helper.cs b/DiffPlex.Windows/Helper.cs
index 2ee40ca0..7b695869 100644
--- a/DiffPlex.Windows/Helper.cs
+++ b/DiffPlex.Windows/Helper.cs
@@ -25,7 +25,7 @@ internal class InternalUtilities
public static readonly SolidColorBrush GrayBackground = new(Color.FromArgb(32, 128, 128, 128));
- public static List GetTextHighlighter(List sub, ChangeType modify, Brush foreground)
+ public static List GetTextHighlighter(IEnumerable sub, ChangeType modify, Brush foreground)
{
if (sub == null) return null;
var insert = new TextHighlighter
diff --git a/DiffPlex.Windows/Models.cs b/DiffPlex.Windows/Models.cs
index c40aff85..18a05679 100644
--- a/DiffPlex.Windows/Models.cs
+++ b/DiffPlex.Windows/Models.cs
@@ -34,6 +34,9 @@ public enum DiffTextViewType : byte
Right = 3,
}
+///
+/// The view info of diff text.
+///
public struct DiffTextViewInfo
{
private readonly object token;
@@ -48,12 +51,14 @@ internal DiffTextViewInfo(object token, DiffTextViewType viewType, DiffPiece mod
{
this.token = token ?? new();
ViewType = viewType;
- model ??= new();
- ChangeType = model.Type;
- Position = model.Position;
- Text = model.Text;
+ Model = model ?? new();
}
+ ///
+ /// Gets the data model of source.
+ ///
+ public DiffPiece Model { get; }
+
///
/// Gets the view type.
///
@@ -62,41 +67,27 @@ internal DiffTextViewInfo(object token, DiffTextViewType viewType, DiffPiece mod
///
/// Gets the change type.
///
- public ChangeType ChangeType { get; }
+ public ChangeType ChangeType => Model.Type;
///
/// Gets the line position.
///
- public int? Position { get; }
+ public int? Position => Model.Position;
///
/// Gets the content text.
///
- public string Text { get; }
+ public string Text => Model.Text;
+
+ ///
+ /// Gets the sub pieces.
+ ///
+ public IReadOnlyList SubPieces => Model.SubPieces;
///
public override string ToString()
{
- var sb = new StringBuilder();
- if (Position.HasValue)
- {
- sb.Append(Position.Value);
- sb.Append(' ');
- }
-
- switch (ChangeType)
- {
- case ChangeType.Inserted:
- sb.Append("+ ");
- break;
- case ChangeType.Deleted:
- sb.Append("- ");
- break;
- }
-
- sb.Append('\t');
- sb.Append(Text);
- return sb.ToString();
+ return Model.ToString();
}
///
@@ -105,7 +96,17 @@ public override string ToString()
/// The token to test.
/// true if the same; otherwise, false.
internal bool IsToken(object token)
- => token == this.token;
+ {
+ return token == this.token;
+ }
+
+ ///
+ /// Writes current diff piece into UTF-8 JSON stream.
+ ///
+ /// The UTF-8 JSON stream writer.
+ /// The JSON srialization options.
+ public void Write(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions options)
+ => Model.Write(writer, options);
}
///
@@ -171,8 +172,6 @@ public bool Equals(DiffTextViewInfo other)
///
internal class DiffTextViewModel : BaseDiffTextViewModel
{
- private object token = new();
-
///
/// Initializes a new instance of the class.
///
diff --git a/DiffPlex.WindowsForms.Demo/DiffPlex.WindowsForms.Demo.csproj b/DiffPlex.WindowsForms.Demo/DiffPlex.WindowsForms.Demo.csproj
index 4cd27512..00d809a2 100644
--- a/DiffPlex.WindowsForms.Demo/DiffPlex.WindowsForms.Demo.csproj
+++ b/DiffPlex.WindowsForms.Demo/DiffPlex.WindowsForms.Demo.csproj
@@ -2,7 +2,7 @@
WinExe
- net8.0-windows;net48;net46
+ net8.0-windows;net48;net462
true
False
..\DiffPlex.ico
diff --git a/DiffPlex.Wpf.Demo/DiffPlex.Wpf.Demo.csproj b/DiffPlex.Wpf.Demo/DiffPlex.Wpf.Demo.csproj
index 74e9abbd..42f57a5b 100644
--- a/DiffPlex.Wpf.Demo/DiffPlex.Wpf.Demo.csproj
+++ b/DiffPlex.Wpf.Demo/DiffPlex.Wpf.Demo.csproj
@@ -2,7 +2,7 @@
WinExe
- net9.0-windows;net8.0-windows;net48;net46
+ net9.0-windows;net8.0-windows;net48;net462
true
False
DiffPlex.Wpf.Demo.App
diff --git a/DiffPlex.Wpf/Controls/DiffViewer.xaml.cs b/DiffPlex.Wpf/Controls/DiffViewer.xaml.cs
index feab0a3c..ce482e32 100644
--- a/DiffPlex.Wpf/Controls/DiffViewer.xaml.cs
+++ b/DiffPlex.Wpf/Controls/DiffViewer.xaml.cs
@@ -1163,7 +1163,7 @@ private void RenderSideBySideDiffs()
private void RenderInlineDiffs()
{
if (inlineResult?.Lines == null) return;
- ICollection selectedLines = inlineResult.Lines;
+ IReadOnlyCollection selectedLines = inlineResult.Lines;
Helper.RenderInlineDiffs(InlineContentPanel, selectedLines, this, IgnoreUnchanged ? LinesContext : -1);
}
@@ -1183,12 +1183,12 @@ private void RightContentPanel_ScrollChanged(object sender, ScrollChangedEventAr
private void ApplyHeaderTextProperties(TextBlock text)
{
- text.SetBinding(TextBlock.FontSizeProperty, new Binding("FontSize") { Source = this, Mode = BindingMode.OneWay });
- text.SetBinding(TextBlock.FontFamilyProperty, new Binding("FontFamily") { Source = this, Mode = BindingMode.OneWay });
- text.SetBinding(TextBlock.FontWeightProperty, new Binding("FontWeight") { Source = this, Mode = BindingMode.OneWay });
- text.SetBinding(TextBlock.FontStretchProperty, new Binding("FontStretch") { Source = this, Mode = BindingMode.OneWay });
- text.SetBinding(TextBlock.FontStyleProperty, new Binding("FontStyle") { Source = this, Mode = BindingMode.OneWay });
- text.SetBinding(TextBlock.ForegroundProperty, new Binding("HeaderForeground") { Source = this, Mode = BindingMode.OneWay, TargetNullValue = Foreground });
+ text.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(FontSize)) { Source = this, Mode = BindingMode.OneWay });
+ text.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(FontFamily)) { Source = this, Mode = BindingMode.OneWay });
+ text.SetBinding(TextBlock.FontWeightProperty, new Binding(nameof(FontWeight)) { Source = this, Mode = BindingMode.OneWay });
+ text.SetBinding(TextBlock.FontStretchProperty, new Binding(nameof(FontStretch)) { Source = this, Mode = BindingMode.OneWay });
+ text.SetBinding(TextBlock.FontStyleProperty, new Binding(nameof(FontStyle)) { Source = this, Mode = BindingMode.OneWay });
+ text.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(HeaderForeground)) { Source = this, Mode = BindingMode.OneWay, TargetNullValue = Foreground });
}
private void UpdateHeaderText()
diff --git a/DiffPlex.Wpf/Controls/Helper.cs b/DiffPlex.Wpf/Controls/Helper.cs
index bb0a8a8c..661bcad2 100644
--- a/DiffPlex.Wpf/Controls/Helper.cs
+++ b/DiffPlex.Wpf/Controls/Helper.cs
@@ -17,7 +17,7 @@ internal static class Helper
///
/// Updates the inline diffs view.
///
- internal static void RenderInlineDiffs(InternalLinesViewer viewer, ICollection lines, UIElement source, int contextLineCount)
+ internal static void RenderInlineDiffs(InternalLinesViewer viewer, IReadOnlyCollection lines, UIElement source, int contextLineCount)
{
viewer.Clear();
if (lines == null) return;
@@ -84,7 +84,7 @@ internal static void RenderInlineDiffs(InternalLinesViewer viewer, ICollection lines, bool isOld, UIElement source, int contextLineCount)
+ internal static void InsertLines(InternalLinesViewer panel, IReadOnlyCollection lines, bool isOld, UIElement source, int contextLineCount)
{
if (lines == null || panel == null) return;
var guid = panel.TrackingId = Guid.NewGuid();
@@ -98,7 +98,7 @@ internal static void InsertLines(InternalLinesViewer panel, List line
_ = InsertLinesAsync(guid, panel, lines, isOld, source, contextLineCount);
}
- private static async Task InsertLinesAsync(Guid guid, InternalLinesViewer panel, List lines, bool isOld, UIElement source, int contextLineCount)
+ private static async Task InsertLinesAsync(Guid guid, InternalLinesViewer panel, IReadOnlyCollection lines, bool isOld, UIElement source, int contextLineCount)
{ // For performance.
if (lines == null || panel == null) return;
var disablePieces = lines.Count > MaxCount;
@@ -474,7 +474,7 @@ private static List> GetSubPiecesInfo(DiffPiece lin
return details;
}
- private static void InsertLinesInteral(InternalLinesViewer panel, List lines, bool isOld, UIElement source, bool disableSubPieces = false)
+ private static void InsertLinesInteral(InternalLinesViewer panel, IReadOnlyCollection lines, bool isOld, UIElement source, bool disableSubPieces = false)
{
foreach (var line in lines)
{
diff --git a/DiffPlex.Wpf/Controls/InlineDiffViewer.xaml.cs b/DiffPlex.Wpf/Controls/InlineDiffViewer.xaml.cs
index 692dfab1..49d6de2d 100644
--- a/DiffPlex.Wpf/Controls/InlineDiffViewer.xaml.cs
+++ b/DiffPlex.Wpf/Controls/InlineDiffViewer.xaml.cs
@@ -171,7 +171,7 @@ public DiffPaneModel DiffModel
///
/// Gets the lines in the diff model.
///
- public IReadOnlyList Lines => DiffModel?.Lines?.AsReadOnly();
+ public IReadOnlyList Lines => DiffModel?.Lines;
///
/// Gets or sets the foreground brush of the line number.
diff --git a/DiffPlex.Wpf/DiffPlex.Wpf.csproj b/DiffPlex.Wpf/DiffPlex.Wpf.csproj
index 74f23611..1ccd8ffd 100644
--- a/DiffPlex.Wpf/DiffPlex.Wpf.csproj
+++ b/DiffPlex.Wpf/DiffPlex.Wpf.csproj
@@ -1,16 +1,16 @@
- net9.0-windows;net8.0-windows;net6.0-windows;net46;net48
+ net9.0-windows;net8.0-windows;net6.0-windows;net462;net48
true
true
- 1.5.0
+ 1.6.0
DiffPlex.Wpf
DiffPlex.Wpf
diff, wpf
DiffPlex.Wpf is a WPF control library that allows you to programatically render visual text diffs in your application. It also provide a diff viewer control used in Windows Forms application.
- 1.5.0.0
- 1.5.0.0
+ 1.6.0.0
+ 1.6.0.0
..\DiffPlex.ico
true
@@ -41,7 +41,7 @@
-
+
diff --git a/DiffPlex.sln b/DiffPlex.sln
index f4cddd81..d8d01db1 100644
--- a/DiffPlex.sln
+++ b/DiffPlex.sln
@@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitattributes = .gitattributes
.gitignore = .gitignore
azure-pipelines.yml = azure-pipelines.yml
+ Directory.Packages.props = Directory.Packages.props
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
global.json = global.json
License.txt = License.txt
diff --git a/DiffPlex/Chunkers/CharacterChunker.cs b/DiffPlex/Chunkers/CharacterChunker.cs
index 8076c4fe..ff4f9c57 100644
--- a/DiffPlex/Chunkers/CharacterChunker.cs
+++ b/DiffPlex/Chunkers/CharacterChunker.cs
@@ -1,19 +1,31 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
-namespace DiffPlex.Chunkers
+namespace DiffPlex.Chunkers;
+
+public class CharacterChunker : IChunker
+#if !NET_TOO_OLD_VER
+ , ISpanChunker
+#endif
{
- public class CharacterChunker:IChunker
+ ///
+ /// Gets the default singleton instance of the chunker.
+ ///
+ public static CharacterChunker Instance { get; } = new CharacterChunker();
+
+ public IReadOnlyList Chunk(string text)
{
- ///
- /// Gets the default singleton instance of the chunker.
- ///
- public static CharacterChunker Instance { get; } = new CharacterChunker();
+ var s = new string[text.Length];
+ for (int i = 0; i < text.Length; i++) s[i] = text[i].ToString();
+ return s;
+ }
- public IReadOnlyList Chunk(string text)
- {
- var s = new string[text.Length];
- for (int i = 0; i < text.Length; i++) s[i] = text[i].ToString();
- return s;
- }
+#if !NET_TOO_OLD_VER
+ public IReadOnlyList Chunk(ReadOnlySpan text)
+ {
+ var list = new List();
+ for (int i = 0; i < text.Length; i++) list.Add(text[i].ToString());
+ return list;
}
+#endif
}
\ No newline at end of file
diff --git a/DiffPlex/Chunkers/CustomFunctionChunker.cs b/DiffPlex/Chunkers/CustomFunctionChunker.cs
index ac63c730..df181eeb 100644
--- a/DiffPlex/Chunkers/CustomFunctionChunker.cs
+++ b/DiffPlex/Chunkers/CustomFunctionChunker.cs
@@ -1,21 +1,30 @@
using System;
using System.Collections.Generic;
-namespace DiffPlex.Chunkers
+namespace DiffPlex.Chunkers;
+
+public class CustomFunctionChunker : IChunker
+#if !NET_TOO_OLD_VER
+ , ISpanChunker
+#endif
{
- public class CustomFunctionChunker: IChunker
+ private readonly Func> customChunkerFunc;
+
+ public CustomFunctionChunker(Func> customChunkerFunc)
{
- private readonly Func> customChunkerFunc;
+ if (customChunkerFunc == null) throw new ArgumentNullException(nameof(customChunkerFunc));
+ this.customChunkerFunc = customChunkerFunc;
+ }
- public CustomFunctionChunker(Func> customChunkerFunc)
- {
- if (customChunkerFunc == null) throw new ArgumentNullException(nameof(customChunkerFunc));
- this.customChunkerFunc = customChunkerFunc;
- }
+ public IReadOnlyList Chunk(string text)
+ {
+ return customChunkerFunc(text);
+ }
- public IReadOnlyList Chunk(string text)
- {
- return customChunkerFunc(text);
- }
+#if !NET_TOO_OLD_VER
+ public IReadOnlyList Chunk(ReadOnlySpan text)
+ {
+ return customChunkerFunc(text.ToString());
}
+#endif
}
\ No newline at end of file
diff --git a/DiffPlex/Chunkers/DelimiterChunker.cs b/DiffPlex/Chunkers/DelimiterChunker.cs
index cdb3ebb0..e5d91dab 100644
--- a/DiffPlex/Chunkers/DelimiterChunker.cs
+++ b/DiffPlex/Chunkers/DelimiterChunker.cs
@@ -1,82 +1,148 @@
using System;
using System.Collections.Generic;
-namespace DiffPlex.Chunkers
+namespace DiffPlex.Chunkers;
+
+public class DelimiterChunker : IChunker
+#if !NET_TOO_OLD_VER
+ , ISpanChunker
+#endif
{
- public class DelimiterChunker : IChunker
+ private readonly char[] delimiters;
+
+ public DelimiterChunker(char[] delimiters)
{
- private readonly char[] delimiters;
+ if (delimiters is null || delimiters.Length == 0)
+ {
+ throw new ArgumentException($"{nameof(delimiters)} cannot be null or empty.", nameof(delimiters));
+ }
- public DelimiterChunker(char[] delimiters)
+ this.delimiters = delimiters;
+ }
+
+ public IReadOnlyList Chunk(string str)
+ {
+ var list = new List();
+ var begin = 0;
+ var processingDelim = false;
+ var delimBegin = 0;
+ for (var i = 0; i < str.Length; i++)
{
- if (delimiters is null || delimiters.Length == 0)
+ if (Array.IndexOf(delimiters, str[i]) != -1)
{
- throw new ArgumentException($"{nameof(delimiters)} cannot be null or empty.", nameof(delimiters));
+ if (i >= str.Length - 1)
+ {
+ if (processingDelim)
+ {
+ list.Add(str.Substring(delimBegin, i + 1 - delimBegin));
+ }
+ else
+ {
+ list.Add(str.Substring(begin, i - begin));
+ list.Add(str.Substring(i, 1));
+ }
+ }
+ else
+ {
+ if (!processingDelim)
+ {
+ // Add everything up to this delimeter as the next chunk (if there is anything)
+ if (i - begin > 0)
+ {
+ list.Add(str.Substring(begin, i - begin));
+ }
+
+ processingDelim = true;
+ delimBegin = i;
+ }
+ }
+
+ begin = i + 1;
}
+ else
+ {
+ if (processingDelim)
+ {
+ if (i - delimBegin > 0)
+ {
+ list.Add(str.Substring(delimBegin, i - delimBegin));
+ }
- this.delimiters = delimiters;
+ processingDelim = false;
+ }
+
+ // If we are at the end, add the remaining as the last chunk
+ if (i >= str.Length - 1)
+ {
+ list.Add(str.Substring(begin, i + 1 - begin));
+ }
+ }
}
- public IReadOnlyList Chunk(string str)
+ return list;
+ }
+
+#if !NET_TOO_OLD_VER
+ public IReadOnlyList Chunk(ReadOnlySpan str)
+ {
+ var list = new List();
+ var begin = 0;
+ var processingDelim = false;
+ var delimBegin = 0;
+ for (var i = 0; i < str.Length; i++)
{
- var list = new List();
- int begin = 0;
- bool processingDelim = false;
- int delimBegin = 0;
- for (int i = 0; i < str.Length; i++)
+ if (Array.IndexOf(delimiters, str[i]) != -1)
{
- if (Array.IndexOf(delimiters, str[i]) != -1)
+ if (i >= str.Length - 1)
{
- if (i >= str.Length - 1)
+ if (processingDelim)
{
- if (processingDelim)
- {
- list.Add(str.Substring(delimBegin, (i + 1 - delimBegin)));
- }
- else
- {
- list.Add(str.Substring(begin, (i - begin)));
- list.Add(str.Substring(i, 1));
- }
+ list.Add(str.Slice(delimBegin, i + 1 - delimBegin).ToString());
}
else
{
- if (!processingDelim)
- {
- // Add everything up to this delimeter as the next chunk (if there is anything)
- if (i - begin > 0)
- {
- list.Add(str.Substring(begin, (i - begin)));
- }
-
- processingDelim = true;
- delimBegin = i;
- }
+ list.Add(str.Slice(begin, i - begin).ToString());
+ list.Add(str.Slice(i, 1).ToString());
}
-
- begin = i + 1;
}
else
{
- if (processingDelim)
+ if (!processingDelim)
{
- if (i - delimBegin > 0)
+ // Add everything up to this delimeter as the next chunk (if there is anything)
+ if (i - begin > 0)
{
- list.Add(str.Substring(delimBegin, (i - delimBegin)));
+ list.Add(str.Slice(begin, i - begin).ToString());
}
- processingDelim = false;
+ processingDelim = true;
+ delimBegin = i;
}
+ }
- // If we are at the end, add the remaining as the last chunk
- if (i >= str.Length - 1)
+ begin = i + 1;
+ }
+ else
+ {
+ if (processingDelim)
+ {
+ if (i - delimBegin > 0)
{
- list.Add(str.Substring(begin, (i + 1 - begin)));
+ list.Add(str.Slice(delimBegin, i - delimBegin).ToString());
}
+
+ processingDelim = false;
}
- }
- return list;
+ // If we are at the end, add the remaining as the last chunk
+ if (i >= str.Length - 1)
+ {
+ list.Add(str.Slice(begin, i + 1 - begin).ToString());
+ }
+ }
}
+
+ return list;
}
+#endif
}
\ No newline at end of file
diff --git a/DiffPlex/Chunkers/LineChunker.cs b/DiffPlex/Chunkers/LineChunker.cs
index 79c6b6ed..6a4b5560 100644
--- a/DiffPlex/Chunkers/LineChunker.cs
+++ b/DiffPlex/Chunkers/LineChunker.cs
@@ -1,20 +1,30 @@
using System;
using System.Collections.Generic;
-namespace DiffPlex.Chunkers
+namespace DiffPlex.Chunkers;
+
+public class LineChunker : IChunker
+#if !NET_TOO_OLD_VER
+ , ISpanChunker
+#endif
{
- public class LineChunker:IChunker
- {
- private readonly string[] lineSeparators = new[] {"\r\n", "\r", "\n"};
+ private readonly string[] lineSeparators = new[] {"\r\n", "\r", "\n"};
+
+ ///
+ /// Gets the default singleton instance of the chunker.
+ ///
+ public static LineChunker Instance { get; } = new LineChunker();
- ///
- /// Gets the default singleton instance of the chunker.
- ///
- public static LineChunker Instance { get; } = new LineChunker();
+ public IReadOnlyList Chunk(string text)
+ {
+ return text.Split(lineSeparators, StringSplitOptions.None);
+ }
- public IReadOnlyList Chunk(string text)
- {
- return text.Split(lineSeparators, StringSplitOptions.None);
- }
+#if !NET_TOO_OLD_VER
+ public IReadOnlyList Chunk(ReadOnlySpan text)
+ {
+ // MemoryExtensions.Split(text, lineSeparators, StringSplitOptions.None);
+ return text.ToString().Split(lineSeparators, StringSplitOptions.None);
}
+#endif
}
\ No newline at end of file
diff --git a/DiffPlex/Chunkers/LineEndingsPreservingChunker.cs b/DiffPlex/Chunkers/LineEndingsPreservingChunker.cs
index a3f8b9b8..58865fdb 100644
--- a/DiffPlex/Chunkers/LineEndingsPreservingChunker.cs
+++ b/DiffPlex/Chunkers/LineEndingsPreservingChunker.cs
@@ -1,51 +1,87 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
-namespace DiffPlex.Chunkers
-{
- public class LineEndingsPreservingChunker:IChunker
- {
- private readonly string[] emptyArray = new string[0];
+namespace DiffPlex.Chunkers;
- ///
- /// Gets the default singleton instance of the chunker.
- ///
- public static LineEndingsPreservingChunker Instance { get; } = new LineEndingsPreservingChunker();
+public class LineEndingsPreservingChunker : IChunker
+#if !NET_TOO_OLD_VER
+ , ISpanChunker
+#endif
+{
+ ///
+ /// Gets the default singleton instance of the chunker.
+ ///
+ public static LineEndingsPreservingChunker Instance { get; } = new LineEndingsPreservingChunker();
- public IReadOnlyList Chunk(string text)
+ public IReadOnlyList Chunk(string text)
+ {
+ var output = new List();
+ if (string.IsNullOrEmpty(text)) return output;
+ var lastCut = 0;
+ for (var currentPosition = 0; currentPosition < text.Length; currentPosition++)
{
- if (string.IsNullOrEmpty(text))
- return emptyArray;
-
- var output = new List();
- var lastCut = 0;
- for (var currentPosition = 0; currentPosition < text.Length; currentPosition++)
+ char ch = text[currentPosition];
+ switch (ch)
{
- char ch = text[currentPosition];
- switch (ch)
- {
- case '\n':
- case '\r':
+ case '\n':
+ case '\r':
+ currentPosition++;
+ if (ch == '\r' && currentPosition < text.Length && text[currentPosition] == '\n')
+ {
currentPosition++;
- if (ch == '\r' && currentPosition < text.Length && text[currentPosition] == '\n')
- {
- currentPosition++;
- }
- var str = text.Substring(lastCut, currentPosition - lastCut);
- lastCut = currentPosition;
- output.Add(str);
- break;
- default:
- continue;
- }
+ }
+ var str = text.Substring(lastCut, currentPosition - lastCut);
+ lastCut = currentPosition;
+ output.Add(str);
+ break;
+ default:
+ continue;
}
+ }
+
+ if (lastCut != text.Length)
+ {
+ var str = text.Substring(lastCut, text.Length - lastCut);
+ output.Add(str);
+ }
- if (lastCut != text.Length)
+ return output;
+ }
+
+#if !NET_TOO_OLD_VER
+ public IReadOnlyList Chunk(ReadOnlySpan text)
+ {
+ var output = new List();
+ if (text.Length == 0) return output;
+ var lastCut = 0;
+ for (var currentPosition = 0; currentPosition < text.Length; currentPosition++)
+ {
+ char ch = text[currentPosition];
+ switch (ch)
{
- var str = text.Substring(lastCut, text.Length - lastCut);
- output.Add(str);
+ case '\n':
+ case '\r':
+ currentPosition++;
+ if (ch == '\r' && currentPosition < text.Length && text[currentPosition] == '\n')
+ {
+ currentPosition++;
+ }
+ var str = text.Slice(lastCut, currentPosition - lastCut);
+ lastCut = currentPosition;
+ output.Add(str.ToString());
+ break;
+ default:
+ continue;
}
+ }
- return output;
+ if (lastCut != text.Length)
+ {
+ var str = text.Slice(lastCut, text.Length - lastCut);
+ output.Add(str.ToString());
}
+
+ return output;
}
+#endif
}
\ No newline at end of file
diff --git a/DiffPlex/Chunkers/WordChunker.cs b/DiffPlex/Chunkers/WordChunker.cs
index 66a9386f..91992bfb 100644
--- a/DiffPlex/Chunkers/WordChunker.cs
+++ b/DiffPlex/Chunkers/WordChunker.cs
@@ -1,16 +1,15 @@
-namespace DiffPlex.Chunkers
+namespace DiffPlex.Chunkers;
+
+public class WordChunker : DelimiterChunker
{
- public class WordChunker:DelimiterChunker
- {
- private static char[] WordSeparators { get; } = { ' ', '\t', '.', '(', ')', '{', '}', ',', '!', '?', ';' };
+ private static char[] WordSeparators { get; } = { ' ', ' ', '\t', '.', '(', ')', '{', '}', '[', ']', ',', '!', '?', ';', ':', '\'', '"', '~', '&', '|', ',', '、', ';', ':', '‘', '’', '“', '”', '(', ')', '【', '】', '『', '』', '「', '」', '《', '》', '。', '!', '?', '~', '—', '…' };
- ///
- /// Gets the default singleton instance of the chunker.
- ///
- public static WordChunker Instance { get; } = new WordChunker();
+ ///
+ /// Gets the default singleton instance of the chunker.
+ ///
+ public static WordChunker Instance { get; } = new();
- public WordChunker() : base(WordSeparators)
- {
- }
+ public WordChunker() : base(WordSeparators)
+ {
}
-}
\ No newline at end of file
+}
diff --git a/DiffPlex/DiffBuilder/IInlineDiffBuilder.cs b/DiffPlex/DiffBuilder/IInlineDiffBuilder.cs
index 4bcf306c..29a3d4ba 100644
--- a/DiffPlex/DiffBuilder/IInlineDiffBuilder.cs
+++ b/DiffPlex/DiffBuilder/IInlineDiffBuilder.cs
@@ -1,10 +1,9 @@
using DiffPlex.DiffBuilder.Model;
-namespace DiffPlex.DiffBuilder
+namespace DiffPlex.DiffBuilder;
+
+public interface IInlineDiffBuilder
{
- public interface IInlineDiffBuilder
- {
- DiffPaneModel BuildDiffModel(string oldText, string newText);
- DiffPaneModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace, bool ignoreCase, IChunker chunker);
- }
+ DiffPaneModel BuildDiffModel(string oldText, string newText);
+ DiffPaneModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace, bool ignoreCase, IChunker chunker);
}
diff --git a/DiffPlex/DiffBuilder/ISideBySideDiffBuilder.cs b/DiffPlex/DiffBuilder/ISideBySideDiffBuilder.cs
index 02c93a07..8a5313dd 100644
--- a/DiffPlex/DiffBuilder/ISideBySideDiffBuilder.cs
+++ b/DiffPlex/DiffBuilder/ISideBySideDiffBuilder.cs
@@ -1,18 +1,17 @@
using DiffPlex.DiffBuilder.Model;
-namespace DiffPlex.DiffBuilder
+namespace DiffPlex.DiffBuilder;
+
+///
+/// Provides methods that generate differences between texts for displaying in a side by side view.
+///
+public interface ISideBySideDiffBuilder
{
///
- /// Provides methods that generate differences between texts for displaying in a side by side view.
+ /// Builds a diff model for displaying diffs in a side by side view
///
- public interface ISideBySideDiffBuilder
- {
- ///
- /// Builds a diff model for displaying diffs in a side by side view
- ///
- /// The old text.
- /// The new text.
- /// The side by side diff model
- SideBySideDiffModel BuildDiffModel(string oldText, string newText);
- }
+ /// The old text.
+ /// The new text.
+ /// The side by side diff model
+ SideBySideDiffModel BuildDiffModel(string oldText, string newText);
}
\ No newline at end of file
diff --git a/DiffPlex/DiffBuilder/InlineDiffBuilder.cs b/DiffPlex/DiffBuilder/InlineDiffBuilder.cs
index 730341c5..8c608507 100644
--- a/DiffPlex/DiffBuilder/InlineDiffBuilder.cs
+++ b/DiffPlex/DiffBuilder/InlineDiffBuilder.cs
@@ -4,116 +4,147 @@
using DiffPlex.DiffBuilder.Model;
using DiffPlex.Model;
-namespace DiffPlex.DiffBuilder
+namespace DiffPlex.DiffBuilder;
+
+public class InlineDiffBuilder : IInlineDiffBuilder
{
- public class InlineDiffBuilder : IInlineDiffBuilder
+ private readonly IDiffer differ;
+
+ ///
+ /// Gets the default singleton instance of the inline diff builder.
+ ///
+ public static InlineDiffBuilder Instance { get; } = new InlineDiffBuilder();
+
+ public InlineDiffBuilder(IDiffer differ = null)
{
- private readonly IDiffer differ;
+ this.differ = differ ?? Differ.Instance;
+ }
- ///
- /// Gets the default singleton instance of the inline diff builder.
- ///
- public static InlineDiffBuilder Instance { get; } = new InlineDiffBuilder();
+ public DiffPaneModel BuildDiffModel(string oldText, string newText)
+ => BuildDiffModel(oldText, newText, ignoreWhitespace: true);
- public InlineDiffBuilder(IDiffer differ = null)
- {
- this.differ = differ ?? Differ.Instance;
- }
+ public DiffPaneModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace)
+ {
+ var chunker = new LineChunker();
+ return BuildDiffModel(oldText, newText, ignoreWhitespace, false, chunker);
+ }
- public DiffPaneModel BuildDiffModel(string oldText, string newText)
- => BuildDiffModel(oldText, newText, ignoreWhitespace: true);
+ public DiffPaneModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace, bool ignoreCase, IChunker chunker)
+ {
+ if (oldText == null) throw new ArgumentNullException(nameof(oldText));
+ if (newText == null) throw new ArgumentNullException(nameof(newText));
- public DiffPaneModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace)
- {
- var chunker = new LineChunker();
- return BuildDiffModel(oldText, newText, ignoreWhitespace, false, chunker);
- }
+ var pieces = new List();
+ var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhitespace, ignoreCase: ignoreCase, chunker);
+ BuildDiffPieces(diffResult, pieces);
+ return new(pieces);
+ }
- public DiffPaneModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace, bool ignoreCase, IChunker chunker)
- {
- if (oldText == null) throw new ArgumentNullException(nameof(oldText));
- if (newText == null) throw new ArgumentNullException(nameof(newText));
-
- var model = new DiffPaneModel();
- var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhitespace, ignoreCase: ignoreCase, chunker);
- BuildDiffPieces(diffResult, model.Lines);
-
- return model;
- }
+ ///
+ /// Gets the inline textual diffs.
+ ///
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The chunker.
+ /// The diffs result.
+ public static DiffPaneModel Diff(string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker chunker = null)
+ {
+ return Diff(Differ.Instance, oldText, newText, ignoreWhiteSpace, ignoreCase, chunker);
+ }
- ///
- /// Gets the inline textual diffs.
- ///
- /// The old text to diff.
- /// The new text.
- /// if ignore the white space; otherwise, .
- /// if case-insensitive; otherwise, .
- /// The chunker.
- /// The diffs result.
- public static DiffPaneModel Diff(string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker chunker = null)
- {
- return Diff(Differ.Instance, oldText, newText, ignoreWhiteSpace, ignoreCase, chunker);
- }
+#if !NET_TOO_OLD_VER
+ ///
+ /// Gets the inline textual diffs.
+ ///
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The chunker.
+ /// The diffs result.
+ public static DiffPaneModel Diff(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker chunker = null)
+ {
+ return Diff(Differ.Instance, oldText, newText, ignoreWhiteSpace, ignoreCase, chunker);
+ }
- ///
- /// Gets the inline textual diffs.
- ///
- /// The differ instance.
- /// The old text to diff.
- /// The new text.
- /// if ignore the white space; otherwise, .
- /// if case-insensitive; otherwise, .
- /// The chunker.
- /// The diffs result.
- public static DiffPaneModel Diff(IDiffer differ, string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker chunker = null)
- {
- if (oldText == null) throw new ArgumentNullException(nameof(oldText));
- if (newText == null) throw new ArgumentNullException(nameof(newText));
-
- var model = new DiffPaneModel();
- var diffResult = (differ ?? Differ.Instance).CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, chunker ?? LineChunker.Instance);
- BuildDiffPieces(diffResult, model.Lines);
-
- return model;
- }
+ ///
+ /// Gets the inline textual diffs.
+ ///
+ /// The differ instance.
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The chunker.
+ /// The diffs result.
+ public static DiffPaneModel Diff(IDiffer differ, ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker chunker = null)
+ {
+ var pieces = new List();
+ var diffResult = (differ ?? Differ.Instance).CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, chunker ?? LineChunker.Instance);
+ BuildDiffPieces(diffResult, pieces);
+ return new(pieces);
+ }
+#endif
+
+ ///
+ /// Gets the inline textual diffs.
+ ///
+ /// The differ instance.
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The chunker.
+ /// The diffs result.
+ public static DiffPaneModel Diff(IDiffer differ, string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker chunker = null)
+ {
+ if (oldText == null) throw new ArgumentNullException(nameof(oldText));
+ if (newText == null) throw new ArgumentNullException(nameof(newText));
- private static void BuildDiffPieces(DiffResult diffResult, List pieces)
+ var pieces = new List();
+ var diffResult = (differ ?? Differ.Instance).CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, chunker ?? LineChunker.Instance);
+ BuildDiffPieces(diffResult, pieces);
+ return new(pieces);
+ }
+
+ private static void BuildDiffPieces(DiffResult diffResult, List pieces)
+ {
+ int bPos = 0;
+
+ foreach (var diffBlock in diffResult.DiffBlocks)
{
- int bPos = 0;
+ for (; bPos < diffBlock.InsertStartB; bPos++)
+ pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, 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));
- foreach (var diffBlock in diffResult.DiffBlocks)
+ i = 0;
+ for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
{
- for (; bPos < diffBlock.InsertStartB; bPos++)
- pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
+ pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
+ bPos++;
+ }
- int i = 0;
- for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
+ if (diffBlock.DeleteCountA > diffBlock.InsertCountB)
+ {
+ for (; i < diffBlock.DeleteCountA; i++)
pieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted));
-
- i = 0;
- for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
+ }
+ else
+ {
+ for (; i < diffBlock.InsertCountB; i++)
{
pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
bPos++;
}
-
- if (diffBlock.DeleteCountA > diffBlock.InsertCountB)
- {
- for (; i < diffBlock.DeleteCountA; i++)
- pieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted));
- }
- else
- {
- for (; i < diffBlock.InsertCountB; i++)
- {
- pieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
- bPos++;
- }
- }
}
-
- for (; bPos < diffResult.PiecesNew.Count; bPos++)
- pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
}
+
+ for (; bPos < diffResult.PiecesNew.Count; bPos++)
+ pieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
}
}
diff --git a/DiffPlex/DiffBuilder/Model/DiffPaneModel.cs b/DiffPlex/DiffBuilder/Model/DiffPaneModel.cs
index f7a19831..3797b23f 100644
--- a/DiffPlex/DiffBuilder/Model/DiffPaneModel.cs
+++ b/DiffPlex/DiffBuilder/Model/DiffPaneModel.cs
@@ -1,20 +1,110 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
-namespace DiffPlex.DiffBuilder.Model
+namespace DiffPlex.DiffBuilder.Model;
+
+///
+/// The model of diff lines.
+///
+#if !NET_TOO_OLD_VER
+[System.Text.Json.Serialization.JsonConverter(typeof(JsonDiffPaneConverter))]
+#endif
+public class DiffPaneModel
{
- public class DiffPaneModel
+ ///
+ /// Initializes a new instance of the DiffPaneModel class.
+ ///
+ /// The lines.
+ public DiffPaneModel(IEnumerable lines)
{
- public List Lines { get; }
+#if NETSTANDARD1_0
+ if (lines is null)
+ Lines = new List();
+ else if (lines is IReadOnlyList col)
+ Lines = col;
+ else
+ Lines = lines.ToList();
+#else
+ if (lines is List list)
+ Lines = list.AsReadOnly();
+ else if (lines is IReadOnlyList col)
+ Lines = col;
+ else if (lines is null)
+ Lines = new List();
+ else
+ Lines = lines.ToList().AsReadOnly();
+#endif
+ }
- public bool HasDifferences
- {
- get { return Lines.Any(x => x.Type != ChangeType.Unchanged); }
- }
+ ///
+ /// Gets all the lines.
+ ///
+ public IReadOnlyList Lines { get; }
- public DiffPaneModel()
- {
- Lines = new List();
- }
+ ///
+ /// Gets the count of lines.
+ ///
+ public int Count => Lines.Count;
+
+ ///
+ /// Gets a value indicating whether it contains any difference.
+ ///
+ public bool HasDifferences => Lines.Any(x => x.Type != ChangeType.Unchanged);
+
+ ///
+ /// Concatenates the members of a constructed this collection of type string, using the specified separator between each line.
+ ///
+ /// The line separator which is included in the returned string only if values has multiple lines.
+ /// The handler to format each line.
+ /// true if skip null returned by the lineGenerator; otherwise, false.
+ /// A string that consists of the elements of values delimited by the separator string; or empty if values has zero elements.
+ public string Join(string separator, Func lineGenerator, bool skipNull = false)
+ {
+ var col = Lines.Select(lineGenerator);
+ if (skipNull) col = col.Where(ele => ele != null);
+ return string.Join(separator, col);
}
-}
\ No newline at end of file
+
+ ///
+ /// Concatenates the members of a constructed this collection of type string in each line.
+ ///
+ /// The handler to format each line.
+ /// true if skip null returned by the lineGenerator; otherwise, false.
+ /// A string that consists of the elements of values delimited by the separator string; or empty if values has zero elements.
+ public string Join(Func lineGenerator, bool skipNull = false)
+ => Join(Environment.NewLine, lineGenerator, skipNull);
+
+ ///
+ /// Concatenates the members of a constructed this collection of type string, using the specified separator between each line.
+ ///
+ /// The line separator which is included in the returned string only if values has multiple lines.
+ /// The handler to format each line.
+ /// true if skip null returned by the lineGenerator; otherwise, false.
+ /// A string that consists of the elements of values delimited by the separator string; or empty if values has zero elements.
+ public string Join(string separator, Func lineGenerator, bool skipNull = false)
+ {
+ var col = Lines.Select(lineGenerator);
+ if (skipNull) col = col.Where(ele => ele != null);
+ return string.Join(separator, col);
+ }
+
+ ///
+ /// Concatenates the members of a constructed this collection of type string in each line.
+ ///
+ /// The handler to format each line.
+ /// true if skip null returned by the lineGenerator; otherwise, false.
+ /// A string that consists of the elements of values delimited by the separator string; or empty if values has zero elements.
+ public string Join(Func lineGenerator, bool skipNull = false)
+ => Join(Environment.NewLine, lineGenerator, skipNull);
+
+ ///
+ /// Concatenates the members of a constructed this collection of type string in each line.
+ ///
+ /// A string that consists of the elements of values delimited by the separator string; or empty if values has zero elements.
+ public string Join()
+ => Join(Environment.NewLine, FormatToString, true);
+
+ private static string FormatToString(DiffPiece value)
+ => value?.ToString();
+}
diff --git a/DiffPlex/DiffBuilder/Model/DiffPiece.cs b/DiffPlex/DiffBuilder/Model/DiffPiece.cs
index 5dc5f4f9..bc609c1f 100644
--- a/DiffPlex/DiffBuilder/Model/DiffPiece.cs
+++ b/DiffPlex/DiffBuilder/Model/DiffPiece.cs
@@ -1,77 +1,172 @@
using System;
using System.Collections.Generic;
+using System.Text;
-namespace DiffPlex.DiffBuilder.Model
+namespace DiffPlex.DiffBuilder.Model;
+
+public enum ChangeType : byte
+{
+ Unchanged,
+ Deleted,
+ Inserted,
+ Imaginary,
+ Modified
+}
+
+///
+/// The diff piece model.
+///
+#if !NET_TOO_OLD_VER
+[System.Text.Json.Serialization.JsonConverter(typeof(JsonDiffPieceConverter))]
+#endif
+public class DiffPiece : IEquatable
{
- public enum ChangeType
+ ///
+ /// Gets the change type.
+ ///
+ public ChangeType Type { get; }
+
+ ///
+ /// Gets the nullable zero-based position.
+ ///
+ public int? Position { get; }
+
+ ///
+ /// Gets the content text.
+ ///
+ public string Text { get; }
+
+ ///
+ /// Gets the sub pieces.
+ ///
+ public IReadOnlyList SubPieces { get; }
+
+ ///
+ /// Initializes a new instance of the DiffPiece class.
+ ///
+ public DiffPiece()
+ : this(null, ChangeType.Imaginary)
{
- Unchanged,
- Deleted,
- Inserted,
- Imaginary,
- Modified
}
- public class DiffPiece : IEquatable
+ ///
+ /// Initializes a new instance of the DiffPiece class.
+ ///
+ /// The content text.
+ /// The change type.
+ /// The nullable zero-based position
+ public DiffPiece(string text, ChangeType type, int? position = null)
+ : this(text, type, position, null)
{
- public ChangeType Type { get; set; }
- public int? Position { get; set; }
- public string Text { get; set; }
- public List SubPieces { get; set; } = new List();
+ }
- public DiffPiece(string text, ChangeType type, int? position = null)
- {
- Text = text;
- Position = position;
- Type = type;
- }
+ ///
+ /// Initializes a new instance of the DiffPiece class.
+ ///
+ /// The content text.
+ /// The change type.
+ /// The nullable zero-based position
+ /// The sub pieces.
+ public DiffPiece(string text, ChangeType type, int? position, IReadOnlyList subPieces)
+ {
+ Text = text;
+ Position = position;
+ Type = type;
+ SubPieces = subPieces ?? new List();
+ }
- public DiffPiece()
- : this(null, ChangeType.Imaginary)
- {
- }
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as DiffPiece);
+ }
- public override bool Equals(object obj)
- {
- return Equals(obj as DiffPiece);
- }
+ public bool Equals(DiffPiece other)
+ {
+ return other != null
+ && Type == other.Type
+ && EqualityComparer.Default.Equals(Position, other.Position)
+ && Text == other.Text
+ && SubPiecesEqual(other);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = 1688038063;
+ hashCode = hashCode * -1521134295 + Type.GetHashCode();
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Position);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Text);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(SubPieces?.Count);
+ return hashCode;
+ }
- public bool Equals(DiffPiece other)
+#if !NET_TOO_OLD_VER
+ ///
+ /// Writes current diff piece into UTF-8 JSON stream.
+ ///
+ /// The UTF-8 JSON stream writer.
+ /// The JSON srialization options.
+ public void Write(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString("type", Type.ToString());
+ if (Position.HasValue) writer.WriteNumber("position", Position.Value);
+ writer.WriteString("text", Text);
+ if (SubPieces.Count > 0)
{
- return other != null
- && Type == other.Type
- && EqualityComparer.Default.Equals(Position, other.Position)
- && Text == other.Text
- && SubPiecesEqual(other);
+ writer.WriteStartArray("sub");
+ JsonDiffPieceConverter.Write(SubPieces, writer, options);
+ writer.WriteEndArray();
}
- public override int GetHashCode()
+ writer.WriteEndObject();
+ }
+#endif
+
+ ///
+ /// Returns a string that represents this diff piece.
+ ///
+ /// A string that represents this diff piece.
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ if (Position.HasValue) sb.Append(Position.Value);
+ sb.Append('\t');
+ switch (Type)
{
- var hashCode = 1688038063;
- hashCode = hashCode * -1521134295 + Type.GetHashCode();
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Position);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Text);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(SubPieces?.Count);
- return hashCode;
+ case ChangeType.Inserted:
+ sb.Append("+ ");
+ break;
+ case ChangeType.Deleted:
+ sb.Append("- ");
+ break;
+ case ChangeType.Modified:
+ sb.Append("M ");
+ break;
+ default:
+ sb.Append(" ");
+ break;
}
- private bool SubPiecesEqual(DiffPiece other)
- {
- if (SubPieces is null)
- return other.SubPieces is null;
- else if (other.SubPieces is null)
- return false;
+ sb.Append(Text);
+ return sb.ToString();
+ }
- if (SubPieces.Count != other.SubPieces.Count)
- return false;
+ private bool SubPiecesEqual(DiffPiece other)
+ {
+ if (SubPieces is null)
+ return other.SubPieces is null;
+ else if (other.SubPieces is null)
+ return false;
- for (int i = 0; i < SubPieces.Count; i++)
- {
- if (!Equals(SubPieces[i], other.SubPieces[i]))
- return false;
- }
+ if (SubPieces.Count != other.SubPieces.Count)
+ return false;
- return true;
+ for (int i = 0; i < SubPieces.Count; i++)
+ {
+ if (!Equals(SubPieces[i], other.SubPieces[i]))
+ return false;
}
+
+ return true;
}
}
\ No newline at end of file
diff --git a/DiffPlex/DiffBuilder/Model/JsonConverter.cs b/DiffPlex/DiffBuilder/Model/JsonConverter.cs
new file mode 100644
index 00000000..2e50def8
--- /dev/null
+++ b/DiffPlex/DiffBuilder/Model/JsonConverter.cs
@@ -0,0 +1,206 @@
+#if !NET_TOO_OLD_VER
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace DiffPlex.DiffBuilder.Model;
+
+internal class JsonDiffPieceConverter : JsonConverter
+{
+ ///
+ public override DiffPiece Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.Null:
+ case JsonTokenType.False:
+ return default;
+ case JsonTokenType.StartObject:
+ var json = JsonElement.ParseValue(ref reader);
+ var sub = ReadList(json, "sub");
+ return Read(json, sub);
+ default:
+ throw new JsonException($"The token type is {reader.TokenType} but expect JSON object.");
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, DiffPiece value, JsonSerializerOptions options)
+ {
+ Write(value, writer, options);
+ }
+
+ private static string GetString(JsonElement json, string property)
+ {
+ if (!json.TryGetProperty(property, out var prop)) return null;
+ if (prop.ValueKind == JsonValueKind.String) return prop.GetString();
+ if (prop.ValueKind == JsonValueKind.Number) return prop.GetDouble().ToString();
+ return null;
+ }
+
+ private static int? GetInt32(JsonElement json, string property)
+ {
+ if (!json.TryGetProperty(property, out var prop)) return null;
+ if (prop.TryGetInt32(out var i)) return i;
+ return null;
+ }
+
+ private static ChangeType GetChangeType(JsonElement json, string property)
+ {
+ if (!json.TryGetProperty(property, out var prop)) return ChangeType.Imaginary;
+ switch (prop.ValueKind)
+ {
+ case JsonValueKind.Null:
+ return ChangeType.Unchanged;
+ case JsonValueKind.False:
+ return ChangeType.Deleted;
+ case JsonValueKind.True:
+ return ChangeType.Inserted;
+ case JsonValueKind.String:
+ var s = prop.GetString();
+ if (string.IsNullOrWhiteSpace(s)) return ChangeType.Imaginary;
+ return Enum.TryParse(s, true, out var v) ? v : s.ToLowerInvariant() switch
+ {
+ "+" or "add" or "insert" => ChangeType.Inserted,
+ "-" or "del" or "delete" => ChangeType.Deleted,
+ " " or "same" => ChangeType.Unchanged,
+ "m" or "mod" or "modify" => ChangeType.Modified,
+ _ => ChangeType.Imaginary
+ };
+ case JsonValueKind.Number:
+ if (prop.TryGetInt32(out var i)) return (ChangeType)i;
+ break;
+ }
+
+ return ChangeType.Imaginary;
+ }
+
+ private static DiffPiece Read(JsonElement json, IReadOnlyList sub)
+ {
+ if (json.ValueKind == JsonValueKind.Object) return new(GetString(json, "text"), GetChangeType(json, "type"), GetInt32(json, "position"), sub);
+ if (json.ValueKind == JsonValueKind.Null || json.ValueKind == JsonValueKind.Undefined || json.ValueKind == JsonValueKind.False) return null;
+ throw new JsonException($"Expect a JSON object");
+ }
+
+ internal static List ReadList(JsonElement json, string property)
+ {
+ if (!json.TryGetProperty(property, out var arr)) return null;
+ return ReadList(arr);
+ }
+
+ internal static List ReadList(JsonElement arr)
+ {
+ if (arr.ValueKind != JsonValueKind.Array)
+ {
+ if (arr.ValueKind == JsonValueKind.Object) return ReadList(arr, "lines");
+ return null;
+ }
+
+ var len = arr.GetArrayLength();
+ var list = new List();
+ for (var i = 0; i < len; i++)
+ {
+ var item = Read(arr[i], null);
+ if (item != null) list.Add(item);
+ }
+
+ return list;
+ }
+
+ internal static void Write(DiffPiece value, Utf8JsonWriter writer, JsonSerializerOptions options)
+ {
+ if (value is null) writer.WriteNullValue();
+ value.Write(writer, options);
+ }
+
+ internal static void Write(IEnumerable sub, Utf8JsonWriter writer, JsonSerializerOptions options)
+ {
+ foreach (var item in sub)
+ {
+ Write(item, writer, options);
+ }
+ }
+}
+
+internal class JsonDiffPaneConverter : JsonConverter
+{
+ ///
+ public override DiffPaneModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.Null:
+ case JsonTokenType.False:
+ return default;
+ case JsonTokenType.StartArray:
+ {
+ var arr = JsonElement.ParseValue(ref reader);
+ var list = JsonDiffPieceConverter.ReadList(arr);
+ return new(list);
+ }
+ case JsonTokenType.StartObject:
+ {
+ var json = JsonElement.ParseValue(ref reader);
+ var list = JsonDiffPieceConverter.ReadList(json, "lines");
+ return new(list);
+ }
+ default:
+ throw new JsonException($"The token type is {reader.TokenType} but expect JSON object.");
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, DiffPaneModel value, JsonSerializerOptions options)
+ {
+ if (value is null) writer.WriteNullValue();
+ writer.WriteStartObject();
+ writer.WriteStartArray("lines");
+ JsonDiffPieceConverter.Write(value.Lines, writer, options);
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+}
+
+internal class JsonSideBySideDiffConverter : JsonConverter
+{
+ ///
+ public override SideBySideDiffModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.Null:
+ return default;
+ case JsonTokenType.StartArray:
+ {
+ var arr = JsonElement.ParseValue(ref reader);
+ var list = JsonDiffPieceConverter.ReadList(arr);
+ return new(list, list);
+ }
+ case JsonTokenType.StartObject:
+ {
+ var json = JsonElement.ParseValue(ref reader);
+ var oldText = JsonDiffPieceConverter.ReadList(json, "old");
+ var newText = JsonDiffPieceConverter.ReadList(json, "new");
+ return new(oldText, newText);
+ }
+ default:
+ throw new JsonException($"The token type is {reader.TokenType} but expect JSON object.");
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, SideBySideDiffModel value, JsonSerializerOptions options)
+ {
+ if (value is null) writer.WriteNullValue();
+ writer.WriteStartObject();
+ writer.WriteStartArray("old");
+ JsonDiffPieceConverter.Write(value.OldText.Lines, writer, options);
+ writer.WriteEndArray();
+ writer.WriteStartArray("new");
+ JsonDiffPieceConverter.Write(value.NewText.Lines, writer, options);
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+}
+#endif
\ No newline at end of file
diff --git a/DiffPlex/DiffBuilder/Model/SideBySideDiffModel.cs b/DiffPlex/DiffBuilder/Model/SideBySideDiffModel.cs
index fed2bc25..08234e23 100644
--- a/DiffPlex/DiffBuilder/Model/SideBySideDiffModel.cs
+++ b/DiffPlex/DiffBuilder/Model/SideBySideDiffModel.cs
@@ -1,17 +1,34 @@
-namespace DiffPlex.DiffBuilder.Model
+using System.Collections.Generic;
+
+namespace DiffPlex.DiffBuilder.Model;
+
+///
+/// A model which represents differences between to texts to be shown side by side.
+///
+/// The old text information in diff.
+/// The new text information in diff.
+#if !NET_TOO_OLD_VER
+[System.Text.Json.Serialization.JsonConverter(typeof(JsonSideBySideDiffConverter))]
+#endif
+public class SideBySideDiffModel(DiffPaneModel oldText, DiffPaneModel newText)
{
///
- /// A model which represents differences between to texts to be shown side by side
+ /// Initializes a new instance of the SideBySideDiffModel class.
///
- public class SideBySideDiffModel
+ /// The old text information in diff.
+ /// The new text information in diff.
+ public SideBySideDiffModel(IReadOnlyList oldText, IReadOnlyList newText)
+ : this(new DiffPaneModel(oldText), new(newText))
{
- public DiffPaneModel OldText { get; }
- public DiffPaneModel NewText { get; }
-
- public SideBySideDiffModel()
- {
- OldText = new DiffPaneModel();
- NewText = new DiffPaneModel();
- }
}
+
+ ///
+ /// Gets the old text model.
+ ///
+ public DiffPaneModel OldText { get; } = oldText ?? new(null);
+
+ ///
+ /// Gets the new text model.
+ ///
+ public DiffPaneModel NewText { get; } = newText ?? new(null);
}
\ No newline at end of file
diff --git a/DiffPlex/DiffBuilder/SideBySideDiffBuilder.cs b/DiffPlex/DiffBuilder/SideBySideDiffBuilder.cs
index 251d04e5..c1cbcf55 100644
--- a/DiffPlex/DiffBuilder/SideBySideDiffBuilder.cs
+++ b/DiffPlex/DiffBuilder/SideBySideDiffBuilder.cs
@@ -1,203 +1,240 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using DiffPlex.Chunkers;
using DiffPlex.DiffBuilder.Model;
using DiffPlex.Model;
-namespace DiffPlex.DiffBuilder
-{
- public class SideBySideDiffBuilder : ISideBySideDiffBuilder
- {
- private readonly IDiffer differ;
- private readonly IChunker lineChunker;
- private readonly IChunker wordChunker;
+namespace DiffPlex.DiffBuilder;
- private delegate ChangeType PieceBuilder(string oldText, string newText, List oldPieces, List newPieces, bool ignoreWhitespace, bool ignoreCase);
+public class SideBySideDiffBuilder : ISideBySideDiffBuilder
+{
+ private readonly IDiffer differ;
+ private readonly IChunker lineChunker;
+ private readonly IChunker wordChunker;
- ///
- /// Gets the default singleton instance.
- ///
- public static SideBySideDiffBuilder Instance { get; } = new SideBySideDiffBuilder();
+ private delegate ChangeType PieceBuilder(string oldText, string newText, List oldPieces, List newPieces, bool ignoreWhitespace, bool ignoreCase);
- public SideBySideDiffBuilder(IDiffer differ, IChunker lineChunker, IChunker wordChunker)
- {
- this.differ = differ ?? Differ.Instance;
- this.lineChunker = lineChunker ?? throw new ArgumentNullException(nameof(lineChunker));
- this.wordChunker = wordChunker ?? throw new ArgumentNullException(nameof(wordChunker));
- }
+ ///
+ /// Gets the default singleton instance.
+ ///
+ public static SideBySideDiffBuilder Instance { get; } = new SideBySideDiffBuilder();
- public SideBySideDiffBuilder(IDiffer differ = null) :
- this(differ, new LineChunker(), new WordChunker())
- {
- }
+ public SideBySideDiffBuilder(IDiffer differ, IChunker lineChunker, IChunker wordChunker)
+ {
+ this.differ = differ ?? Differ.Instance;
+ this.lineChunker = lineChunker ?? throw new ArgumentNullException(nameof(lineChunker));
+ this.wordChunker = wordChunker ?? throw new ArgumentNullException(nameof(wordChunker));
+ }
- public SideBySideDiffBuilder(IDiffer differ, char[] wordSeparators)
- : this(differ, new LineChunker(), new DelimiterChunker(wordSeparators))
- {
- }
+ public SideBySideDiffBuilder(IDiffer differ = null) :
+ this(differ, new LineChunker(), new WordChunker())
+ {
+ }
- public SideBySideDiffModel BuildDiffModel(string oldText, string newText)
- => BuildDiffModel(oldText, newText, ignoreWhitespace: true);
+ public SideBySideDiffBuilder(IDiffer differ, char[] wordSeparators)
+ : this(differ, new LineChunker(), new DelimiterChunker(wordSeparators))
+ {
+ }
- public SideBySideDiffModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace) => BuildDiffModel(
- oldText,
- newText,
- ignoreWhitespace,
- false);
+ public SideBySideDiffModel BuildDiffModel(string oldText, string newText)
+ => BuildDiffModel(oldText, newText, ignoreWhitespace: true);
- public SideBySideDiffModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace, bool ignoreCase)
- {
- return BuildLineDiff(
- oldText ?? throw new ArgumentNullException(nameof(oldText)),
- newText ?? throw new ArgumentNullException(nameof(newText)),
- ignoreWhitespace,
- ignoreCase);
- }
+ public SideBySideDiffModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace) => BuildDiffModel(
+ oldText,
+ newText,
+ ignoreWhitespace,
+ false);
- ///
- /// Gets the side-by-side textual diffs.
- ///
- /// The old text to diff.
- /// The new text.
- /// if ignore the white space; otherwise, .
- /// if case-insensitive; otherwise, .
- /// The diffs result.
- public static SideBySideDiffModel Diff(string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false)
- {
- if (oldText == null) throw new ArgumentNullException(nameof(oldText));
- if (newText == null) throw new ArgumentNullException(nameof(newText));
+ public SideBySideDiffModel BuildDiffModel(string oldText, string newText, bool ignoreWhitespace, bool ignoreCase)
+ {
+ return BuildLineDiff(
+ oldText ?? throw new ArgumentNullException(nameof(oldText)),
+ newText ?? throw new ArgumentNullException(nameof(newText)),
+ ignoreWhitespace,
+ ignoreCase);
+ }
- var model = new SideBySideDiffModel();
- var diffResult = Differ.Instance.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, LineChunker.Instance);
- BuildDiffPieces(diffResult, model.OldText.Lines, model.NewText.Lines, BuildWordDiffPiecesInternal, ignoreWhiteSpace, ignoreCase);
+ ///
+ /// Gets the side-by-side textual diffs.
+ ///
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The diffs result.
+ public static SideBySideDiffModel Diff(string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false)
+ {
+ if (oldText == null) throw new ArgumentNullException(nameof(oldText));
+ if (newText == null) throw new ArgumentNullException(nameof(newText));
+ var diffResult = Differ.Instance.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, LineChunker.Instance);
+ return BuildDiffModel(diffResult, BuildWordDiffPiecesInternal, ignoreWhiteSpace, ignoreCase);
+ }
- return model;
- }
+#if !NET_TOO_OLD_VER
+ ///
+ /// Gets the side-by-side textual diffs.
+ ///
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The diffs result.
+ public static SideBySideDiffModel Diff(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace = true, bool ignoreCase = false)
+ {
+ if (oldText == null) throw new ArgumentNullException(nameof(oldText));
+ if (newText == null) throw new ArgumentNullException(nameof(newText));
+ var diffResult = Differ.Instance.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, LineChunker.Instance);
+ return BuildDiffModel(diffResult, BuildWordDiffPiecesInternal, ignoreWhiteSpace, ignoreCase);
+ }
- ///
- /// Gets the side-by-side textual diffs.
- ///
- /// The differ instance.
- /// The old text to diff.
- /// The new text.
- /// if ignore the white space; otherwise, .
- /// if case-insensitive; otherwise, .
- /// The line chunker.
- /// The word chunker.
- /// The diffs result.
- public static SideBySideDiffModel Diff(IDiffer differ, string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker lineChunker = null, IChunker wordChunker = null)
+ ///
+ /// Gets the side-by-side textual diffs.
+ ///
+ /// The differ instance.
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The line chunker.
+ /// The word chunker.
+ /// The diffs result.
+ public static SideBySideDiffModel Diff(IDiffer differ, ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker lineChunker = null, IChunker wordChunker = null)
+ {
+ if (differ == null) return Diff(oldText, newText, ignoreWhiteSpace, ignoreCase);
+ var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, lineChunker ?? LineChunker.Instance);
+ return BuildDiffModel(diffResult, (ot, nt, op, np, iw, ic) =>
{
- if (oldText == null) throw new ArgumentNullException(nameof(oldText));
- if (newText == null) throw new ArgumentNullException(nameof(newText));
-
- if (differ == null) return Diff(oldText, newText, ignoreWhiteSpace, ignoreCase);
-
- var model = new SideBySideDiffModel();
- var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, lineChunker ?? LineChunker.Instance);
- BuildDiffPieces(diffResult, model.OldText.Lines, model.NewText.Lines, (ot, nt, op, np, iw, ic) =>
- {
- var r = differ.CreateDiffs(ot, nt, iw, ic, wordChunker ?? WordChunker.Instance);
- return BuildDiffPieces(r, op, np, null, iw, ic);
- }, ignoreWhiteSpace, ignoreCase);
+ var r = differ.CreateDiffs(ot, nt, iw, ic, wordChunker ?? WordChunker.Instance);
+ return BuildDiffPieces(r, op, np, null, iw, ic);
+ }, ignoreWhiteSpace, ignoreCase);
+ }
+#endif
+
+ ///
+ /// Gets the side-by-side textual diffs.
+ ///
+ /// The differ instance.
+ /// The old text to diff.
+ /// The new text.
+ /// if ignore the white space; otherwise, .
+ /// if case-insensitive; otherwise, .
+ /// The line chunker.
+ /// The word chunker.
+ /// The diffs result.
+ public static SideBySideDiffModel Diff(IDiffer differ, string oldText, string newText, bool ignoreWhiteSpace = true, bool ignoreCase = false, IChunker lineChunker = null, IChunker wordChunker = null)
+ {
+ if (oldText == null) throw new ArgumentNullException(nameof(oldText));
+ if (newText == null) throw new ArgumentNullException(nameof(newText));
+ if (differ == null) return Diff(oldText, newText, ignoreWhiteSpace, ignoreCase);
+ var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, lineChunker ?? LineChunker.Instance);
+ return BuildDiffModel(diffResult, (ot, nt, op, np, iw, ic) =>
+ {
+ var r = differ.CreateDiffs(ot, nt, iw, ic, wordChunker ?? WordChunker.Instance);
+ return BuildDiffPieces(r, op, np, null, iw, ic);
+ }, ignoreWhiteSpace, ignoreCase);
+ }
- return model;
- }
+ private static ChangeType BuildWordDiffPiecesInternal(string oldText, string newText, List oldPieces, List newPieces, bool ignoreWhiteSpace, bool ignoreCase)
+ {
+ var diffResult = Differ.Instance.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, WordChunker.Instance);
+ return BuildDiffPieces(diffResult, oldPieces, newPieces, null, ignoreWhiteSpace, ignoreCase);
+ }
- private static ChangeType BuildWordDiffPiecesInternal(string oldText, string newText, List oldPieces, List newPieces, bool ignoreWhiteSpace, bool ignoreCase)
- {
- var diffResult = Differ.Instance.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, WordChunker.Instance);
- return BuildDiffPieces(diffResult, oldPieces, newPieces, null, ignoreWhiteSpace, ignoreCase);
- }
+ private SideBySideDiffModel BuildLineDiff(string oldText, string newText, bool ignoreWhiteSpace, bool ignoreCase)
+ {
+ var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, lineChunker);
+ return BuildDiffModel(diffResult, BuildWordDiffPieces, ignoreWhiteSpace, ignoreCase);
+ }
- private SideBySideDiffModel BuildLineDiff(string oldText, string newText, bool ignoreWhiteSpace, bool ignoreCase)
- {
- var model = new SideBySideDiffModel();
- var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, lineChunker);
- BuildDiffPieces(diffResult, model.OldText.Lines, model.NewText.Lines, BuildWordDiffPieces, ignoreWhiteSpace, ignoreCase);
+ private ChangeType BuildWordDiffPieces(string oldText, string newText, List oldPieces, List newPieces, bool ignoreWhiteSpace, bool ignoreCase)
+ {
+ var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace: ignoreWhiteSpace, ignoreCase, wordChunker);
+ return BuildDiffPieces(diffResult, oldPieces, newPieces, subPieceBuilder: null, ignoreWhiteSpace, ignoreCase);
+ }
- return model;
- }
+ private static SideBySideDiffModel BuildDiffModel(DiffResult diffResult, PieceBuilder subPieceBuilder, bool ignoreWhiteSpace, bool ignoreCase)
+ {
+ var oldLines = new List();
+ var newLines = new List();
+ BuildDiffPieces(diffResult, oldLines, newLines, subPieceBuilder, ignoreWhiteSpace, ignoreCase);
+ return new(oldLines, newLines);
+ }
- private ChangeType BuildWordDiffPieces(string oldText, string newText, List oldPieces, List newPieces, bool ignoreWhiteSpace, bool ignoreCase)
- {
- var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhiteSpace: ignoreWhiteSpace, ignoreCase, wordChunker);
- return BuildDiffPieces(diffResult, oldPieces, newPieces, subPieceBuilder: null, ignoreWhiteSpace, ignoreCase);
- }
+ private static ChangeType BuildDiffPieces(DiffResult diffResult, List oldPieces, List newPieces, PieceBuilder subPieceBuilder, bool ignoreWhiteSpace, bool ignoreCase)
+ {
+ var aPos = 0;
+ var bPos = 0;
- private static ChangeType BuildDiffPieces(DiffResult diffResult, List oldPieces, List newPieces, PieceBuilder subPieceBuilder, bool ignoreWhiteSpace, bool ignoreCase)
+ foreach (var diffBlock in diffResult.DiffBlocks)
{
- int aPos = 0;
- int bPos = 0;
-
- foreach (var diffBlock in diffResult.DiffBlocks)
+ while (bPos < diffBlock.InsertStartB && aPos < diffBlock.DeleteStartA)
{
- 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));
- aPos++;
- bPos++;
- }
-
- int i = 0;
- for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
- {
- var oldPiece = new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1);
- var newPiece = new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1);
-
- if (subPieceBuilder != null)
- {
- var subChangeSummary = subPieceBuilder(diffResult.PiecesOld[aPos], diffResult.PiecesNew[bPos], oldPiece.SubPieces, newPiece.SubPieces, ignoreWhiteSpace, ignoreCase);
- newPiece.Type = oldPiece.Type = subChangeSummary;
- }
-
- oldPieces.Add(oldPiece);
- newPieces.Add(newPiece);
- aPos++;
- bPos++;
- }
+ oldPieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1));
+ newPieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
+ aPos++;
+ bPos++;
+ }
- if (diffBlock.DeleteCountA > diffBlock.InsertCountB)
- {
- for (; i < diffBlock.DeleteCountA; i++)
- {
- oldPieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1));
- newPieces.Add(new DiffPiece());
- aPos++;
- }
- }
- else
+ int i = 0;
+ for (; i < Math.Min(diffBlock.DeleteCountA, diffBlock.InsertCountB); i++)
+ {
+ var oldPieceType = ChangeType.Deleted;
+ var newPieceType = ChangeType.Inserted;
+ var oldSubPieces = new List();
+ var newSubPieces = new List();
+ if (subPieceBuilder != null)
{
- for (; i < diffBlock.InsertCountB; i++)
- {
- newPieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
- oldPieces.Add(new DiffPiece());
- bPos++;
- }
+ var subChangeSummary = subPieceBuilder(diffResult.PiecesOld[aPos], diffResult.PiecesNew[bPos], oldSubPieces, newSubPieces, ignoreWhiteSpace, ignoreCase);
+ oldPieceType = newPieceType = subChangeSummary;
}
- }
- while (bPos < diffResult.PiecesNew.Count && aPos < diffResult.PiecesOld.Count)
- {
- oldPieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1));
- newPieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
+ oldPieces.Add(new(diffResult.PiecesOld[i + diffBlock.DeleteStartA], oldPieceType, aPos + 1, oldSubPieces));
+ newPieces.Add(new(diffResult.PiecesNew[i + diffBlock.InsertStartB], newPieceType, bPos + 1, newSubPieces));
aPos++;
bPos++;
}
- // 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))
+ if (diffBlock.DeleteCountA > diffBlock.InsertCountB)
{
- return ChangeType.Modified;
+ for (; i < diffBlock.DeleteCountA; i++)
+ {
+ oldPieces.Add(new DiffPiece(diffResult.PiecesOld[i + diffBlock.DeleteStartA], ChangeType.Deleted, aPos + 1));
+ newPieces.Add(new DiffPiece());
+ aPos++;
+ }
}
-
- if (newPieces.Any(x => x.Type is ChangeType.Modified or ChangeType.Inserted or ChangeType.Deleted))
+ else
{
- return ChangeType.Modified;
+ for (; i < diffBlock.InsertCountB; i++)
+ {
+ newPieces.Add(new DiffPiece(diffResult.PiecesNew[i + diffBlock.InsertStartB], ChangeType.Inserted, bPos + 1));
+ oldPieces.Add(new DiffPiece());
+ bPos++;
+ }
}
+ }
- return ChangeType.Unchanged;
+ while (bPos < diffResult.PiecesNew.Count && aPos < diffResult.PiecesOld.Count)
+ {
+ oldPieces.Add(new DiffPiece(diffResult.PiecesOld[aPos], ChangeType.Unchanged, aPos + 1));
+ newPieces.Add(new DiffPiece(diffResult.PiecesNew[bPos], ChangeType.Unchanged, bPos + 1));
+ aPos++;
+ bPos++;
}
+
+ // 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))
+ {
+ return ChangeType.Modified;
+ }
+
+ if (newPieces.Any(x => x.Type is ChangeType.Modified or ChangeType.Inserted or ChangeType.Deleted))
+ {
+ return ChangeType.Modified;
+ }
+
+ return ChangeType.Unchanged;
}
}
\ No newline at end of file
diff --git a/DiffPlex/DiffPlex.csproj b/DiffPlex/DiffPlex.csproj
index 2c5bd5b0..03444ad7 100644
--- a/DiffPlex/DiffPlex.csproj
+++ b/DiffPlex/DiffPlex.csproj
@@ -1,14 +1,18 @@
- net45;netstandard1.0;netstandard2.0;net6.0
- 1.7.2
+ net45;net462;net48;netstandard1.0;netstandard2.0;net6.0;net8.0
+ 2.0.0
diff
DiffPlex is a diffing library that allows you to programmatically create text diffs. DiffPlex is a fast and tested library.
- Fixed diffing of sub-components (like words). Ensures ignoreWhitespace and ignoreCase are honored in that case and that the parent reflects modification state of the child.
+ Modernization with ReadOnlySpan<char> and JSON supports.
README.md
+
+
+
+
-
+
diff --git a/DiffPlex/Differ.cs b/DiffPlex/Differ.cs
index 281ee96c..e4d1e734 100644
--- a/DiffPlex/Differ.cs
+++ b/DiffPlex/Differ.cs
@@ -57,23 +57,84 @@ public DiffResult CreateDiffs(string oldText, string newText, bool ignoreWhiteSp
if (oldText == null) throw new ArgumentNullException(nameof(oldText));
if (newText == null) throw new ArgumentNullException(nameof(newText));
if (chunker == null) throw new ArgumentNullException(nameof(chunker));
-
- var pieceHash = new Dictionary(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
- var lineDiffs = new List();
-
var modOld = new ModificationData(oldText);
var modNew = new ModificationData(newText);
+ return CreateDiffs(modOld, modNew, ignoreWhiteSpace, ignoreCase, chunker);
+ }
+
+#if !NET_TOO_OLD_VER
+ public DiffResult CreateLineDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhitespace)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhitespace, false, new LineChunker());
+ }
+
+ public DiffResult CreateLineDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhitespace, bool ignoreCase)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhitespace, ignoreCase, new LineChunker());
+ }
+
+ public DiffResult CreateCharacterDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhitespace)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhitespace, false, new CharacterChunker());
+ }
+
+ public DiffResult CreateCharacterDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhitespace, bool ignoreCase)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhitespace, ignoreCase, new CharacterChunker());
+ }
+
+ public DiffResult CreateWordDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhitespace, char[] separators)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhitespace, false, new DelimiterChunker(separators));
+ }
+
+ public DiffResult CreateWordDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhitespace, bool ignoreCase, char[] separators)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhitespace, ignoreCase, new DelimiterChunker(separators));
+ }
+
+ public DiffResult CreateCustomDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace, Func chunker)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhiteSpace, false, new CustomFunctionChunker(chunker));
+ }
+
+ public DiffResult CreateCustomDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace, bool ignoreCase, Func chunker)
+ {
+ return CreateDiffs(oldText, newText, ignoreWhiteSpace, ignoreCase, new CustomFunctionChunker(chunker));
+ }
+
+ public DiffResult CreateDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace, bool ignoreCase, IChunker chunker)
+ {
+ if (chunker == null) throw new ArgumentNullException(nameof(chunker));
+ var modOld = new ModificationDataInfo();
+ var modNew = new ModificationDataInfo();
+ return CreateDiffs(modOld, oldText, modNew, newText, ignoreWhiteSpace, ignoreCase, chunker);
+ }
+ private DiffResult CreateDiffs(ModificationDataInfo modOld, ReadOnlySpan oldText, ModificationDataInfo modNew, ReadOnlySpan newText, bool ignoreWhiteSpace, bool ignoreCase, IChunker chunker)
+ {
+ var pieceHash = new Dictionary(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
+ BuildPieceHashes(pieceHash, modOld, oldText, ignoreWhiteSpace, chunker);
+ BuildPieceHashes(pieceHash, modNew, newText, ignoreWhiteSpace, chunker);
+ return CreateDiffsByPieces(modOld, modNew, ignoreWhiteSpace, ignoreCase, chunker);
+ }
+#endif
+ private DiffResult CreateDiffs(ModificationData modOld, ModificationData modNew, bool ignoreWhiteSpace, bool ignoreCase, IChunker chunker)
+ {
+ var pieceHash = new Dictionary(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
BuildPieceHashes(pieceHash, modOld, ignoreWhiteSpace, chunker);
BuildPieceHashes(pieceHash, modNew, ignoreWhiteSpace, chunker);
+ return CreateDiffsByPieces(modOld, modNew, ignoreWhiteSpace, ignoreCase, chunker);
+ }
+ private DiffResult CreateDiffsByPieces(ModificationDataInfo modOld, ModificationDataInfo modNew, bool ignoreWhiteSpace, bool ignoreCase, IChunker chunker)
+ {
BuildModificationData(modOld, modNew);
-
- int piecesALength = modOld.HashedPieces.Length;
- int piecesBLength = modNew.HashedPieces.Length;
- int posA = 0;
- int posB = 0;
-
+ var lineDiffs = new List();
+ var piecesALength = modOld.HashedPieces.Length;
+ var piecesBLength = modNew.HashedPieces.Length;
+ var posA = 0;
+ var posB = 0;
do
{
while (posA < piecesALength
@@ -260,7 +321,7 @@ private static EditLengthResult CalculateEditLength(int[] A, int startA, int end
throw new Exception("Should never get here");
}
- protected static void BuildModificationData(ModificationData A, ModificationData B)
+ protected static void BuildModificationData(ModificationDataInfo A, ModificationDataInfo B)
{
int N = A.HashedPieces.Length;
int M = B.HashedPieces.Length;
@@ -271,10 +332,10 @@ protected static void BuildModificationData(ModificationData A, ModificationData
}
private static void BuildModificationData
- (ModificationData A,
+ (ModificationDataInfo A,
int startA,
int endA,
- ModificationData B,
+ ModificationDataInfo B,
int startB,
int endB,
int[] forwardDiagonal,
@@ -334,6 +395,27 @@ private static void BuildPieceHashes(IDictionary pieceHash, Modific
}
var pieces = chunker.Chunk(data.RawData);
+ BuildPieceHashes(pieces, pieceHash, data, ignoreWhitespace, chunker);
+ }
+
+#if !NET_TOO_OLD_VER
+ private static void BuildPieceHashes(IDictionary pieceHash, ModificationDataInfo data, ReadOnlySpan text, bool ignoreWhitespace, IChunker chunker)
+ {
+ if (text.Length == 0)
+ {
+ data.Pieces = [];
+ data.HashedPieces = [];
+ data.Modifications = [];
+ return;
+ }
+
+ var pieces = chunker is ISpanChunker spanChunker ? spanChunker.Chunk(text) : chunker.Chunk(text.ToString());
+ BuildPieceHashes(pieces, pieceHash, data, ignoreWhitespace, chunker);
+ }
+#endif
+
+ private static void BuildPieceHashes(IReadOnlyList pieces, IDictionary pieceHash, ModificationDataInfo data, bool ignoreWhitespace, IChunker chunker)
+ {
data.Pieces = pieces;
data.HashedPieces = new int[pieces.Count];
data.Modifications = new bool[pieces.Count];
diff --git a/DiffPlex/IChunker.cs b/DiffPlex/IChunker.cs
index 9e9de1a2..3a5de6de 100644
--- a/DiffPlex/IChunker.cs
+++ b/DiffPlex/IChunker.cs
@@ -1,15 +1,28 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
-namespace DiffPlex
+namespace DiffPlex;
+
+///
+/// Responsible for how to turn the document into pieces
+///
+public interface IChunker
+{
+ ///
+ /// Divide text into sub-parts
+ ///
+ IReadOnlyList Chunk(string text);
+}
+
+#if !NET_TOO_OLD_VER
+///
+/// Responsible for how to turn the document into pieces
+///
+public interface ISpanChunker : IChunker
{
///
- /// Responsible for how to turn the document into pieces
+ /// Divide text into sub-parts
///
- public interface IChunker
- {
- ///
- /// Divide text into sub-parts
- ///
- IReadOnlyList Chunk(string text);
- }
-}
\ No newline at end of file
+ IReadOnlyList Chunk(ReadOnlySpan text);
+}
+#endif
\ No newline at end of file
diff --git a/DiffPlex/IDiffer.cs b/DiffPlex/IDiffer.cs
index c860e04f..a1be97a2 100644
--- a/DiffPlex/IDiffer.cs
+++ b/DiffPlex/IDiffer.cs
@@ -42,5 +42,18 @@ public interface IDiffer
/// Component responsible for tokenizing the compared texts
/// A object which details the differences
DiffResult CreateDiffs(string oldText, string newText, bool ignoreWhiteSpace, bool ignoreCase, IChunker chunker);
+
+#if !NET_TOO_OLD_VER
+ ///
+ /// Creates a diff by comparing text line by line.
+ ///
+ /// The old text.
+ /// The new text.
+ /// If set to will ignore white space when determining if lines are the same.
+ /// Determine if the text comparision is case sensitive or not
+ /// Component responsible for tokenizing the compared texts
+ /// A object which details the differences
+ DiffResult CreateDiffs(ReadOnlySpan oldText, ReadOnlySpan newText, bool ignoreWhiteSpace, bool ignoreCase, IChunker chunker);
+#endif
}
}
\ No newline at end of file
diff --git a/DiffPlex/Model/DiffBlock.cs b/DiffPlex/Model/DiffBlock.cs
index 7fa5fe8a..7a8430ca 100644
--- a/DiffPlex/Model/DiffBlock.cs
+++ b/DiffPlex/Model/DiffBlock.cs
@@ -1,37 +1,36 @@
-namespace DiffPlex.Model
+namespace DiffPlex.Model;
+
+///
+/// A block of consecutive edits from A and/or B
+///
+public class DiffBlock
{
///
- /// A block of consecutive edits from A and/or B
+ /// Position where deletions in A begin
///
- public class DiffBlock
- {
- ///
- /// Position where deletions in A begin
- ///
- public int DeleteStartA { get; }
+ public int DeleteStartA { get; }
- ///
- /// The number of deletions in A
- ///
- public int DeleteCountA { get; }
+ ///
+ /// The number of deletions in A
+ ///
+ public int DeleteCountA { get; }
- ///
- /// Position where insertion in B begin
- ///
- public int InsertStartB { get; }
+ ///
+ /// Position where insertion in B begin
+ ///
+ public int InsertStartB { get; }
- ///
- /// The number of insertions in B
- ///
- public int InsertCountB { get; }
+ ///
+ /// The number of insertions in B
+ ///
+ public int InsertCountB { get; }
- public DiffBlock(int deleteStartA, int deleteCountA, int insertStartB, int insertCountB)
- {
- DeleteStartA = deleteStartA;
- DeleteCountA = deleteCountA;
- InsertStartB = insertStartB;
- InsertCountB = insertCountB;
- }
+ public DiffBlock(int deleteStartA, int deleteCountA, int insertStartB, int insertCountB)
+ {
+ DeleteStartA = deleteStartA;
+ DeleteCountA = deleteCountA;
+ InsertStartB = insertStartB;
+ InsertCountB = insertCountB;
}
}
\ No newline at end of file
diff --git a/DiffPlex/Model/DiffResult.cs b/DiffPlex/Model/DiffResult.cs
index 8055eb1e..5de21c6c 100644
--- a/DiffPlex/Model/DiffResult.cs
+++ b/DiffPlex/Model/DiffResult.cs
@@ -1,33 +1,31 @@
using System.Collections.Generic;
-namespace DiffPlex.Model
+namespace DiffPlex.Model;
+
+///
+/// The result of diffing two pieces of text
+///
+public class DiffResult
{
///
- /// The result of diffing two pieces of text
+ /// The chunked pieces of the old text
///
- public class DiffResult
- {
- ///
- /// The chunked pieces of the old text
- ///
- public IReadOnlyList PiecesOld { get; }
-
- ///
- /// The chunked pieces of the new text
- ///
- public IReadOnlyList PiecesNew { get; }
+ public IReadOnlyList PiecesOld { get; }
+ ///
+ /// The chunked pieces of the new text
+ ///
+ public IReadOnlyList PiecesNew { get; }
- ///
- /// A collection of DiffBlocks which details deletions and insertions
- ///
- public IList DiffBlocks { get; }
+ ///
+ /// A collection of DiffBlocks which details deletions and insertions
+ ///
+ public IList DiffBlocks { get; }
- public DiffResult(IReadOnlyList piecesOld, IReadOnlyList piecesNew, IList blocks)
- {
- PiecesOld = piecesOld;
- PiecesNew = piecesNew;
- DiffBlocks = blocks;
- }
+ public DiffResult(IReadOnlyList piecesOld, IReadOnlyList piecesNew, IList blocks)
+ {
+ PiecesOld = piecesOld;
+ PiecesNew = piecesNew;
+ DiffBlocks = blocks;
}
}
\ No newline at end of file
diff --git a/DiffPlex/Model/EditLengthResult.cs b/DiffPlex/Model/EditLengthResult.cs
index 859014fa..17b362e6 100644
--- a/DiffPlex/Model/EditLengthResult.cs
+++ b/DiffPlex/Model/EditLengthResult.cs
@@ -1,23 +1,22 @@
-namespace DiffPlex.Model
+namespace DiffPlex.Model;
+
+public enum Edit
{
- public enum Edit
- {
- None,
- DeleteRight,
- DeleteLeft,
- InsertDown,
- InsertUp
- }
+ None,
+ DeleteRight,
+ DeleteLeft,
+ InsertDown,
+ InsertUp
+}
- public class EditLengthResult
- {
- public int EditLength { get; set; }
+public class EditLengthResult
+{
+ public int EditLength { get; set; }
- public int StartX { get; set; }
- public int EndX { get; set; }
- public int StartY { get; set; }
- public int EndY { get; set; }
+ public int StartX { get; set; }
+ public int EndX { get; set; }
+ public int StartY { get; set; }
+ public int EndY { get; set; }
- public Edit LastEdit { get; set; }
- }
+ public Edit LastEdit { get; set; }
}
\ No newline at end of file
diff --git a/DiffPlex/Model/ModificationData.cs b/DiffPlex/Model/ModificationData.cs
index 9a6e0774..7a8296e5 100644
--- a/DiffPlex/Model/ModificationData.cs
+++ b/DiffPlex/Model/ModificationData.cs
@@ -1,20 +1,23 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
-namespace DiffPlex.Model
+namespace DiffPlex.Model;
+
+public class ModificationData : ModificationDataInfo
{
- public class ModificationData
+ public ModificationData(string str)
{
- public int[] HashedPieces { get; set; }
+ RawData = str;
+ }
- public string RawData { get; }
+ public string RawData { get; }
+}
- public bool[] Modifications { get; set; }
+public class ModificationDataInfo
+{
+ public int[] HashedPieces { get; set; }
- public IReadOnlyList Pieces { get; set; }
+ public bool[] Modifications { get; set; }
- public ModificationData(string str)
- {
- RawData = str;
- }
- }
+ public IReadOnlyList Pieces { get; set; }
}
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 00000000..050db610
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,22 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Facts.DiffPlex/DiffBuilder/JsonDiffModelFacts.cs b/Facts.DiffPlex/DiffBuilder/JsonDiffModelFacts.cs
new file mode 100644
index 00000000..e7034c0a
--- /dev/null
+++ b/Facts.DiffPlex/DiffBuilder/JsonDiffModelFacts.cs
@@ -0,0 +1,50 @@
+using DiffPlex.DiffBuilder.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Facts.DiffPlex.DiffBuilder;
+
+///
+/// The JSON supports test suite of diff models.
+///
+public class JsonDiffModelFacts
+{
+ ///
+ /// Tests for the JSON serialization supports of diff models.
+ ///
+ [Fact]
+ public void TestSerialization()
+ {
+ var t = "The diff model is invalid but does not matter since it is only used for testing.";
+ var model = new SideBySideDiffModel(new List
+ {
+ new(),
+ new(t, ChangeType.Inserted, 20),
+ new("Another test text here…", ChangeType.Deleted, 10),
+ }, null);
+ var s = JsonSerializer.Serialize(model);
+ model = JsonSerializer.Deserialize(s);
+ Assert.NotNull(model);
+ Assert.Equal(3, model.OldText.Count);
+ Assert.Null(model.OldText.Lines[0].Text);
+ Assert.Equal(t, model.OldText.Lines[1].Text);
+ Assert.Equal(ChangeType.Inserted, model.OldText.Lines[1].Type);
+ Assert.Equal(ChangeType.Deleted, model.OldText.Lines[2].Type);
+ Assert.Equal(0, model.NewText.Count);
+ s = JsonSerializer.Serialize(model.OldText);
+ model = new(JsonSerializer.Deserialize(s), null);
+ Assert.Equal(3, model.OldText.Count);
+ Assert.Null(model.OldText.Lines[0].Text);
+ Assert.Equal(t, model.OldText.Lines[1].Text);
+ Assert.Equal(ChangeType.Inserted, model.OldText.Lines[1].Type);
+ Assert.Equal(ChangeType.Deleted, model.OldText.Lines[2].Type);
+ Assert.Equal(0, model.NewText.Count);
+ s = model.OldText.Join();
+ Assert.NotNull(s);
+ }
+}
diff --git a/Facts.DiffPlex/Facts.DiffPlex.csproj b/Facts.DiffPlex/Facts.DiffPlex.csproj
index da7a8a3b..2263c01e 100644
--- a/Facts.DiffPlex/Facts.DiffPlex.csproj
+++ b/Facts.DiffPlex/Facts.DiffPlex.csproj
@@ -5,10 +5,10 @@
-
-
-
-
+
+
+
+
diff --git a/Facts.DiffPlex/SideBySideDiffBuilderFacts.cs b/Facts.DiffPlex/SideBySideDiffBuilderFacts.cs
index b40b2765..090f717f 100644
--- a/Facts.DiffPlex/SideBySideDiffBuilderFacts.cs
+++ b/Facts.DiffPlex/SideBySideDiffBuilderFacts.cs
@@ -432,31 +432,22 @@ public void Can_compare_whitespace()
new DiffPiece[]
{
new DiffPiece("1", ChangeType.Unchanged, 1),
- new DiffPiece(" 2", ChangeType.Modified, 2)
+ new DiffPiece(" 2", ChangeType.Modified, 2, new List
{
- SubPieces =
- {
- new DiffPiece(" ", ChangeType.Deleted, 1),
- new DiffPiece("2", ChangeType.Unchanged, 2),
- },
- },
- new DiffPiece("3 ", ChangeType.Modified, 3)
+ new DiffPiece(" ", ChangeType.Deleted, 1),
+ new DiffPiece("2", ChangeType.Unchanged, 2),
+ }),
+ new DiffPiece("3 ", ChangeType.Modified, 3, new List
{
- SubPieces =
- {
- new DiffPiece("3", ChangeType.Unchanged, 1),
- new DiffPiece(" ", ChangeType.Deleted, 2),
- },
- },
- new DiffPiece(" 4 ", ChangeType.Modified, 4)
+ new DiffPiece("3", ChangeType.Unchanged, 1),
+ new DiffPiece(" ", ChangeType.Deleted, 2),
+ }),
+ new DiffPiece(" 4 ", ChangeType.Modified, 4, new List
{
- SubPieces =
- {
- new DiffPiece(" ", ChangeType.Deleted, 1),
- new DiffPiece("4", ChangeType.Unchanged, 2),
- new DiffPiece(" ", ChangeType.Deleted, 3),
- },
- },
+ new DiffPiece(" ", ChangeType.Deleted, 1),
+ new DiffPiece("4", ChangeType.Unchanged, 2),
+ new DiffPiece(" ", ChangeType.Deleted, 3),
+ }),
new DiffPiece("5", ChangeType.Unchanged, 5),
});
Assert.Equal(
@@ -464,31 +455,22 @@ public void Can_compare_whitespace()
new DiffPiece[]
{
new DiffPiece("1", ChangeType.Unchanged, 1),
- new DiffPiece("2", ChangeType.Modified, 2)
+ new DiffPiece("2", ChangeType.Modified, 2, new List
{
- SubPieces =
- {
- new DiffPiece(null, ChangeType.Imaginary),
- new DiffPiece("2", ChangeType.Unchanged, 1),
- },
- },
- new DiffPiece("3", ChangeType.Modified, 3)
+ new DiffPiece(null, ChangeType.Imaginary),
+ new DiffPiece("2", ChangeType.Unchanged, 1),
+ }),
+ new DiffPiece("3", ChangeType.Modified, 3, new List
{
- SubPieces =
- {
- new DiffPiece("3", ChangeType.Unchanged, 1),
- new DiffPiece(null, ChangeType.Imaginary),
- },
- },
- new DiffPiece("4", ChangeType.Modified, 4)
+ new DiffPiece("3", ChangeType.Unchanged, 1),
+ new DiffPiece(null, ChangeType.Imaginary),
+ }),
+ new DiffPiece("4", ChangeType.Modified, 4, new List
{
- SubPieces =
- {
- new DiffPiece(null, ChangeType.Imaginary),
- new DiffPiece("4", ChangeType.Unchanged, 1),
- new DiffPiece(null, ChangeType.Imaginary),
- },
- },
+ new DiffPiece(null, ChangeType.Imaginary),
+ new DiffPiece("4", ChangeType.Unchanged, 1),
+ new DiffPiece(null, ChangeType.Imaginary),
+ }),
new DiffPiece("5", ChangeType.Unchanged, 5),
});
}
diff --git a/NuGet.props b/NuGet.props
index 17d49f86..91537e7f 100644
--- a/NuGet.props
+++ b/NuGet.props
@@ -17,8 +17,11 @@
$(NoWarn);1591
true
+
+ NET_TOO_OLD_VER
+
-
+
diff --git a/Perf.DiffPlex/Perf.DiffPlex.csproj b/Perf.DiffPlex/Perf.DiffPlex.csproj
index 0c38c522..d306a2e6 100644
--- a/Perf.DiffPlex/Perf.DiffPlex.csproj
+++ b/Perf.DiffPlex/Perf.DiffPlex.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/WebDiffer/WebDiffer.csproj b/WebDiffer/WebDiffer.csproj
index 6f465bbc..34e45db1 100644
--- a/WebDiffer/WebDiffer.csproj
+++ b/WebDiffer/WebDiffer.csproj
@@ -1,13 +1,13 @@
- net6.0
+ net8.0
true
-
-
+
+