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 - - + +