diff --git a/Debuggee/Debuggee.csproj b/Debuggee/Debuggee.csproj new file mode 100644 index 0000000..887c330 --- /dev/null +++ b/Debuggee/Debuggee.csproj @@ -0,0 +1,37 @@ + + + + netstandard2.0;net472;netcoreapp2.0 + ExpressionTreeVisualizer.Serialization + ExpressionTreeVisualizer.Debuggee + 8.0 + enable + true + + + + false + false + bin/$(Configuration)/net2.0/ + + + + false + false + bin/$(Configuration)/netcoreapp/ + + + + + + + + + ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll + false + + + + + + diff --git a/Visualizer/VisualizerDataObjectSource.cs b/Debuggee/VisualizerDataObjectSource.cs similarity index 91% rename from Visualizer/VisualizerDataObjectSource.cs rename to Debuggee/VisualizerDataObjectSource.cs index dfdd224..ec63707 100644 --- a/Visualizer/VisualizerDataObjectSource.cs +++ b/Debuggee/VisualizerDataObjectSource.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.DebuggerVisualizers; using System.IO; +using ExpressionTreeVisualizer.Serialization; namespace ExpressionTreeVisualizer { public class VisualizerDataObjectSource : VisualizerObjectSource { diff --git a/ExpressionTreeVisualizer.sln b/ExpressionTreeVisualizer.sln index 1ae6f97..bbfed8d 100644 --- a/ExpressionTreeVisualizer.sln +++ b/ExpressionTreeVisualizer.sln @@ -7,17 +7,27 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_visualizerTests", "_visual EndProject Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "_visualizerTests.VB", "_visualizerTests.VB\_visualizerTests.VB.vbproj", "{16EF3252-7F7C-4E3F-A3B0-275ACACFBA3A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Visualizer", "Tests.Visualizer\Tests.Visualizer.csproj", "{A6DB4A34-28E2-4012-AF70-9E24DAB9BCD6}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Visualizer.Shared", "Visualizer.Shared\Visualizer.Shared.shproj", "{16CA0653-E03A-47DF-A5BF-292EEF9AA5F0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests.Visualizer\Tests.csproj", "{A6DB4A34-28E2-4012-AF70-9E24DAB9BCD6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Visualizer", "Visualizer\Visualizer.csproj", "{83B4B968-2066-4463-8F62-A4DFA405A2DB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_visualizerTestNoRef", "_visualizerTestNoRef\_visualizerTestNoRef.csproj", "{5F11601F-1A4F-4C5B-99D5-5F5EC3F2EB1F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Debuggee", "Debuggee\Debuggee.csproj", "{8D8CCC9D-618B-45A1-B6EE-02F675B4DB70}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Serialization", "Serialization\Serialization.shproj", "{9365B962-F17C-451C-92EB-F4FED9C6C84E}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UI", "UI\UI.shproj", "{E1D67370-17AC-47FC-89BC-34B20B3BD6E7}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Visualizer.Xaml", "Visualizer.Xaml\Visualizer.Xaml.shproj", "{89DE9332-5AA0-4136-A785-ED712FDC39FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Package", "Package\Package.csproj", "{0AA18EF4-75D3-42D8-A084-75F42F4E836A}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution - Visualizer.Shared\Visualizer.Shared.projitems*{16ca0653-e03a-47df-a5bf-292eef9aa5f0}*SharedItemsImports = 13 + Visualizer.Xaml\Visualizer.Xaml.projitems*{89de9332-5aa0-4136-a785-ed712fdc39fc}*SharedItemsImports = 13 + Serialization\Serialization.projitems*{9365b962-f17c-451c-92eb-f4fed9c6c84e}*SharedItemsImports = 13 + UI\UI.projitems*{e1d67370-17ac-47fc-89bc-34b20b3bd6e7}*SharedItemsImports = 13 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +51,14 @@ Global {5F11601F-1A4F-4C5B-99D5-5F5EC3F2EB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F11601F-1A4F-4C5B-99D5-5F5EC3F2EB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F11601F-1A4F-4C5B-99D5-5F5EC3F2EB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {8D8CCC9D-618B-45A1-B6EE-02F675B4DB70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D8CCC9D-618B-45A1-B6EE-02F675B4DB70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D8CCC9D-618B-45A1-B6EE-02F675B4DB70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D8CCC9D-618B-45A1-B6EE-02F675B4DB70}.Release|Any CPU.Build.0 = Release|Any CPU + {0AA18EF4-75D3-42D8-A084-75F42F4E836A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AA18EF4-75D3-42D8-A084-75F42F4E836A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AA18EF4-75D3-42D8-A084-75F42F4E836A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AA18EF4-75D3-42D8-A084-75F42F4E836A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Package/Package.csproj b/Package/Package.csproj new file mode 100644 index 0000000..08c7c11 --- /dev/null +++ b/Package/Package.csproj @@ -0,0 +1,32 @@ + + + net472 + ExpressionTreeVisualizer + ExpressionTreeVisualizer.UI + 8.0 + enable + true + + + + + + + + ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll + false + + + + + + + + + + + + + + diff --git a/Serialization/EndNodeData.cs b/Serialization/EndNodeData.cs new file mode 100644 index 0000000..0419819 --- /dev/null +++ b/Serialization/EndNodeData.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ExpressionTreeVisualizer.Serialization { + [Serializable] + [SuppressMessage("", "IDE0032", Justification = "https://github.com/dotnet/core/issues/2981")] + public struct EndNodeData { + private string? _closure; + private string? _name; + private string? _type; + private string? _value; + + public string? Closure { + get => _closure; + set => _closure = value; + } + public string? Name { + get => _name; + set => _name = value; + } + public string? Type { + get => _type; + set => _type = value; + } + public string? Value { + get => _value; + set => _value = value; + } + } +} diff --git a/Serialization/EndNodeType.cs b/Serialization/EndNodeType.cs new file mode 100644 index 0000000..9d5ab83 --- /dev/null +++ b/Serialization/EndNodeType.cs @@ -0,0 +1,8 @@ +namespace ExpressionTreeVisualizer.Serialization { + public enum EndNodeTypes { + Constant, + Parameter, + ClosedVar, + Default + } +} diff --git a/Visualizer/VisualizerData.cs b/Serialization/ExpressionNodeData.cs similarity index 60% rename from Visualizer/VisualizerData.cs rename to Serialization/ExpressionNodeData.cs index 87e75b8..659065a 100644 --- a/Visualizer/VisualizerData.cs +++ b/Serialization/ExpressionNodeData.cs @@ -1,113 +1,22 @@ -using ExpressionTreeToString; -using ExpressionTreeToString.Util; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static ExpressionTreeToString.Globals; +using ExpressionTreeVisualizer.Serialization.Util; using System.Linq.Expressions; +using ExpressionTreeToString.Util; +using System.Reflection; +using static ExpressionTreeVisualizer.Serialization.EndNodeTypes; using static ExpressionTreeToString.Util.Functions; -using static ExpressionTreeVisualizer.EndNodeTypes; -using static ExpressionTreeToString.FormatterNames; using System.Collections; +using static ExpressionTreeToString.FormatterNames; using System.Runtime.CompilerServices; -using static ExpressionTreeToString.Globals; -using System.Reflection; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace ExpressionTreeVisualizer { - [Serializable] - public class VisualizerDataOptions : INotifyPropertyChanged { - private string _formatter = CSharp; - public string Formatter { - get => _formatter; - set { - Language = ResolveLanguage(value); - this.NotifyChanged(ref _formatter, value, args => PropertyChanged?.Invoke(this, args)); - } - } - - private string _language = CSharp; - public string Language { - get => _language; - set => this.NotifyChanged(ref _language, value, args => PropertyChanged?.Invoke(this, args)); - } - - public string? Path { get; set; } - - [field: NonSerialized] - public event PropertyChangedEventHandler? PropertyChanged; - - public VisualizerDataOptions(VisualizerDataOptions? options = null) { - if (options is { }) { - _formatter = options.Formatter; - _language = options.Language; - Path = options.Path; - } - } - } - - [Serializable] - public class VisualizerData { - public string Source { get; set; } - public VisualizerDataOptions Options { get; set; } - public ExpressionNodeData NodeData { get; set; } - - [NonSerialized] // the items in this List are grouped and serialized into separate collections - public List CollectedEndNodes; - [NonSerialized] - public Dictionary PathSpans; - - public Dictionary> Constants { get; } - public Dictionary> Parameters { get; } - public Dictionary> ClosedVars { get; } - public Dictionary> Defaults { get; } - - public ExpressionNodeData FindNodeBySpan(int start, int length) { - var end = start + length; - //if (start < NodeData.Span.start || end > NodeData.SpanEnd) { throw new ArgumentOutOfRangeException(); } - var current = NodeData; - while (true) { - var child = current.Children.SingleOrDefault(x => x.Span.start <= start && x.SpanEnd >= end); - if (child == null) { break; } - current = child; - } - return current; - } - - public VisualizerData(object o, VisualizerDataOptions? options = null) { - Options = options ?? new VisualizerDataOptions(); - if (!Options.Path.IsNullOrWhitespace()) { - o = ((Expression)ResolvePath(o, Options.Path)).ExtractValue(); - } - Source = WriterBase.Create(o, Options.Formatter, Options.Language, out var pathSpans).ToString(); - PathSpans = pathSpans; - CollectedEndNodes = new List(); - NodeData = new ExpressionNodeData(o, ("", ""), this, false); - - // TODO it should be possible to write the following using LINQ - Constants = new Dictionary>(); - Parameters = new Dictionary>(); - ClosedVars = new Dictionary>(); - Defaults = new Dictionary>(); - - foreach (var x in CollectedEndNodes) { - var dict = x.EndNodeType switch { - Constant => Constants, - Parameter => Parameters, - ClosedVar => ClosedVars, - Default => Defaults, - _ => throw new InvalidOperationException(), - }; - if (!dict.TryGetValue(x.EndNodeData, out var lst)) { - lst = new List(); - dict[x.EndNodeData] = lst; - } - lst.Add(x); - } - } - } +namespace ExpressionTreeVisualizer.Serialization { [Serializable] [DebuggerDisplay("{FullPath}")] public class ExpressionNodeData : INotifyPropertyChanged { @@ -126,13 +35,13 @@ public class ExpressionNodeData : INotifyPropertyChanged { public (string @namespace, string typename, string propertyname)? ParentProperty { get; set; } public List<(string @namespace, string enumTypename, string membername)>? NodeTypesParts { get; set; } - private readonly List<(string @namespace, string typename)> _baseTypes; - public List<(string @namespace, string typename)> BaseTypes => _baseTypes; + private readonly List<(string @namespace, string typename)>? _baseTypes; + public List<(string @namespace, string typename)>? BaseTypes => _baseTypes; public string? WatchExpressionFormatString { get; set; } public bool EnableValueInNewWindow { get; set; } private readonly string[] _factoryMethodNames; - public string[] FactoryMethodNames => _factoryMethodNames; + public string[]? FactoryMethodNames => _factoryMethodNames; public EndNodeData EndNodeData => new EndNodeData { Closure = Closure, @@ -157,11 +66,11 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare case Expression expr: NodeType = expr.NodeType.ToString(); NodeTypesParts = new List<(string @namespace, string enumTypename, string membername)> { - (typeof(ExpressionType).Namespace, nameof(ExpressionType), NodeType) + (typeof(ExpressionType).Namespace!, nameof(ExpressionType), NodeType) }; if (expr is GotoExpression gexpr) { NodeTypesParts.Add( - typeof(GotoExpressionKind).Namespace, nameof(GotoExpressionKind), gexpr.Kind.ToString() + typeof(GotoExpressionKind).Namespace!, nameof(GotoExpressionKind), gexpr.Kind.ToString() ); } ReflectionTypeName = expr.Type.FriendlyName(language); @@ -170,10 +79,10 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare // fill the Name and Closure properties, for expressions Name = expr.Name(language); - if (expr is MemberExpression mexpr && + if (expr is MemberExpression mexpr && mexpr.Expression?.Type is Type expressionType && expressionType.IsClosureClass()) { - Closure = expressionType.Name; + Closure = expressionType.Name; } object? value = null; @@ -207,8 +116,8 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare case MemberBinding mbind: NodeType = mbind.BindingType.ToString(); NodeTypesParts = new List<(string @namespace, string enumTypename, string membername)> { - (typeof(MemberBindingType).Namespace, nameof(MemberBindingType), NodeType) - }; + (typeof(MemberBindingType).Namespace!, nameof(MemberBindingType), NodeType) + }; Name = mbind.Member.Name; break; case CallSiteBinder callSiteBinder: @@ -225,7 +134,9 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare if (parentWatchExpression.IsNullOrWhitespace()) { WatchExpressionFormatString = "{0}"; - } else if (pi != null) { + } else if (pi is { }) { + if (pi.DeclaringType is null) { throw new ArgumentNullException("pi.DeclaringType"); } + var watchPathFromParent = PathFromParent; if (visualizerData.Options.Language == CSharp) { WatchExpressionFormatString = $"(({pi.DeclaringType.FullName}){parentWatchExpression}).{watchPathFromParent}"; @@ -240,7 +151,7 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare var preferredOrder = PreferredPropertyOrders.FirstOrDefault(x => x.type.IsAssignableFrom(type)).Item2; Children = type.GetProperties() .Where(prp => - !(prp.DeclaringType.Name == "BlockExpression" && prp.Name == "Result") && + !(prp.DeclaringType!.Name == "BlockExpression" && prp.Name == "Result") && propertyTypes.Any(x => x.IsAssignableFrom(prp.PropertyType)) ) .OrderBy(prp => { @@ -252,7 +163,7 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare if (prp.PropertyType.InheritsFromOrImplements()) { return (prp.GetValue(o) as IEnumerable).Cast().Select((x, index) => ($"{prp.Name}[{index}]", x, prp)); } else { - return new[] { (prp.Name, prp.GetValue(o), prp) }; + return new[] { (prp.Name, prp.GetValue(o)!, prp) }; } }) .Where(x => x.x != null) @@ -261,11 +172,11 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare // populate URLs if (pi != null) { - ParentProperty = (pi.DeclaringType.Namespace, pi.DeclaringType.Name, pi.Name); + ParentProperty = (pi.DeclaringType.Namespace!, pi.DeclaringType.Name, pi.Name); } if (!baseTypes.TryGetValue(o.GetType(), out _baseTypes)) { - _baseTypes = o.GetType().BaseTypes(true, true).Where(x => x != typeof(object) && x.IsPublic).Select(x => (x.Namespace, x.Name)).Distinct().ToList(); + _baseTypes = o.GetType().BaseTypes(true, true).Where(x => x != typeof(object) && x.IsPublic).Select(x => (x.Namespace!, x.Name)).Distinct().ToList(); baseTypes[o.GetType()] = _baseTypes; } @@ -275,7 +186,7 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare } if (factoryMethodName.IsNullOrWhitespace()) { var publicType = o.GetType().BaseTypes(false, true).FirstOrDefault(x => !x.IsInterface && x.IsPublic); - factoryMethods.TryGetValue(publicType, out _factoryMethodNames); + factoryMethods.TryGetValue(publicType, out _factoryMethodNames!); } else { _factoryMethodNames = new[] { factoryMethodName }; } @@ -294,7 +205,7 @@ internal ExpressionNodeData(object o, (string aggregatePath, string pathFromPare private bool _isSelected; public bool IsSelected { get => _isSelected; - set => this.NotifyChanged(ref _isSelected, value, args => PropertyChanged?.Invoke(this, args)); + set => Extensions.NotifyChanged(this, ref _isSelected, value, args => PropertyChanged?.Invoke(this, args)); } public void ClearSelection() { @@ -302,37 +213,4 @@ public void ClearSelection() { Children.ForEach(x => x.ClearSelection()); } } - - [Serializable] - [SuppressMessage("", "IDE0032", Justification = "https://github.com/dotnet/core/issues/2981")] - public struct EndNodeData { - private string? _closure; - private string? _name; - private string? _type; - private string? _value; - - public string? Closure { - get => _closure; - set => _closure = value; - } - public string? Name { - get => _name; - set => _name = value; - } - public string? Type { - get => _type; - set => _type = value; - } - public string? Value { - get => _value; - set => _value = value; - } - } - - public enum EndNodeTypes { - Constant, - Parameter, - ClosedVar, - Default - } } diff --git a/Serialization/Extensions.cs b/Serialization/Extensions.cs new file mode 100644 index 0000000..eb19d84 --- /dev/null +++ b/Serialization/Extensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace ExpressionTreeVisualizer.Serialization.Util { + public static class Extensions { + public static void NotifyChanged(this INotifyPropertyChanged inpc, ref T current, T newValue, Action eventRaiser, [CallerMemberName] string? name = null) where T : IEquatable { + if (current.Equals(newValue)) { return; } + current = newValue; + eventRaiser(new PropertyChangedEventArgs(name)); + } + } +} diff --git a/Visualizer/NullableAttributes.cs b/Serialization/NullableAttributes.cs similarity index 100% rename from Visualizer/NullableAttributes.cs rename to Serialization/NullableAttributes.cs diff --git a/Serialization/Serialization.projitems b/Serialization/Serialization.projitems new file mode 100644 index 0000000..1ae4c3d --- /dev/null +++ b/Serialization/Serialization.projitems @@ -0,0 +1,20 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 9365b962-f17c-451c-92eb-f4fed9c6c84e + + + ExpressionTreeVisualizer.Serialization + + + + + + + + + + + \ No newline at end of file diff --git a/Visualizer.Shared/Visualizer.Shared.shproj b/Serialization/Serialization.shproj similarity index 83% rename from Visualizer.Shared/Visualizer.Shared.shproj rename to Serialization/Serialization.shproj index 8162089..1bf58cf 100644 --- a/Visualizer.Shared/Visualizer.Shared.shproj +++ b/Serialization/Serialization.shproj @@ -1,13 +1,13 @@ - + - 16ca0653-e03a-47df-a5bf-292eef9aa5f0 + 9365b962-f17c-451c-92eb-f4fed9c6c84e 14.0 - + diff --git a/Serialization/VisualizerData.cs b/Serialization/VisualizerData.cs new file mode 100644 index 0000000..55eedc9 --- /dev/null +++ b/Serialization/VisualizerData.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using ExpressionTreeToString; +using ExpressionTreeToString.Util; +using static ExpressionTreeToString.Util.Functions; +using static ExpressionTreeVisualizer.Serialization.EndNodeTypes; + +namespace ExpressionTreeVisualizer.Serialization { + [Serializable] + public class VisualizerData { + public string Source { get; set; } + public VisualizerDataOptions Options { get; set; } + public ExpressionNodeData NodeData { get; set; } + + [NonSerialized] // the items in this List are grouped and serialized into separate collections + public List CollectedEndNodes; + [NonSerialized] + public Dictionary PathSpans; + + public Dictionary> Constants { get; } + public Dictionary> Parameters { get; } + public Dictionary> ClosedVars { get; } + public Dictionary> Defaults { get; } + + public ExpressionNodeData FindNodeBySpan(int start, int length) { + var end = start + length; + //if (start < NodeData.Span.start || end > NodeData.SpanEnd) { throw new ArgumentOutOfRangeException(); } + var current = NodeData; + while (true) { + var child = current.Children.SingleOrDefault(x => x.Span.start <= start && x.SpanEnd >= end); + if (child == null) { break; } + current = child; + } + return current; + } + + public VisualizerData(object o, VisualizerDataOptions? options = null) { + Options = options ?? new VisualizerDataOptions(); + if (!Options.Path.IsNullOrWhitespace()) { + o = ((Expression)ResolvePath(o, Options.Path)).ExtractValue(); + } + Source = WriterBase.Create(o, Options.Formatter, Options.Language, out var pathSpans).ToString(); + PathSpans = pathSpans; + CollectedEndNodes = new List(); + NodeData = new ExpressionNodeData(o, ("", ""), this, false); + + // TODO it should be possible to write the following using LINQ + Constants = new Dictionary>(); + Parameters = new Dictionary>(); + ClosedVars = new Dictionary>(); + Defaults = new Dictionary>(); + + foreach (var x in CollectedEndNodes) { + var dict = x.EndNodeType switch + { + Constant => Constants, + Parameter => Parameters, + ClosedVar => ClosedVars, + Default => Defaults, + _ => throw new InvalidOperationException(), + }; + if (!dict.TryGetValue(x.EndNodeData, out var lst)) { + lst = new List(); + dict[x.EndNodeData] = lst; + } + lst.Add(x); + } + } + } + +} diff --git a/Serialization/VisualizerDataOptions.cs b/Serialization/VisualizerDataOptions.cs new file mode 100644 index 0000000..19d3dd5 --- /dev/null +++ b/Serialization/VisualizerDataOptions.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel; +using static ExpressionTreeToString.Util.Functions; +using static ExpressionTreeToString.FormatterNames; +using ExpressionTreeVisualizer.Serialization.Util; + +namespace ExpressionTreeVisualizer.Serialization { + [Serializable] + public class VisualizerDataOptions : INotifyPropertyChanged { + private string _formatter = CSharp; + public string Formatter { + get => _formatter; + set { + Language = ResolveLanguage(value); + this.NotifyChanged(ref _formatter, value, args => PropertyChanged?.Invoke(this, args)); + } + } + + private string _language = CSharp; + public string Language { + get => _language; + set => this.NotifyChanged(ref _language, value, args => PropertyChanged?.Invoke(this, args)); + } + + public string? Path { get; set; } + + [field: NonSerialized] + public event PropertyChangedEventHandler? PropertyChanged; + + public VisualizerDataOptions(VisualizerDataOptions? options = null) { + if (options is { }) { + _formatter = options.Formatter; + _language = options.Language; + Path = options.Path; + } + } + } + +} diff --git a/Tests.Visualizer/Extensions.cs b/Tests.Visualizer/Extensions.cs index 3ce0a0a..e76c073 100644 --- a/Tests.Visualizer/Extensions.cs +++ b/Tests.Visualizer/Extensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Xunit; -namespace ExpressionTreeToString.Tests.Visualizer { +namespace ExpressionTreeVisualizer.Tests { internal static class Extensions { internal static TheoryData ToTheoryData(this IEnumerable> src) { var ret = new TheoryData(); diff --git a/Tests.Visualizer/TestContainer.cs b/Tests.Visualizer/TestContainer.cs index ee6f780..2bfbbec 100644 --- a/Tests.Visualizer/TestContainer.cs +++ b/Tests.Visualizer/TestContainer.cs @@ -6,9 +6,9 @@ using System.Threading.Tasks; using Xunit; using ExpressionTreeTestObjects; -using ExpressionTreeToString.Tests; +using ExpressionTreeVisualizer.Serialization; -namespace ExpressionTreeToString.Tests.Visualizer { +namespace ExpressionTreeVisualizer.Tests { public class TestContainer { [Theory] [MemberData(nameof(TestObjects))] diff --git a/Tests.Visualizer/Tests.Visualizer.csproj b/Tests.Visualizer/Tests.csproj similarity index 77% rename from Tests.Visualizer/Tests.Visualizer.csproj rename to Tests.Visualizer/Tests.csproj index 4b92dc3..c1f0380 100644 --- a/Tests.Visualizer/Tests.Visualizer.csproj +++ b/Tests.Visualizer/Tests.csproj @@ -4,6 +4,8 @@ net472 8.0 enable + ExpressionTreeVisualizer.Tests + ExpressionTreeVisualizer.Tests @@ -14,8 +16,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + \ No newline at end of file diff --git a/Visualizer/Converters.cs b/UI/Converters.cs similarity index 100% rename from Visualizer/Converters.cs rename to UI/Converters.cs diff --git a/Visualizer.Shared/ExpressionRootPrompt.xaml b/UI/ExpressionRootPrompt.xaml similarity index 100% rename from Visualizer.Shared/ExpressionRootPrompt.xaml rename to UI/ExpressionRootPrompt.xaml diff --git a/Visualizer.Shared/ExpressionRootPrompt.xaml.cs b/UI/ExpressionRootPrompt.xaml.cs similarity index 100% rename from Visualizer.Shared/ExpressionRootPrompt.xaml.cs rename to UI/ExpressionRootPrompt.xaml.cs diff --git a/UI/UI.projitems b/UI/UI.projitems new file mode 100644 index 0000000..ab60fab --- /dev/null +++ b/UI/UI.projitems @@ -0,0 +1,43 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + e1d67370-17ac-47fc-89bc-34b20b3bd6e7 + + + ExpressionTreeVisualizer + + + + + ExpressionRootPrompt.xaml + + + + + + + + + + + VisualizerDataControl.xaml + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + + \ No newline at end of file diff --git a/UI/UI.shproj b/UI/UI.shproj new file mode 100644 index 0000000..0e62973 --- /dev/null +++ b/UI/UI.shproj @@ -0,0 +1,13 @@ + + + + e1d67370-17ac-47fc-89bc-34b20b3bd6e7 + 14.0 + + + + + + + + diff --git a/Visualizer/Util/Extensions/DependencyObject.cs b/UI/Util/Extensions/DependencyObject.cs similarity index 100% rename from Visualizer/Util/Extensions/DependencyObject.cs rename to UI/Util/Extensions/DependencyObject.cs diff --git a/Visualizer/Util/Extensions/MultiSelectTreeView.cs b/UI/Util/Extensions/MultiSelectTreeView.cs similarity index 100% rename from Visualizer/Util/Extensions/MultiSelectTreeView.cs rename to UI/Util/Extensions/MultiSelectTreeView.cs diff --git a/Visualizer/Util/Extensions/MultiSelector.cs b/UI/Util/Extensions/MultiSelector.cs similarity index 100% rename from Visualizer/Util/Extensions/MultiSelector.cs rename to UI/Util/Extensions/MultiSelector.cs diff --git a/Visualizer/Util/Extensions/Selector.cs b/UI/Util/Extensions/Selector.cs similarity index 100% rename from Visualizer/Util/Extensions/Selector.cs rename to UI/Util/Extensions/Selector.cs diff --git a/Visualizer/Util/Extensions/TreeView.cs b/UI/Util/Extensions/TreeView.cs similarity index 100% rename from Visualizer/Util/Extensions/TreeView.cs rename to UI/Util/Extensions/TreeView.cs diff --git a/Visualizer/Util/Functions.cs b/UI/Util/Functions.cs similarity index 100% rename from Visualizer/Util/Functions.cs rename to UI/Util/Functions.cs diff --git a/Visualizer/Util/Monitor.cs b/UI/Util/Monitor.cs similarity index 100% rename from Visualizer/Util/Monitor.cs rename to UI/Util/Monitor.cs diff --git a/UI/VisualizerData.cs b/UI/VisualizerData.cs new file mode 100644 index 0000000..82280f9 --- /dev/null +++ b/UI/VisualizerData.cs @@ -0,0 +1,338 @@ +//using ExpressionTreeToString; +//using ExpressionTreeToString.Util; +//using System; +//using System.Collections.Generic; +//using System.ComponentModel; +//using System.Linq; +//using System.Linq.Expressions; +//using static ExpressionTreeToString.Util.Functions; +//using static ExpressionTreeVisualizer.EndNodeTypes; +//using static ExpressionTreeToString.FormatterNames; +//using System.Collections; +//using System.Runtime.CompilerServices; +//using static ExpressionTreeToString.Globals; +//using System.Reflection; +//using System.Diagnostics; +//using System.Diagnostics.CodeAnalysis; + +//namespace ExpressionTreeVisualizer { +// [Serializable] +// public class VisualizerDataOptions : INotifyPropertyChanged { +// private string _formatter = CSharp; +// public string Formatter { +// get => _formatter; +// set { +// Language = ResolveLanguage(value); +// this.NotifyChanged(ref _formatter, value, args => PropertyChanged?.Invoke(this, args)); +// } +// } + +// private string _language = CSharp; +// public string Language { +// get => _language; +// set => this.NotifyChanged(ref _language, value, args => PropertyChanged?.Invoke(this, args)); +// } + +// public string? Path { get; set; } + +// [field: NonSerialized] +// public event PropertyChangedEventHandler? PropertyChanged; + +// public VisualizerDataOptions(VisualizerDataOptions? options = null) { +// if (options is { }) { +// _formatter = options.Formatter; +// _language = options.Language; +// Path = options.Path; +// } +// } +// } + +// [Serializable] +// public class VisualizerData { +// public string Source { get; set; } +// public VisualizerDataOptions Options { get; set; } +// public ExpressionNodeData NodeData { get; set; } + +// [NonSerialized] // the items in this List are grouped and serialized into separate collections +// public List CollectedEndNodes; +// [NonSerialized] +// public Dictionary PathSpans; + +// public Dictionary> Constants { get; } +// public Dictionary> Parameters { get; } +// public Dictionary> ClosedVars { get; } +// public Dictionary> Defaults { get; } + +// public ExpressionNodeData FindNodeBySpan(int start, int length) { +// var end = start + length; +// //if (start < NodeData.Span.start || end > NodeData.SpanEnd) { throw new ArgumentOutOfRangeException(); } +// var current = NodeData; +// while (true) { +// var child = current.Children.SingleOrDefault(x => x.Span.start <= start && x.SpanEnd >= end); +// if (child == null) { break; } +// current = child; +// } +// return current; +// } + +// public VisualizerData(object o, VisualizerDataOptions? options = null) { +// Options = options ?? new VisualizerDataOptions(); +// if (!Options.Path.IsNullOrWhitespace()) { +// o = ((Expression)ResolvePath(o, Options.Path)).ExtractValue(); +// } +// Source = WriterBase.Create(o, Options.Formatter, Options.Language, out var pathSpans).ToString(); +// PathSpans = pathSpans; +// CollectedEndNodes = new List(); +// NodeData = new ExpressionNodeData(o, ("", ""), this, false); + +// // TODO it should be possible to write the following using LINQ +// Constants = new Dictionary>(); +// Parameters = new Dictionary>(); +// ClosedVars = new Dictionary>(); +// Defaults = new Dictionary>(); + +// foreach (var x in CollectedEndNodes) { +// var dict = x.EndNodeType switch { +// Constant => Constants, +// Parameter => Parameters, +// ClosedVar => ClosedVars, +// Default => Defaults, +// _ => throw new InvalidOperationException(), +// }; +// if (!dict.TryGetValue(x.EndNodeData, out var lst)) { +// lst = new List(); +// dict[x.EndNodeData] = lst; +// } +// lst.Add(x); +// } +// } +// } + +// [Serializable] +// [DebuggerDisplay("{FullPath}")] +// public class ExpressionNodeData : INotifyPropertyChanged { +// public List Children { get; set; } +// public string NodeType { get; set; } // ideally this should be an intersection type of multiple enums +// public string? ReflectionTypeName { get; set; } +// public (int start, int length) Span { get; set; } +// public int SpanEnd => Span.start + Span.length; +// public string? StringValue { get; set; } +// public string? Name { get; set; } +// public string? Closure { get; set; } +// public EndNodeTypes? EndNodeType { get; set; } +// public bool IsDeclaration { get; set; } +// public string PathFromParent { get; set; } = ""; +// public string FullPath { get; set; } = ""; +// public (string @namespace, string typename, string propertyname)? ParentProperty { get; set; } +// public List<(string @namespace, string enumTypename, string membername)>? NodeTypesParts { get; set; } + +// private readonly List<(string @namespace, string typename)> _baseTypes; +// public List<(string @namespace, string typename)> BaseTypes => _baseTypes; +// public string? WatchExpressionFormatString { get; set; } +// public bool EnableValueInNewWindow { get; set; } + +// private readonly string[] _factoryMethodNames; +// public string[] FactoryMethodNames => _factoryMethodNames; + +// public EndNodeData EndNodeData => new EndNodeData { +// Closure = Closure, +// Name = Name, +// Type = ReflectionTypeName, +// Value = StringValue +// }; + +// private static readonly HashSet propertyTypes = NodeTypes.SelectMany(x => new[] { x, typeof(IEnumerable<>).MakeGenericType(x) }).ToHashSet(); + +// internal ExpressionNodeData(object o, (string aggregatePath, string pathFromParent) path, VisualizerData visualizerData, bool isParameterDeclaration = false, PropertyInfo? pi = null, string? parentWatchExpression = null) { +// var (aggregatePath, pathFromParent) = path; +// PathFromParent = pathFromParent; +// if (aggregatePath.IsNullOrWhitespace() || pathFromParent.IsNullOrWhitespace()) { +// FullPath = aggregatePath + pathFromParent; +// } else { +// FullPath = $"{aggregatePath}.{pathFromParent}"; +// } + +// var language = visualizerData.Options.Language; +// switch (o) { +// case Expression expr: +// NodeType = expr.NodeType.ToString(); +// NodeTypesParts = new List<(string @namespace, string enumTypename, string membername)> { +// (typeof(ExpressionType).Namespace, nameof(ExpressionType), NodeType) +// }; +// if (expr is GotoExpression gexpr) { +// NodeTypesParts.Add( +// typeof(GotoExpressionKind).Namespace, nameof(GotoExpressionKind), gexpr.Kind.ToString() +// ); +// } +// ReflectionTypeName = expr.Type.FriendlyName(language); +// IsDeclaration = isParameterDeclaration; + +// // fill the Name and Closure properties, for expressions +// Name = expr.Name(language); + +// if (expr is MemberExpression mexpr && +// mexpr.Expression?.Type is Type expressionType && +// expressionType.IsClosureClass()) { +// Closure = expressionType.Name; +// } + +// object? value = null; + +// // fill StringValue and EndNodeType properties, for expressions +// switch (expr) { +// case ConstantExpression cexpr when !cexpr.Type.IsClosureClass(): +// value = cexpr.Value; +// EndNodeType = Constant; +// break; +// case ParameterExpression pexpr1: +// EndNodeType = Parameter; +// break; +// case Expression e1 when expr.IsClosedVariable(): +// value = expr.ExtractValue(); +// EndNodeType = ClosedVar; +// break; +// case DefaultExpression defexpr: +// value = defexpr.ExtractValue(); +// EndNodeType = Default; +// break; +// } +// if (EndNodeType != null) { visualizerData.CollectedEndNodes.Add(this); } + +// if (value != null) { +// StringValue = StringValue(value, language); +// EnableValueInNewWindow = value.GetType().InheritsFromOrImplementsAny(NodeTypes); +// } + +// break; +// case MemberBinding mbind: +// NodeType = mbind.BindingType.ToString(); +// NodeTypesParts = new List<(string @namespace, string enumTypename, string membername)> { +// (typeof(MemberBindingType).Namespace, nameof(MemberBindingType), NodeType) +// }; +// Name = mbind.Member.Name; +// break; +// case CallSiteBinder callSiteBinder: +// NodeType = callSiteBinder.BinderType(); +// break; +// default: +// NodeType = o.GetType().FriendlyName(language); +// break; +// } + +// if (visualizerData.PathSpans.TryGetValue(FullPath, out var span)) { +// Span = span; +// } + +// if (parentWatchExpression.IsNullOrWhitespace()) { +// WatchExpressionFormatString = "{0}"; +// } else if (pi != null) { +// var watchPathFromParent = PathFromParent; +// if (visualizerData.Options.Language == CSharp) { +// WatchExpressionFormatString = $"(({pi.DeclaringType.FullName}){parentWatchExpression}).{watchPathFromParent}"; +// } else { //VisualBasic +// watchPathFromParent = watchPathFromParent.Replace("[", "(").Replace("]", ")"); +// WatchExpressionFormatString = $"CType({parentWatchExpression}, {pi.DeclaringType.FullName}).{watchPathFromParent}"; +// } +// } + +// // populate Children +// var type = o.GetType(); +// var preferredOrder = PreferredPropertyOrders.FirstOrDefault(x => x.type.IsAssignableFrom(type)).Item2; +// Children = type.GetProperties() +// .Where(prp => +// !(prp.DeclaringType.Name == "BlockExpression" && prp.Name == "Result") && +// propertyTypes.Any(x => x.IsAssignableFrom(prp.PropertyType)) +// ) +// .OrderBy(prp => { +// if (preferredOrder == null) { return -1; } +// return Array.IndexOf(preferredOrder, prp.Name); +// }) +// .ThenBy(prp => prp.Name) +// .SelectMany(prp => { +// if (prp.PropertyType.InheritsFromOrImplements()) { +// return (prp.GetValue(o) as IEnumerable).Cast().Select((x, index) => ($"{prp.Name}[{index}]", x, prp)); +// } else { +// return new[] { (prp.Name, prp.GetValue(o), prp) }; +// } +// }) +// .Where(x => x.x != null) +// .SelectT((relativePath, o1, prp) => new ExpressionNodeData(o1, (FullPath ?? "", relativePath), visualizerData, false, prp, WatchExpressionFormatString)) +// .ToList(); + +// // populate URLs +// if (pi != null) { +// ParentProperty = (pi.DeclaringType.Namespace, pi.DeclaringType.Name, pi.Name); +// } + +// if (!baseTypes.TryGetValue(o.GetType(), out _baseTypes)) { +// _baseTypes = o.GetType().BaseTypes(true, true).Where(x => x != typeof(object) && x.IsPublic).Select(x => (x.Namespace, x.Name)).Distinct().ToList(); +// baseTypes[o.GetType()] = _baseTypes; +// } + +// string? factoryMethodName = null; +// if (o is BinaryExpression || o is UnaryExpression) { +// BinaryUnaryMethods.TryGetValue(((Expression)o).NodeType, out factoryMethodName); +// } +// if (factoryMethodName.IsNullOrWhitespace()) { +// var publicType = o.GetType().BaseTypes(false, true).FirstOrDefault(x => !x.IsInterface && x.IsPublic); +// factoryMethods.TryGetValue(publicType, out _factoryMethodNames); +// } else { +// _factoryMethodNames = new[] { factoryMethodName }; +// } +// } + +// private static readonly Dictionary> baseTypes = new Dictionary>(); + +// private static readonly Dictionary factoryMethods = typeof(Expression).GetMethods() +// .Where(x => x.IsStatic) +// .GroupBy(x => x.ReturnType, x => x.Name) +// .ToDictionary(x => x.Key, x => x.Distinct().Ordered().ToArray()); + + +// public event PropertyChangedEventHandler? PropertyChanged; + +// private bool _isSelected; +// public bool IsSelected { +// get => _isSelected; +// set => this.NotifyChanged(ref _isSelected, value, args => PropertyChanged?.Invoke(this, args)); +// } + +// public void ClearSelection() { +// IsSelected = false; +// Children.ForEach(x => x.ClearSelection()); +// } +// } + +// [Serializable] +// [SuppressMessage("", "IDE0032", Justification = "https://github.com/dotnet/core/issues/2981")] +// public struct EndNodeData { +// private string? _closure; +// private string? _name; +// private string? _type; +// private string? _value; + +// public string? Closure { +// get => _closure; +// set => _closure = value; +// } +// public string? Name { +// get => _name; +// set => _name = value; +// } +// public string? Type { +// get => _type; +// set => _type = value; +// } +// public string? Value { +// get => _value; +// set => _value = value; +// } +// } + +// public enum EndNodeTypes { +// Constant, +// Parameter, +// ClosedVar, +// Default +// } +//} diff --git a/Visualizer.Shared/VisualizerDataControl.xaml b/UI/VisualizerDataControl.xaml similarity index 100% rename from Visualizer.Shared/VisualizerDataControl.xaml rename to UI/VisualizerDataControl.xaml diff --git a/Visualizer.Shared/VisualizerDataControl.xaml.cs b/UI/VisualizerDataControl.xaml.cs similarity index 99% rename from Visualizer.Shared/VisualizerDataControl.xaml.cs rename to UI/VisualizerDataControl.xaml.cs index 39b2a09..b65feda 100644 --- a/Visualizer.Shared/VisualizerDataControl.xaml.cs +++ b/UI/VisualizerDataControl.xaml.cs @@ -9,6 +9,7 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using static ExpressionTreeToString.FormatterNames; +using ExpressionTreeVisualizer.Serialization; namespace ExpressionTreeVisualizer { diff --git a/Visualizer/WpfAutoGrid/AutoGrid.cs b/UI/WpfAutoGrid/AutoGrid.cs similarity index 100% rename from Visualizer/WpfAutoGrid/AutoGrid.cs rename to UI/WpfAutoGrid/AutoGrid.cs diff --git a/Visualizer/WpfAutoGrid/DependencyExtensions.cs b/UI/WpfAutoGrid/DependencyExtensions.cs similarity index 100% rename from Visualizer/WpfAutoGrid/DependencyExtensions.cs rename to UI/WpfAutoGrid/DependencyExtensions.cs diff --git a/Visualizer/WpfAutoGrid/license.txt b/UI/WpfAutoGrid/license.txt similarity index 100% rename from Visualizer/WpfAutoGrid/license.txt rename to UI/WpfAutoGrid/license.txt diff --git a/Visualizer.Shared/Visualizer.Shared.projitems b/Visualizer.Shared/Visualizer.Shared.projitems deleted file mode 100644 index 6db5809..0000000 --- a/Visualizer.Shared/Visualizer.Shared.projitems +++ /dev/null @@ -1,39 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 16ca0653-e03a-47df-a5bf-292eef9aa5f0 - - - ExpressionTreeVisualizer.Shared - - - - - VisualizerDataControl.xaml - - - VisualizerWindow.xaml - - - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - - - ExpressionRootPrompt.xaml - - - \ No newline at end of file diff --git a/Visualizer.Xaml/Visualizer.Xaml.projitems b/Visualizer.Xaml/Visualizer.Xaml.projitems new file mode 100644 index 0000000..df214ca --- /dev/null +++ b/Visualizer.Xaml/Visualizer.Xaml.projitems @@ -0,0 +1,22 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 89de9332-5aa0-4136-a785-ed712fdc39fc + + + ExpressionTreeVisualizer + + + + VisualizerWindow.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/Visualizer.Xaml/Visualizer.Xaml.shproj b/Visualizer.Xaml/Visualizer.Xaml.shproj new file mode 100644 index 0000000..f0e0161 --- /dev/null +++ b/Visualizer.Xaml/Visualizer.Xaml.shproj @@ -0,0 +1,13 @@ + + + + 89de9332-5aa0-4136-a785-ed712fdc39fc + 14.0 + + + + + + + + diff --git a/Visualizer.Shared/VisualizerWindow.xaml b/Visualizer.Xaml/VisualizerWindow.xaml similarity index 100% rename from Visualizer.Shared/VisualizerWindow.xaml rename to Visualizer.Xaml/VisualizerWindow.xaml diff --git a/Visualizer.Shared/VisualizerWindow.xaml.cs b/Visualizer.Xaml/VisualizerWindow.xaml.cs similarity index 100% rename from Visualizer.Shared/VisualizerWindow.xaml.cs rename to Visualizer.Xaml/VisualizerWindow.xaml.cs diff --git a/Visualizer/Visualizer.cs b/Visualizer/Visualizer.cs index 5e497ac..74264e2 100644 --- a/Visualizer/Visualizer.cs +++ b/Visualizer/Visualizer.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Windows; +using ExpressionTreeVisualizer.Serialization; [assembly: DebuggerVisualizer( visualizer: typeof(ExpressionTreeVisualizer.Visualizer), diff --git a/Visualizer/Visualizer.csproj b/Visualizer/Visualizer.csproj index 9a2d155..e76b71c 100644 --- a/Visualizer/Visualizer.csproj +++ b/Visualizer/Visualizer.csproj @@ -9,8 +9,6 @@ true - - ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll @@ -20,10 +18,17 @@ - - - + + + + + + + + + + \ No newline at end of file diff --git a/_visualizerTestNoRef/_visualizerTestNoRef.csproj b/_visualizerTestNoRef/_visualizerTestNoRef.csproj index c73e0d1..23df604 100644 --- a/_visualizerTestNoRef/_visualizerTestNoRef.csproj +++ b/_visualizerTestNoRef/_visualizerTestNoRef.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + netcoreapp2.1