├── screenshot-01.png ├── sync-from-code.gif ├── sync-from-tree.gif ├── formatter-switch.gif ├── sync-from-endnodes.gif ├── .github └── ISSUE_TEMPLATE │ ├── misc.md │ ├── bug-report.md │ └── feature-request.md ├── Serialization ├── EndNodeType.cs ├── EndNodeData.cs ├── VisualizerData.cs ├── Serialization.projitems ├── Serialization.shproj ├── Config.cs ├── NullableAttributes.cs └── ExpressionNodeData.cs ├── .gitmodules ├── UI ├── SettingsControl.xaml.cs ├── SettingsControl.xaml ├── ViewModels │ ├── ConfigViewModel.cs │ ├── EndNodeGroupViewModel.cs │ ├── ExpressionNodeDataViewModel.cs │ └── VisualizerDataViewModel.cs ├── UI.shproj ├── UI.projitems ├── Converters.cs ├── VisualizerDataControl.xaml.cs └── VisualizerDataControl.xaml ├── Debuggee ├── VisualizerDataObjectSource.cs └── Debuggee.csproj ├── Tests.Visualizer ├── Extensions.cs ├── TestContainer.cs └── Tests.csproj ├── Visualizer ├── VisualizerWindow.xaml ├── VisualizerWindow.xaml.cs ├── Visualizer.csproj └── Visualizer.cs ├── LICENSE ├── appveyor.yml ├── Package └── Package.csproj ├── .gitattributes ├── ExpressionTreeVisualizer.sln ├── README.md ├── .gitignore └── .editorconfig /screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zspitz/ExpressionTreeVisualizer/HEAD/screenshot-01.png -------------------------------------------------------------------------------- /sync-from-code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zspitz/ExpressionTreeVisualizer/HEAD/sync-from-code.gif -------------------------------------------------------------------------------- /sync-from-tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zspitz/ExpressionTreeVisualizer/HEAD/sync-from-tree.gif -------------------------------------------------------------------------------- /formatter-switch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zspitz/ExpressionTreeVisualizer/HEAD/formatter-switch.gif -------------------------------------------------------------------------------- /sync-from-endnodes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zspitz/ExpressionTreeVisualizer/HEAD/sync-from-endnodes.gif -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/misc.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General Issue 3 | about: Miscellaneous issue 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /Serialization/EndNodeType.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionTreeVisualizer.Serialization { 2 | public enum EndNodeTypes { 3 | Constant, 4 | Parameter, 5 | ClosedVar, 6 | Default 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Visualizer/Periscope"] 2 | path = Visualizer/Periscope 3 | url = https://github.com/zspitz/Periscope.git 4 | [submodule "Debuggee/Periscope.Debuggee"] 5 | path = Debuggee/Periscope.Debuggee 6 | url = https://github.com/zspitz/Periscope.Debuggee.git 7 | -------------------------------------------------------------------------------- /UI/SettingsControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using static ExpressionTreeToString.Renderers; 2 | using static ZSpitz.Util.LanguageNames; 3 | 4 | namespace ExpressionTreeVisualizer { 5 | public partial class SettingsControl { 6 | public SettingsControl() { 7 | InitializeComponent(); 8 | 9 | cmbFormatters.ItemsSource = RendererKeys; 10 | cmbLanguages.ItemsSource = new[] { CSharp, VisualBasic }; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Debuggee/VisualizerDataObjectSource.cs: -------------------------------------------------------------------------------- 1 | using ExpressionTreeVisualizer.Serialization; 2 | using Periscope.Debuggee; 3 | 4 | namespace ExpressionTreeVisualizer { 5 | public class VisualizerDataObjectSource : VisualizerObjectSourceBase { 6 | static VisualizerDataObjectSource() => SubfolderAssemblyResolver.Hook("ExpressionTreeVisualizer"); 7 | public override object GetResponse(object target, Config config) => new VisualizerData(target, config); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests.Visualizer/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Xunit; 4 | 5 | namespace ExpressionTreeVisualizer.Tests { 6 | internal static class Extensions { 7 | internal static TheoryData ToTheoryData(this IEnumerable> src) { 8 | var ret = new TheoryData(); 9 | foreach (var (a, b) in src) { 10 | ret.Add(a, b); 11 | } 12 | return ret; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Visualizer/VisualizerWindow.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tests.Visualizer/TestContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Xunit; 3 | using ExpressionTreeTestObjects; 4 | using ExpressionTreeVisualizer.Serialization; 5 | 6 | namespace ExpressionTreeVisualizer.Tests { 7 | public class TestContainer { 8 | [Theory] 9 | [MemberData(nameof(TestObjects))] 10 | public void TestMethod(string objectName, object o) { 11 | var vd = new VisualizerData(o); 12 | } 13 | 14 | public static TheoryData TestObjects => 15 | Objects.Get().Select(x => ($"{x.source}.{x.name}", x.o)).ToTheoryData(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UI/SettingsControl.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /UI/ViewModels/ConfigViewModel.cs: -------------------------------------------------------------------------------- 1 | using ZSpitz.Util.Wpf; 2 | using ExpressionTreeVisualizer.Serialization; 3 | 4 | namespace ExpressionTreeVisualizer { 5 | public class ConfigViewModel : ViewModelBase { 6 | public ConfigViewModel(Config config) : base(config) { } 7 | 8 | public string Formatter { 9 | get => Model.Formatter; 10 | set { 11 | var prevLanguage = Model.Language; 12 | NotifyChanged(Model.Formatter, value, () => Model.Formatter = value); 13 | NotifyChanged(prevLanguage, Language, null, "Language"); 14 | } 15 | } 16 | 17 | public string Language { 18 | get => Model.Language; 19 | set => NotifyChanged(Model.Language, value, () => Model.Language = value); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Serialization/EndNodeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace ExpressionTreeVisualizer.Serialization { 5 | [Serializable] 6 | [SuppressMessage("", "IDE0032", Justification = "https://github.com/dotnet/core/issues/2981")] 7 | public struct EndNodeData { 8 | private string? _closure; 9 | private string? _name; 10 | private string? _type; 11 | private string? _value; 12 | 13 | public string? Closure { 14 | get => _closure; 15 | set => _closure = value; 16 | } 17 | public string? Name { 18 | get => _name; 19 | set => _name = value; 20 | } 21 | public string? Type { 22 | get => _type; 23 | set => _type = value; 24 | } 25 | public string? Value { 26 | get => _value; 27 | set => _value = value; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Serialization/VisualizerData.cs: -------------------------------------------------------------------------------- 1 | using ExpressionTreeToString; 2 | using System; 3 | using System.Linq.Expressions; 4 | using ZSpitz.Util; 5 | using static ZSpitz.Util.Functions; 6 | 7 | namespace ExpressionTreeVisualizer.Serialization { 8 | [Serializable] 9 | public class VisualizerData { 10 | public Config Config { get; set; } 11 | public string Source { get; set; } 12 | public ExpressionNodeData Root { get; set; } 13 | 14 | public VisualizerData(object o, Config? config = null) { 15 | Config = config ?? new Config(); 16 | if (!Config.Path.IsNullOrWhitespace()) { 17 | o = ((Expression)ResolvePath(o, Config.Path)!).ExtractValue()!; 18 | } 19 | Source = Renderers.Invoke(Config.Formatter, o, Config.Language, out var pathSpans); 20 | 21 | var valueExtractor = new ValueExtractor(); 22 | Root = new ExpressionNodeData(o, ("", ""), this, valueExtractor, pathSpans, false); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in the visualizer 4 | title: '' 5 | labels: bug, visualizer ui 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Version info:** 28 | 29 | - Visual Studio version: 30 | - Visualizer location: [VS install path, or Documents subfolder] 31 | - Version: [GitHub release version or project commit] 32 | 33 | Note that the visualizer location and version can be found at the bottom of the **Settings** popup (click the gear icon). 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /Serialization/Serialization.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 9365b962-f17c-451c-92eb-f4fed9c6c84e 7 | 8 | 9 | ExpressionTreeVisualizer.Serialization 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /UI/UI.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | e1d67370-17ac-47fc-89bc-34b20b3bd6e7 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Serialization/Serialization.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9365b962-f17c-451c-92eb-f4fed9c6c84e 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zev Spitz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Tests.Visualizer/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | 9.0 6 | enable 7 | ExpressionTreeVisualizer.Tests 8 | ExpressionTreeVisualizer.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for the visualizer 4 | title: '' 5 | labels: enhancement, visualizer ui 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Version info:** 11 | 12 | - Visual Studio version: 13 | - Visualizer location: [VS install path, or Documents subfolder] 14 | - Version: [GitHub release version or project commit] 15 | 16 | Note that the visualizer location and version can be found at the bottom of the **Settings** popup (click the gear icon). 17 | 18 | **Is your feature request related to a problem? Please describe.** 19 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain the problem you are trying to solve. 23 | 24 | **Describe the solution you'd like** 25 | A clear and concise description of what you want to happen. 26 | 27 | **Describe alternatives you've considered** 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | **Additional context** 31 | Add any other context or screenshots about the feature request here. 32 | -------------------------------------------------------------------------------- /Visualizer/VisualizerWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | using ExpressionTreeVisualizer.Serialization; 4 | 5 | namespace ExpressionTreeVisualizer { 6 | public partial class VisualizerWindow : VisualizerWindowBase { 7 | protected override void TransformConfig(Config config, object parameter) => 8 | config.Path = parameter switch { 9 | ExpressionNodeDataViewModel nodeVM => nodeVM.Model.FullPath, 10 | _ => throw new ArgumentException("Unrecognized parameter type."), 11 | }; 12 | 13 | public VisualizerWindow() => InitializeComponent(); 14 | 15 | protected override (object windowContext, object optionsContext, Config config) GetViewState(object response, ICommand? OpenInNewWindow) { 16 | if (response is not VisualizerData vd) { 17 | throw new InvalidOperationException("Unrecognized response type; expected VisualizerData."); 18 | } 19 | 20 | return ( 21 | new VisualizerDataViewModel(vd, OpenInNewWindow, Periscope.Visualizer.CopyWatchExpression), 22 | new ConfigViewModel(vd.Config), 23 | vd.Config 24 | ); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /UI/ViewModels/EndNodeGroupViewModel.cs: -------------------------------------------------------------------------------- 1 | using ExpressionTreeVisualizer.Serialization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using ZSpitz.Util; 10 | using ZSpitz.Util.Wpf; 11 | 12 | namespace ExpressionTreeVisualizer { 13 | public class EndNodeGroupViewModel : Selectable { 14 | public readonly ReadOnlyCollection Nodes; 15 | 16 | public EndNodeGroupViewModel(EndNodeData model, IEnumerable nodes) : base(model) { 17 | Nodes = nodes.ToReadOnlyCollection(); 18 | 19 | void handler(object? s, PropertyChangedEventArgs e) { 20 | if (e.PropertyName != nameof(IsSelected)) { return; } 21 | IsSelected = Nodes.Any(x => x.IsSelected); 22 | } 23 | 24 | foreach (var node in Nodes) { 25 | node.PropertyChanged += handler; 26 | } 27 | } 28 | 29 | //public void IsSelectedAndChildren(bool selected) { 30 | // foreach (var node in Nodes) { 31 | // node.IsSelected = selected; 32 | // } 33 | //} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /UI/ViewModels/ExpressionNodeDataViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ZSpitz.Util; 7 | using ZSpitz.Util.Wpf; 8 | using ExpressionTreeVisualizer.Serialization; 9 | using System.Windows.Input; 10 | 11 | namespace ExpressionTreeVisualizer { 12 | public class ExpressionNodeDataViewModel : Selectable { 13 | public List Children { get; } 14 | 15 | public ExpressionNodeDataViewModel(ExpressionNodeData model, List allNodes, ICommand? openInNewWindow = null, RelayCommand? copyWatchExpression = null) : base(model) { 16 | if (model.EnableValueInNewWindow) { OpenInNewWindow = openInNewWindow; } 17 | CopyWatchExpression = copyWatchExpression; 18 | 19 | Children = model.Children.Select(x => { 20 | var vm = new ExpressionNodeDataViewModel(x, allNodes, openInNewWindow, CopyWatchExpression); 21 | allNodes.Add(vm); 22 | return vm; 23 | }).ToList(); 24 | } 25 | 26 | public void ClearSelection(params ExpressionNodeDataViewModel[] toSelect) { 27 | IsSelected = this.In(toSelect); 28 | Children.ForEach(x => x.ClearSelection(toSelect)); 29 | } 30 | 31 | public ICommand? OpenInNewWindow { get; private set; } 32 | public RelayCommand? CopyWatchExpression { get; private set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Serialization/Config.cs: -------------------------------------------------------------------------------- 1 | #if VISUALIZER_DEBUGGEE 2 | using Periscope.Debuggee; 3 | #endif 4 | using System; 5 | using static ExpressionTreeToString.RendererNames; 6 | 7 | namespace ExpressionTreeVisualizer.Serialization { 8 | [Serializable] 9 | #if VISUALIZER_DEBUGGEE 10 | public class Config : Periscope.Debuggee.ConfigBase { 11 | #else 12 | public class Config { 13 | #endif 14 | private string formatter = CSharp; 15 | public string Formatter { 16 | get => formatter; 17 | set { 18 | formatter = value; 19 | if (value == VisualBasic || value == CSharp) { 20 | Language = value; 21 | } 22 | } 23 | } 24 | 25 | public string Language { get; set; } = CSharp; 26 | public string? Path { get; set; } 27 | 28 | #if VISUALIZER_DEBUGGEE 29 | public override ConfigDiffStates Diff(Config baseline) => 30 | ( 31 | baseline.Formatter == Formatter && 32 | baseline.Language == Language && 33 | baseline.Path == Path 34 | ) ? ConfigDiffStates.NoAction : ConfigDiffStates.NeedsTransfer; 35 | 36 | public override Config Clone() => 37 | #else 38 | public Config Clone() => 39 | #endif 40 | new() { 41 | Formatter = Formatter, 42 | Language = Language, 43 | Path = Path 44 | }; 45 | 46 | public void Deconstruct(out string formatter, out string language, out string? path) { 47 | formatter = Formatter; 48 | language = Language; 49 | path = Path; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /UI/UI.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | e1d67370-17ac-47fc-89bc-34b20b3bd6e7 7 | 8 | 9 | ExpressionTreeVisualizer 10 | 11 | 12 | 13 | 14 | SettingsControl.xaml 15 | 16 | 17 | 18 | 19 | 20 | 21 | VisualizerDataControl.xaml 22 | 23 | 24 | 25 | 26 | Designer 27 | MSBuild:Compile 28 | 29 | 30 | MSBuild:Compile 31 | Designer 32 | 33 | 34 | -------------------------------------------------------------------------------- /UI/Converters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using ZSpitz.Util; 5 | using ZSpitz.Util.Wpf; 6 | using static System.Windows.Visibility; 7 | 8 | namespace ExpressionTreeVisualizer { 9 | public class RootConverter : ReadOnlyConverterBase { 10 | public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) => new[] { value }; 11 | } 12 | 13 | public class ConditionalFormatConverter : ReadOnlyConverterBase { 14 | public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { 15 | var sValue = value as string; 16 | if (sValue.IsNullOrWhitespace()) { return value; } 17 | return value.Formatted((string)parameter); 18 | } 19 | } 20 | 21 | public class TitleConverter : ReadOnlyMultiConverterBase { 22 | public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { 23 | var formatter = values[0] as string; 24 | var language = values[1] as string; 25 | var path = values[2] as string; 26 | var parts = new List<(string? name, string? value)> { 27 | {"Formatter", formatter } 28 | }; 29 | if (formatter != language) { 30 | parts.Add("Language", language); 31 | } 32 | if (!path.IsNullOrWhitespace()) { 33 | parts.Add("Path", path); 34 | } 35 | return parts.SelectT((name, val) => $"{name}: {val}").Joined(", "); 36 | } 37 | } 38 | 39 | public class NotNullToVisibilityConverter : ReadOnlyConverterBase { 40 | public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) => 41 | value is null ? Collapsed : Visible; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Visualizer/Visualizer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | ExpressionTreeVisualizer 6 | ExpressionTreeVisualizer 7 | 9.0 8 | enable 9 | true 10 | true 11 | 0.0.0 12 | 13 | 14 | 15 | 16 | ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll 17 | false 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.7.{build} 2 | image: Visual Studio 2019 3 | configuration: Debug 4 | init: 5 | - git config --global core.autocrlf false 6 | branches: 7 | only: 8 | - master 9 | skip_tags: true 10 | only_commits: 11 | files: 12 | - Debuggee/ 13 | - Package/ 14 | - Serialization/ 15 | - UI/ 16 | - Visualizer/ 17 | - appveyor.yml 18 | 19 | dotnet_csproj: 20 | patch: true 21 | file: '**\*.csproj' 22 | version: '{version}' 23 | package_version: '{version}' 24 | assembly_version: '{version}' 25 | file_version: '{version}' 26 | informational_version: '{version}' 27 | 28 | before_build: 29 | - cmd: dotnet restore 30 | 31 | install: 32 | - cmd: git submodule update --init --recursive 33 | 34 | build: 35 | project: ExpressionTreeVisualizer.sln 36 | 37 | matrix: 38 | fast_finish: true 39 | 40 | after_build: 41 | - 7z a %APPVEYOR_BUILD_FOLDER%\ExpressionTreeVisualizer.2017.zip -r %APPVEYOR_BUILD_FOLDER%\Debuggee\bin\Debug\net2.0\*.dll" 42 | - 7z a %APPVEYOR_BUILD_FOLDER%\ExpressionTreeVisualizer.2017.zip -r %APPVEYOR_BUILD_FOLDER%\Visualizer\bin\Debug\net472\*.dll* 43 | - 7z a %APPVEYOR_BUILD_FOLDER%\ExpressionTreeVisualizer.2019.zip -r %APPVEYOR_BUILD_FOLDER%\Debuggee\bin\Debug\*.dll* 44 | - 7z a %APPVEYOR_BUILD_FOLDER%\ExpressionTreeVisualizer.2019.zip -r %APPVEYOR_BUILD_FOLDER%\Visualizer\bin\Debug\net472\*.dll* 45 | 46 | artifacts: 47 | - path: ExpressionTreeVisualizer.2019.zip 48 | type: File 49 | name: visualizer 50 | 51 | - path: ExpressionTreeVisualizer.2017.zip 52 | type: File 53 | name: visualizer 54 | 55 | - path: '**\*.nupkg' 56 | type: NuGetPackage 57 | 58 | deploy: 59 | - provider: GitHub 60 | description: '' 61 | auth_token: 62 | secure: NgxEvJd/ApBpuz6rwCNDfOI5c6nAvBIabLMj1vAtzuV4ozyaVDfjRJKa7WeJK2Ri 63 | artifact: visualizer 64 | 65 | - provider: NuGet 66 | api_key: 67 | secure: ljT3pyH+AZJ5Jy2/fiKA6riLob9Rrl6v99ORudC6KWVs5jOvBicZ861/UCEGWMEJ 68 | artifact: /.*\.nupkg/ -------------------------------------------------------------------------------- /Package/Package.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472;netcoreapp3.1;net5.0-windows 4 | ExpressionTreeVisualizer 5 | ExpressionTreeVisualizer.UI 6 | 9.0 7 | enable 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | Zev Spitz 15 | 16 | ExpressionTreeVisualizer.UI 17 | UI pieces for displaying expression trees 18 | Copyright (c) 2020 Zev Spitz 19 | MIT 20 | https://github.com/zspitz/ExpressionTreeVisualizer 21 | https://github.com/zspitz/ExpressionTreeVisualizer 22 | git 23 | .net csharp expression-trees visualizer 24 | Initial release 25 | 0.1.0 26 | true 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Debuggee/Debuggee.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472;netstandard2.0;netcoreapp2.0 5 | ExpressionTreeVisualizer.Debuggee 6 | ExpressionTreeVisualizer.Debuggee 7 | 9.0 8 | enable 9 | true 10 | VISUALIZER_DEBUGGEE 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | false 19 | false 20 | bin/$(Configuration)/net2.0/ 21 | 22 | 23 | 24 | false 25 | false 26 | bin/$(Configuration)/netcoreapp/ 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll 35 | false 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Visualizer/Visualizer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using ExpressionTreeVisualizer.Serialization; 3 | using Microsoft.VisualStudio.DebuggerVisualizers; 4 | using Periscope; 5 | 6 | [assembly: DebuggerVisualizer( 7 | visualizer: typeof(ExpressionTreeVisualizer.Visualizer), 8 | visualizerObjectSource: typeof(ExpressionTreeVisualizer.VisualizerDataObjectSource), 9 | Target = typeof(System.Linq.Expressions.Expression), 10 | Description = "Expression Tree Visualizer")] 11 | 12 | [assembly: DebuggerVisualizer( 13 | visualizer: typeof(ExpressionTreeVisualizer.Visualizer), 14 | visualizerObjectSource: typeof(ExpressionTreeVisualizer.VisualizerDataObjectSource), 15 | Target = typeof(System.Linq.Expressions.ElementInit), 16 | Description = "Expression Tree Visualizer")] 17 | 18 | [assembly: DebuggerVisualizer( 19 | visualizer: typeof(ExpressionTreeVisualizer.Visualizer), 20 | visualizerObjectSource: typeof(ExpressionTreeVisualizer.VisualizerDataObjectSource), 21 | Target = typeof(System.Linq.Expressions.MemberBinding), 22 | Description = "Expression Tree Visualizer")] 23 | 24 | [assembly: DebuggerVisualizer( 25 | visualizer: typeof(ExpressionTreeVisualizer.Visualizer), 26 | visualizerObjectSource: typeof(ExpressionTreeVisualizer.VisualizerDataObjectSource), 27 | Target = typeof(System.Linq.Expressions.SwitchCase), 28 | Description = "Expression Tree Visualizer")] 29 | 30 | [assembly: DebuggerVisualizer( 31 | visualizer: typeof(ExpressionTreeVisualizer.Visualizer), 32 | visualizerObjectSource: typeof(ExpressionTreeVisualizer.VisualizerDataObjectSource), 33 | Target = typeof(System.Linq.Expressions.CatchBlock), 34 | Description = "Expression Tree Visualizer")] 35 | 36 | [assembly: DebuggerVisualizer( 37 | visualizer: typeof(ExpressionTreeVisualizer.Visualizer), 38 | visualizerObjectSource: typeof(ExpressionTreeVisualizer.VisualizerDataObjectSource), 39 | Target = typeof(System.Linq.Expressions.LabelTarget), 40 | Description = "Expression Tree Visualizer")] 41 | 42 | namespace ExpressionTreeVisualizer { 43 | public abstract class VisualizerWindowBase : VisualizerWindowBase { } 44 | 45 | public class Visualizer : VisualizerBase { 46 | static Visualizer() => Periscope.Debuggee.SubfolderAssemblyResolver.Hook("ExpressionTreeVisualizer"); 47 | public Visualizer() : base(new GithubProjectInfo("zspitz", "expressiontreevisualizer")) { } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /ExpressionTreeVisualizer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29609.76 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Debuggee", "Debuggee\Debuggee.csproj", "{E865E70A-0959-4835-8C64-95A0D9815868}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Package", "Package\Package.csproj", "{7A8DE3FA-8761-45DE-9465-53D2C778F355}" 9 | EndProject 10 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Serialization", "Serialization\Serialization.shproj", "{9365B962-F17C-451C-92EB-F4FED9C6C84E}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests.Visualizer\Tests.csproj", "{FB1502D6-D312-4381-A000-31CBC36599A4}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Visualizer", "Visualizer\Visualizer.csproj", "{B77B6DF5-33D7-4A81-9E17-E32731EFD4AB}" 15 | EndProject 16 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UI", "UI\UI.shproj", "{E1D67370-17AC-47FC-89BC-34B20B3BD6E7}" 17 | EndProject 18 | Global 19 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 20 | Serialization\Serialization.projitems*{7a8de3fa-8761-45de-9465-53d2c778f355}*SharedItemsImports = 5 21 | UI\UI.projitems*{7a8de3fa-8761-45de-9465-53d2c778f355}*SharedItemsImports = 5 22 | Serialization\Serialization.projitems*{9365b962-f17c-451c-92eb-f4fed9c6c84e}*SharedItemsImports = 13 23 | UI\UI.projitems*{b77b6df5-33d7-4a81-9e17-e32731efd4ab}*SharedItemsImports = 5 24 | UI\UI.projitems*{e1d67370-17ac-47fc-89bc-34b20b3bd6e7}*SharedItemsImports = 13 25 | Serialization\Serialization.projitems*{e865e70a-0959-4835-8c64-95a0d9815868}*SharedItemsImports = 5 26 | EndGlobalSection 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {E865E70A-0959-4835-8C64-95A0D9815868}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E865E70A-0959-4835-8C64-95A0D9815868}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E865E70A-0959-4835-8C64-95A0D9815868}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {E865E70A-0959-4835-8C64-95A0D9815868}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {7A8DE3FA-8761-45DE-9465-53D2C778F355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {7A8DE3FA-8761-45DE-9465-53D2C778F355}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {7A8DE3FA-8761-45DE-9465-53D2C778F355}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {7A8DE3FA-8761-45DE-9465-53D2C778F355}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {FB1502D6-D312-4381-A000-31CBC36599A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {FB1502D6-D312-4381-A000-31CBC36599A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {FB1502D6-D312-4381-A000-31CBC36599A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {FB1502D6-D312-4381-A000-31CBC36599A4}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {B77B6DF5-33D7-4A81-9E17-E32731EFD4AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {B77B6DF5-33D7-4A81-9E17-E32731EFD4AB}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {B77B6DF5-33D7-4A81-9E17-E32731EFD4AB}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {B77B6DF5-33D7-4A81-9E17-E32731EFD4AB}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {8FEB0977-A0E0-4FA7-B9C2-75772CF79FB5} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expression Tree Visualizer 2 | 3 | [![AppVeyor build status](https://img.shields.io/appveyor/ci/zspitz/expressiontreevisualizer?style=flat&max-age=86400)](https://ci.appveyor.com/project/zspitz/expressiontreevisualizer) [![Tests](https://img.shields.io/appveyor/tests/zspitz/expressiontreevisualizer?compact_message&style=flat&max-age=86400)](https://ci.appveyor.com/project/zspitz/expressiontreevisualizer) [![GitHub Release](https://img.shields.io/github/release/zspitz/expressiontreevisualizer.svg?style=flat&max-age=86400)](https://github.com/zspitz/ExpressionTreeVisualizer/releases) [![NuGet status of UI components package](https://img.shields.io/nuget/v/ExpressionTreeVisualizer.UI.svg?style=flat&max-age=86400)](https://www.nuget.org/packages/ExpressionTreeVisualizer.UI/) 4 | 5 | This project provides a custom debugging visualizer for expression trees, that can be used while debugging with Visual Studio (on Windows). The UI contains: 6 | 7 | 1. a graphical treeview of the expression tree structure, 8 | 2. [source code representation of the tree](https://github.com/zspitz/ExpressionTreeToString), and 9 | 3. end nodes -- nodes in the tree which are not composed of other expressions: parameters, closure variables, constants and default values 10 | 11 | ![Screenshot](screenshot-01.png) 12 | 13 | There are [multiple renderers](https://github.com/zspitz/ExpressionTreeVisualizer/wiki#settings) available: 14 | 15 | ![Language switch](formatter-switch.gif) 16 | 17 | Selection syncing: 18 | 19 | * when selecting from the tree: 20 | 21 | ![Selection sync from tree](sync-from-tree.gif) 22 | 23 | * from source code: 24 | 25 | ![Selection sync from source code](sync-from-code.gif) 26 | 27 | * and from end nodes: 28 | 29 | ![Selection sync from end nodes](sync-from-endnodes.gif) 30 | 31 | For more information, see the [wiki](https://github.com/zspitz/ExpressionTreeVisualizer/wiki). 32 | 33 | The UI components are also available as a [NuGet package](https://www.nuget.org/packages/ExpressionTreeVisualizer.UI/) (targeting .NET Core 3.1 / .NET Framework 4.7.2), which you can use in your own projects (demo available [here](https://github.com/zspitz/ExpressionTreeVisualizer.UIDemo)). 34 | 35 | ## Requirements / Supports 36 | 37 | The visualizer been tested with VS 2019 and VS 2017; theoretically the 2017 version should work with previous versions of VS. 38 | 39 | It supports .NET Core, ,NET Framework, and any .NET Standard 2.0 implementation. (Note that .NET Framework < 4.7 [may have issues in VS 2017](https://github.com/zspitz/ANTLR4ParseTreeVisualizer/issues/51).) 40 | 41 | ## Installation 42 | 43 | From the [project's Releases page](https://github.com/zspitz/ExpressionTreeVisualizer/releases), download the ZIP file corresponding to your Visual Studio version (2019 or 2017). Then: 44 | 45 | 1. [Unblock the ZIP file](https://github.com/zspitz/ExpressionTreeVisualizer/wiki/Troubleshooting-visualizer-installation). 46 | 2. Extract the files to the visualizers folder, preserving the [required folder structure](https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-install-a-visualizer?view=vs-2019#to-install-a-visualizer-for-visual-studio-2019). 47 | 48 | ## Uninstallation 49 | 50 | To delete the visualizer: 51 | 52 | 1. Navigate to the visualizer location. This can be done by clicking on the **About** link, and then clicking on the **ExpressionTreeVisualizer.dll** link. 53 | 2. Delete all files and folders whose name starts with `ExpressionTreeVisualizer`. You can find these by typing `name:ExpressionTreeVisualizer` in the Windows search box. 54 | 55 | ## Contributing / Feedback 56 | 57 | * Test the visualizer. (The significance of this kind of contribution cannot be overestimated.) 58 | * Suggest ideas and enhancements (via [issues](https://github.com/zspitz/ExpressionTreeVisualizer/issues/new/choose)) 59 | * Notify about bugs (via [issues](https://github.com/zspitz/ExpressionTreeVisualizer/issues/new/choose)) 60 | * Provide screenshots demonstrating different features of the visualizer (via [issues](https://github.com/zspitz/ExpressionTreeVisualizer/issues/new/choose)), to be included in the README or wiki pages 61 | * Star the project 62 | * Spread the word 63 | * Provide feedback to Microsoft about [these limitations to the visualizer API](https://github.com/zspitz/ExpressionToString/wiki/External-issues) 64 | 65 | ## Credits 66 | 67 | * John M. Wright's series on [writing debugger visualizers](https://wrightfully.com/writing-a-readonly-debugger-visualizer) 68 | * Multiple-selection treeview is provided by [MultiSelectTreeView](https://github.com/ygoe/MultiSelectTreeView) 69 | * [ReadableExpressions](https://github.com/agileobjects/ReadableExpressions) for inspiration 70 | * [Greenshot](https://getgreenshot.org/) and [ScreenToGIF](https://www.screentogif.com/) for the screenshots 71 | -------------------------------------------------------------------------------- /UI/VisualizerDataControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using static System.Windows.SystemColors; 7 | using ZSpitz.Util; 8 | 9 | namespace ExpressionTreeVisualizer { 10 | public partial class VisualizerDataControl { 11 | public VisualizerDataControl() { 12 | InitializeComponent(); 13 | 14 | // When a control loses focus, it should look no different from when it had the focus (e.g. selection color) 15 | Resources[InactiveSelectionHighlightBrushKey] = HighlightBrush; 16 | Resources[InactiveSelectionHighlightTextBrushKey] = HighlightTextBrush; 17 | 18 | Loaded += (s, e) => { 19 | // HACK: without the next two lines the selection will only be visible if the textbox currently has the focus 20 | source.LostFocus += (s1, e1) => e1.Handled = true; 21 | source.Focus(); 22 | 23 | // except if the user makes the window wider, then narrower, the tree doesn't contract 24 | //tree.LayoutUpdated += (s1, e1) => { 25 | // // prevents the whole window from shrinking when nodes are collapsed 26 | // if (tree.ActualWidth > tree.MinWidth) { tree.MinWidth = tree.ActualWidth; } 27 | // if (tree.ActualHeight > tree.MinHeight) { tree.MinHeight = tree.ActualHeight; } 28 | //}; 29 | 30 | }; 31 | } 32 | 33 | private void HelpContextMenu_Loaded(object sender, RoutedEventArgs e) { 34 | var menu = (MenuItem)sender; 35 | var node = (ExpressionNodeDataViewModel)menu.DataContext; 36 | 37 | if (menu.Items.Any()) { return; } 38 | 39 | var listData = new List<(string header, string url)>(); 40 | 41 | if (node.Model.ParentProperty is { }) { 42 | var (@namespace, typename, propertyname) = node.Model.ParentProperty.Value; 43 | listData.Add( 44 | $"Property: {typename}.{propertyname}", 45 | $"{baseUrl}{new[] { @namespace, typename, propertyname }.Joined(".")}" 46 | ); 47 | } 48 | 49 | addSeparator(); 50 | 51 | if (node.Model.NodeTypesParts is { }) { 52 | foreach (var (@namespace, typename, membername) in node.Model.NodeTypesParts) { 53 | listData.Add( 54 | $"Node type: {typename}.{membername}", 55 | $"{baseUrl}{new[] { @namespace, typename }.Joined(".")}#{new[] { @namespace.Replace(".", "_"), typename, membername }.Joined("_")}" 56 | ); 57 | } 58 | } 59 | 60 | addSeparator(); 61 | 62 | if (node.Model.BaseTypes is { }) { 63 | node.Model.BaseTypes.SelectT((@namespace, typename) => ( 64 | $"Base type: {typename}", 65 | $"{baseUrl}{@namespace}.{typename.Replace("~", "-")}" 66 | )).AddRangeTo(listData); 67 | } 68 | 69 | addSeparator(); 70 | 71 | if (node.Model.FactoryMethodNames is { }) { 72 | node.Model.FactoryMethodNames.Select(methodName => ( 73 | $"Factory method: {methodName}", 74 | $"{baseUrl}system.linq.expressions.expression.{methodName}" 75 | )).AddRangeTo(listData); 76 | } 77 | 78 | if (listData.Any() && listData.Last().header == "---") { 79 | listData.RemoveLast(); 80 | } 81 | 82 | foreach (var (header, url) in listData) { 83 | if (header == "---") { 84 | menu.Items.Add(new Separator()); 85 | continue; 86 | } 87 | 88 | var mi = new MenuItem() { 89 | Header = header, 90 | DataContext = url 91 | }; 92 | mi.Click += static (s1, e1) => { 93 | // we need to explicitly set UseShellExecute to true -- https://github.com/dotnet/runtime/issues/28005 94 | var psi = new ProcessStartInfo { 95 | FileName = (string)((MenuItem)s1).DataContext, 96 | UseShellExecute = true 97 | }; 98 | Process.Start(psi); 99 | }; 100 | menu.Items.Add(mi); 101 | } 102 | 103 | void addSeparator() { 104 | if (listData.None() || listData.Last().header == "---") { return; } 105 | listData.Add("---", ""); 106 | } 107 | } 108 | 109 | private const string baseUrl = "https://docs.microsoft.com/dotnet/api/"; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Serialization/NullableAttributes.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | // This was copied from https://github.com/dotnet/coreclr/blob/60f1e6265bd1039f023a82e0643b524d6aaf7845/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/NullableAttributes.cs 6 | // and updated to have the scope of the attributes be internal. 7 | 8 | namespace System.Diagnostics.CodeAnalysis { 9 | /// Specifies that null is allowed as an input even if the corresponding type disallows it. 10 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] 11 | internal sealed class AllowNullAttribute : Attribute { } 12 | 13 | /// Specifies that null is disallowed as an input even if the corresponding type allows it. 14 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] 15 | internal sealed class DisallowNullAttribute : Attribute { } 16 | 17 | /// Specifies that an output may be null even if the corresponding type disallows it. 18 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] 19 | internal sealed class MaybeNullAttribute : Attribute { } 20 | 21 | /// Specifies that an output will not be null even if the corresponding type allows it. 22 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] 23 | internal sealed class NotNullAttribute : Attribute { } 24 | 25 | /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. 26 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 27 | internal sealed class MaybeNullWhenAttribute : Attribute { 28 | /// Initializes the attribute with the specified return value condition. 29 | /// 30 | /// The return value condition. If the method returns this value, the associated parameter may be null. 31 | /// 32 | public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; 33 | 34 | /// Gets the return value condition. 35 | public bool ReturnValue { get; } 36 | } 37 | 38 | /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. 39 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 40 | internal sealed class NotNullWhenAttribute : Attribute { 41 | /// Initializes the attribute with the specified return value condition. 42 | /// 43 | /// The return value condition. If the method returns this value, the associated parameter will not be null. 44 | /// 45 | public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; 46 | 47 | /// Gets the return value condition. 48 | public bool ReturnValue { get; } 49 | } 50 | 51 | /// Specifies that the output will be non-null if the named parameter is non-null. 52 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] 53 | internal sealed class NotNullIfNotNullAttribute : Attribute { 54 | /// Initializes the attribute with the associated parameter name. 55 | /// 56 | /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. 57 | /// 58 | public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; 59 | 60 | /// Gets the associated parameter name. 61 | public string ParameterName { get; } 62 | } 63 | 64 | /// Applied to a method that will never return under any circumstance. 65 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 66 | internal sealed class DoesNotReturnAttribute : Attribute { } 67 | 68 | /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. 69 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 70 | internal sealed class DoesNotReturnIfAttribute : Attribute { 71 | /// Initializes the attribute with the specified parameter value. 72 | /// 73 | /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to 74 | /// the associated parameter matches this value. 75 | /// 76 | public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; 77 | 78 | /// Gets the condition parameter value. 79 | public bool ParameterValue { get; } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /generated test data.txt 263 | 264 | # Because the plaground projects (start with _) should not be in the repo 265 | _* 266 | # neither should the solution that references them 267 | /ExpressionTreeVisualizer.Dev.sln 268 | 269 | /PostBuild 270 | -------------------------------------------------------------------------------- /UI/ViewModels/VisualizerDataViewModel.cs: -------------------------------------------------------------------------------- 1 | using ExpressionTreeVisualizer.Serialization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using static ExpressionTreeVisualizer.Serialization.EndNodeTypes; 6 | using ZSpitz.Util; 7 | using ZSpitz.Util.Wpf; 8 | using System.Windows.Input; 9 | 10 | namespace ExpressionTreeVisualizer { 11 | public class VisualizerDataViewModel : ViewModelBase { 12 | public ExpressionNodeDataViewModel Root { get; } 13 | 14 | public List AllNodes { get; } 15 | 16 | public List Constants { get; } 17 | public List Parameters { get; } 18 | public List ClosedVars { get; } 19 | public List Defaults { get; } 20 | 21 | private readonly List allGroups; 22 | 23 | public VisualizerDataViewModel(VisualizerData model, ICommand? openInNewWindow=null, RelayCommand? copyWatchExpression = null) : base(model) { 24 | AllNodes = new List(); 25 | Root = new ExpressionNodeDataViewModel(model.Root, AllNodes, openInNewWindow, copyWatchExpression); 26 | 27 | var grouped = 28 | AllNodes 29 | .Where(x => x.Model.EndNodeType != null) 30 | .GroupBy( 31 | x => x.Model.EndNodeType!.Value, 32 | (endNodeType, grp) => ( 33 | endNodeType, 34 | grp.GroupBy( 35 | x => x.Model.EndNodeData, 36 | (endNodeData, grp1) => new EndNodeGroupViewModel(endNodeData, grp1) 37 | ) 38 | ) 39 | ) 40 | .SelectMany(x => x.Item2.Select(y => (x.endNodeType, y))) 41 | .ToLookup(x => x.endNodeType, x => x.y); 42 | 43 | Constants = grouped[Constant].ToList(); 44 | Parameters = grouped[Parameter].ToList(); 45 | ClosedVars = grouped[ClosedVar].ToList(); 46 | Defaults = grouped[Default].ToList(); 47 | 48 | allGroups = grouped.SelectMany().ToList(); 49 | 50 | UpdateSelection = new RelayCommand(sender => { 51 | if (inUpdateSelection) { return; } 52 | 53 | inUpdateSelection = true; 54 | 55 | switch (sender) { 56 | case string s: 57 | var selected = FindNodeBySpan(SourceSelectionStart, SourceSelectionLength); 58 | Root.ClearSelection(selected); 59 | break; 60 | case ExpressionNodeDataViewModel node: 61 | Root.ClearSelection(node); 62 | (SourceSelectionStart, SourceSelectionLength) = node.Model.Span; 63 | break; 64 | case EndNodeGroupViewModel group: 65 | allGroups.Where(x => x != group).ForEach(x => x.IsSelected = false); 66 | Root.ClearSelection(group.Nodes.ToArray()); 67 | (SourceSelectionStart, SourceSelectionLength) = group.Nodes.First().Model.Span; 68 | break; 69 | case null: 70 | // previously selected group was unselected (presumably the only way for null to be passed in) 71 | Root.ClearSelection(); 72 | break; 73 | default: 74 | throw new InvalidOperationException("Selection update from unknown part of viewmodel"); 75 | } 76 | inUpdateSelection = false; 77 | }); 78 | } 79 | 80 | private bool inUpdateSelection; 81 | 82 | public ExpressionNodeDataViewModel FindNodeBySpan(int start, int length) { 83 | var end = start + length; 84 | //if (start < NodeData.Span.start || end > NodeData.SpanEnd) { throw new ArgumentOutOfRangeException(); } 85 | var current = Root; 86 | while (true) { 87 | var child = 88 | (start, length) == (0,0) ? 89 | current.Children.FirstOrDefault(x => x.Model.Span == (0,0)) : 90 | current.Children.SingleOrDefault(x => x.Model.Span.start <= start && x.Model.SpanEnd >= end); 91 | if (child == null) { break; } 92 | current = child; 93 | } 94 | return current; 95 | } 96 | 97 | private int sourceSelectionStart; 98 | public int SourceSelectionStart { 99 | get => sourceSelectionStart; 100 | set => NotifyChanged(ref sourceSelectionStart, value); 101 | } 102 | 103 | private int sourceSelectionLength; 104 | 105 | public int SourceSelectionLength { 106 | get => sourceSelectionLength; 107 | set => NotifyChanged(ref sourceSelectionLength, value); 108 | } 109 | 110 | private int sourceSelectionEnd => 111 | sourceSelectionLength == 0 ? 112 | sourceSelectionStart : 113 | sourceSelectionStart + sourceSelectionLength - 1; 114 | 115 | public RelayCommand UpdateSelection { get; } 116 | 117 | public string WindowTitle { 118 | get { 119 | var (formatter, language, path) = Model.Config; 120 | var parts = new List<(string? name, string? value)> { 121 | {"Formatter", formatter } 122 | }; 123 | if (formatter != language) { 124 | parts.Add("Language", language); 125 | } 126 | if (!path.IsNullOrWhitespace()) { 127 | parts.Add("Path", path); 128 | } 129 | return "Expression Tree Visualizer — " + parts.SelectT((name, val) => $"{name}: {val}").Joined(", "); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | 4 | [*.{cs,vb}] 5 | indent_size = 4 6 | indent_style = space 7 | end_of_line = crlf 8 | insert_final_newline = true 9 | 10 | dotnet_separate_import_directive_groups = false 11 | dotnet_sort_system_directives_first = false 12 | 13 | dotnet_style_qualification_for_event = false:suggestion 14 | dotnet_style_qualification_for_field = false:suggestion 15 | dotnet_style_qualification_for_method = false:suggestion 16 | dotnet_style_qualification_for_property = false:suggestion 17 | 18 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 19 | dotnet_style_predefined_type_for_member_access = true:suggestion 20 | 21 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent 22 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent 23 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 24 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent 25 | 26 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 27 | 28 | dotnet_style_coalesce_expression = true:suggestion 29 | dotnet_style_collection_initializer = true:suggestion 30 | dotnet_style_explicit_tuple_names = true:suggestion 31 | dotnet_style_null_propagation = true:suggestion 32 | dotnet_style_object_initializer = true:suggestion 33 | dotnet_style_operator_placement_when_wrapping = end_of_line 34 | dotnet_style_prefer_auto_properties = true:suggestion 35 | dotnet_style_prefer_compound_assignment = true:suggestion 36 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 37 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion 38 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 39 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 40 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 41 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 42 | dotnet_style_prefer_simplified_interpolation = true:suggestion 43 | 44 | 45 | # Naming rules 46 | 47 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 48 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 49 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 50 | 51 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 52 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 53 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 54 | 55 | dotnet_naming_rule.non_private_members_should_be_pascal_case.severity = suggestion 56 | dotnet_naming_rule.non_private_members_should_be_pascal_case.symbols = non_private_members 57 | dotnet_naming_rule.non_private_members_should_be_pascal_case.style = pascal_case 58 | 59 | dotnet_naming_rule.private_members_should_be_pascal_case.severity = suggestion 60 | dotnet_naming_rule.private_members_should_be_pascal_case.symbols = private_members 61 | dotnet_naming_rule.private_members_should_be_pascal_case.style = camel_case 62 | 63 | dotnet_naming_ruke.non_private_fields_should_be_canel_case.severity = suggestion 64 | dotnet_naming_ruke.non_private_fields_should_be_canel_case.symbols = non_private_fields 65 | dotnet_naming_ruke.non_private_fields_should_be_canel_case.style = camel_case 66 | 67 | 68 | # Symbols for use with naming rules 69 | 70 | dotnet_naming_symbols.interface.applicable_kinds = interface 71 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 72 | dotnet_naming_symbols.interface.required_modifiers = 73 | 74 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum, delegate 75 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 76 | dotnet_naming_symbols.types.required_modifiers = 77 | 78 | dotnet_naming_symbols.non_private_members.applicable_kinds = property, method, event 79 | dotnet_naming_symbols.non_private_members.applicable_accessibilities = public, internal, protected, private_protected, protected_internal 80 | 81 | dotnet_naming_symbols.private_members.applicable_kinds = property, method, field, event 82 | dotnet_naming_symbols.private_members.applicable_accessibilities = private 83 | 84 | dotnet_naming_symbols.non_private_fields.applicable_kinds = field 85 | dotnet_naming_symbols.non_private_fields.applicable_accessibilities = internal, protected, private_protected, protected_internal 86 | 87 | 88 | # Naming styles 89 | 90 | dotnet_naming_style.pascal_case.required_prefix = 91 | dotnet_naming_style.pascal_case.required_suffix = 92 | dotnet_naming_style.pascal_case.word_separator = 93 | dotnet_naming_style.pascal_case.capitalization = pascal_case 94 | 95 | dotnet_naming_style.begins_with_i.required_prefix = I 96 | dotnet_naming_style.begins_with_i.required_suffix = 97 | dotnet_naming_style.begins_with_i.word_separator = 98 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 99 | 100 | dotnet_naming_style.camel_case.required_prefix = 101 | dotnet_naming_style.camel_case.required_suffix = 102 | dotnet_naming_style.camel_case.word_separator = 103 | dotnet_naming_style.camel_case.capitalization = camel_case 104 | 105 | 106 | [*.cs] 107 | csharp_new_line_before_catch = false 108 | csharp_new_line_before_else = false 109 | csharp_new_line_before_finally = false 110 | csharp_new_line_before_members_in_anonymous_types = true 111 | csharp_new_line_before_members_in_object_initializers = true 112 | csharp_new_line_before_open_brace = none 113 | csharp_new_line_between_query_expression_clauses = true 114 | 115 | csharp_indent_block_contents = true 116 | csharp_indent_braces = false 117 | csharp_indent_case_contents = true 118 | csharp_indent_case_contents_when_block = true 119 | csharp_indent_labels = one_less_than_current 120 | csharp_indent_switch_labels = true 121 | 122 | csharp_space_after_cast = false 123 | csharp_space_after_colon_in_inheritance_clause = true 124 | csharp_space_after_comma = true 125 | csharp_space_after_dot = false 126 | csharp_space_after_keywords_in_control_flow_statements = true 127 | csharp_space_after_semicolon_in_for_statement = true 128 | csharp_space_around_binary_operators = before_and_after 129 | csharp_space_around_declaration_statements = false 130 | csharp_space_before_colon_in_inheritance_clause = true 131 | csharp_space_before_comma = false 132 | csharp_space_before_dot = false 133 | csharp_space_before_open_square_brackets = false 134 | csharp_space_before_semicolon_in_for_statement = false 135 | csharp_space_between_empty_square_brackets = false 136 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 137 | csharp_space_between_method_call_name_and_opening_parenthesis = false 138 | csharp_space_between_method_call_parameter_list_parentheses = false 139 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 140 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 141 | csharp_space_between_method_declaration_parameter_list_parentheses = false 142 | csharp_space_between_parentheses = false 143 | csharp_space_between_square_brackets = false 144 | 145 | csharp_preserve_single_line_blocks = true 146 | csharp_preserve_single_line_statements = true 147 | 148 | csharp_prefer_braces = true:warning 149 | 150 | csharp_style_expression_bodied_constructors = true:suggestion 151 | csharp_style_expression_bodied_methods = true:suggestion 152 | csharp_style_expression_bodied_properties = true:suggestion 153 | 154 | csharp_prefer_simple_default_expression = true:suggestion 155 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 156 | 157 | csharp_style_var_elsewhere = true:suggestion 158 | csharp_style_var_for_built_in_types = true:suggestion 159 | csharp_style_var_when_type_is_apparent = true:suggestion 160 | 161 | csharp_preferred_modifier_order = internal,protected,public,private,static,readonly,abstract,override,sealed,virtual:suggestion 162 | 163 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 164 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 165 | csharp_style_inlined_variable_declaration = true:suggestion 166 | csharp_style_deconstructed_variable_declaration = true:suggestion 167 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 168 | csharp_style_throw_expression = true:suggestion 169 | csharp_style_conditional_delegate_call = true:suggestion 170 | 171 | 172 | [*.vb] 173 | visual_basic_preferred_modifier_order = partial,default,private,protected,public,friend,notoverridable,overridable,mustoverride,overloads,overrides,mustinherit,notinheritable,static,shared,shadows,readonly,writeonly,dim,const,withevents,widening,narrowing,custom,async,iterator:silent 174 | visual_basic_style_unused_value_assignment_preference = unused_local_variable:suggestion 175 | visual_basic_style_unused_value_expression_statement_preference = unused_local_variable:silent 176 | -------------------------------------------------------------------------------- /Serialization/ExpressionNodeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using ExpressionTreeToString.Util; 7 | using static ExpressionTreeVisualizer.Serialization.EndNodeTypes; 8 | using static ExpressionTreeToString.Globals; 9 | using System.Runtime.CompilerServices; 10 | using System.Collections; 11 | using ZSpitz.Util; 12 | using static ZSpitz.Util.Functions; 13 | using ExpressionTreeToString; 14 | using static ZSpitz.Util.Language; 15 | 16 | namespace ExpressionTreeVisualizer.Serialization { 17 | [Serializable] 18 | public class ExpressionNodeData { 19 | private static readonly HashSet propertyTypes = 20 | NodeTypes 21 | .SelectMany(x => new[] { x, typeof(IEnumerable<>).MakeGenericType(x) }) 22 | .ToHashSet(); 23 | 24 | private static readonly Dictionary> baseTypes = new(); 25 | 26 | private static readonly Dictionary factoryMethodNamesByType = 27 | typeof(Expression).GetMethods() 28 | .Where(x => x.IsStatic) 29 | .GroupBy(x => x.ReturnType, x => x.Name) 30 | .ToDictionary(x => x.Key, x => x.Distinct().Ordered().ToArray()); 31 | 32 | 33 | public string PathFromParent { get; set; } 34 | public string FullPath { get; set; } 35 | 36 | public string NodeType { get; set; } // ideally this should be an intersection type of multiple enums 37 | public List<(string @namespace, string enumTypename, string membername)>? NodeTypesParts { get; set; } 38 | public string? ReflectionTypeName { get; set; } 39 | public bool IsDeclaration { get; set; } 40 | 41 | public string? Name { get; set; } 42 | public string? Closure { get; set; } 43 | public EndNodeTypes? EndNodeType { get; set; } 44 | public string? StringValue { get; set; } 45 | 46 | public bool EnableValueInNewWindow { get; set; } 47 | 48 | public (int start, int length) Span { get; set; } 49 | public int SpanEnd => Span.start + Span.length; 50 | 51 | public string? WatchExpressionFormatString { get; set; } 52 | 53 | public List Children { get; set; } 54 | 55 | 56 | public (string @namespace, string typename, string propertyname)? ParentProperty { get; set; } 57 | 58 | 59 | private readonly List<(string @namespace, string typename)>? _baseTypes; 60 | public List<(string @namespace, string typename)>? BaseTypes => _baseTypes; 61 | 62 | private readonly string[] _factoryMethodNames; 63 | public string[] FactoryMethodNames => _factoryMethodNames; 64 | 65 | 66 | internal ExpressionNodeData(object o, (string aggregatePath, string pathFromParent) path, VisualizerData visualizerData, ValueExtractor valueExtractor, Dictionary pathSpans, bool isParameterDeclaration = false, PropertyInfo? pi = null, string? parentWatchExpression = null) : 67 | this( 68 | o, path, 69 | OneOfStringLanguageExtensions.ResolveLanguage(visualizerData.Config.Language)!.Value, 70 | valueExtractor, 71 | pathSpans, isParameterDeclaration, pi, parentWatchExpression 72 | ) { } 73 | 74 | private ExpressionNodeData( 75 | object o, (string aggregatePath, string pathFromParent) path, 76 | Language language, 77 | ValueExtractor valueExtractor, 78 | Dictionary pathSpans, bool isParameterDeclaration = false, PropertyInfo? pi = null, string? parentWatchExpression = null 79 | ) { 80 | var (aggregatePath, pathFromParent) = path; 81 | PathFromParent = pathFromParent; 82 | 83 | var separator = 84 | aggregatePath is null or "" || pathFromParent is null or "" ? 85 | "" : 86 | "."; 87 | FullPath = aggregatePath + separator + pathFromParent; 88 | 89 | switch (o) { 90 | case Expression expr: 91 | NodeType = expr.NodeType.ToString(); 92 | NodeTypesParts = new List<(string @namespace, string enumTypename, string membername)> { 93 | (typeof(ExpressionType).Namespace!, nameof(ExpressionType), NodeType) 94 | }; 95 | if (expr is GotoExpression gexpr) { 96 | NodeTypesParts.Add( 97 | typeof(GotoExpressionKind).Namespace!, nameof(GotoExpressionKind), gexpr.Kind.ToString() 98 | ); 99 | } 100 | ReflectionTypeName = expr.Type.FriendlyName(language); 101 | IsDeclaration = isParameterDeclaration; 102 | 103 | // fill the Name and Closure properties, for expressions 104 | Name = expr.Name(language); 105 | 106 | if (expr is MemberExpression mexpr && 107 | mexpr.Expression?.Type is Type expressionType && 108 | expressionType.IsClosureClass()) { 109 | Closure = expressionType.Name; 110 | } 111 | 112 | var (evaluated, value) = valueExtractor.GetValue(expr); 113 | if (evaluated) { 114 | StringValue = StringValue(value, language); // TODO value is allowed to be null 115 | EnableValueInNewWindow = value is { } && value.GetType().InheritsFromOrImplementsAny(NodeTypes); 116 | } 117 | 118 | // fill StringValue and EndNodeType properties, for expressions 119 | switch (expr) { 120 | case ConstantExpression cexpr when !cexpr.Type.IsClosureClass(): 121 | EndNodeType = Constant; 122 | break; 123 | case ParameterExpression pexpr1: 124 | EndNodeType = Parameter; 125 | break; 126 | case Expression e1 when expr.IsClosedVariable(): 127 | EndNodeType = ClosedVar; 128 | break; 129 | case DefaultExpression defexpr: 130 | EndNodeType = Default; 131 | break; 132 | } 133 | 134 | break; 135 | case MemberBinding mbind: 136 | NodeType = mbind.BindingType.ToString(); 137 | NodeTypesParts = new List<(string @namespace, string enumTypename, string membername)> { 138 | (typeof(MemberBindingType).Namespace!, nameof(MemberBindingType), NodeType) 139 | }; 140 | Name = mbind.Member.Name; 141 | break; 142 | case CallSiteBinder callSiteBinder: 143 | NodeType = callSiteBinder.BinderType().ToString(); 144 | break; 145 | default: 146 | NodeType = o.GetType().FriendlyName(language)!; 147 | break; 148 | } 149 | 150 | if (pathSpans.TryGetValue(FullPath, out var span)) { 151 | Span = span; 152 | } 153 | 154 | var type = o.GetType(); 155 | var properties = type.GetProperties() 156 | .Where(prp => 157 | !(prp.DeclaringType!.Name == "BlockExpression" && prp.Name == "Result") && 158 | propertyTypes.Any(x => x.IsAssignableFrom(prp.PropertyType)) 159 | ) 160 | .ToList(); 161 | 162 | var typename = o.GetType().BaseTypes(false, true).First(x => !x.IsGenericType && x.IsPublic && properties.All(prp => x.GetProperty(prp.Name) is { })); // because the FullName of generic types is often the fully-qualified name 163 | if (parentWatchExpression.IsNullOrWhitespace()) { 164 | var formatString = "{0}"; 165 | WatchExpressionFormatString = 166 | language == CSharp ? 167 | $"(({typename}){formatString})" : 168 | $"CType({formatString}, {typename})"; 169 | } else { 170 | var watchPathFromParent = PathFromParent; 171 | if (language == CSharp) { 172 | WatchExpressionFormatString = $"(({typename}){parentWatchExpression}.{watchPathFromParent})"; 173 | } else { // Visual Basic 174 | watchPathFromParent = watchPathFromParent.Replace("[", "(").Replace("]", ")"); 175 | WatchExpressionFormatString = $"CType({parentWatchExpression}.{watchPathFromParent}, {typename})"; 176 | } 177 | } 178 | 179 | // populate Children 180 | var preferredOrder = PreferredPropertyOrders.FirstOrDefault(x => x.type.IsAssignableFrom(type)).propertyNames; 181 | Children = properties 182 | .OrderBy(prp => 183 | preferredOrder is not null ? 184 | Array.IndexOf(preferredOrder, prp.Name) : 185 | -1 186 | ) 187 | .ThenBy(prp => prp.Name) 188 | .SelectMany(prp => { 189 | if (prp.PropertyType.InheritsFromOrImplements()) { 190 | return (prp.GetValue(o) as IEnumerable)!.Cast().Select((x, index) => ($"{prp.Name}[{index}]", x, prp)); 191 | } else { 192 | return new[] { (prp.Name, prp.GetValue(o)!, prp) }; 193 | } 194 | }) 195 | .Where(x => x.x != null) 196 | .SelectT((relativePath, o1, prp) => new ExpressionNodeData( 197 | o1, (FullPath ?? "", relativePath), 198 | language, valueExtractor, pathSpans, 199 | false, prp, WatchExpressionFormatString)) 200 | .ToList(); 201 | 202 | // populate URLs 203 | if (pi != null) { 204 | ParentProperty = (pi.DeclaringType!.Namespace!, pi.DeclaringType.Name, pi.Name); 205 | } 206 | 207 | if (!baseTypes.TryGetValue(o.GetType(), out _baseTypes)) { 208 | _baseTypes = o.GetType().BaseTypes(true, true).Where(x => x != typeof(object) && x.IsPublic).Select(x => (x.Namespace!, x.Name)).Distinct().ToList(); 209 | baseTypes[o.GetType()] = _baseTypes; 210 | } 211 | 212 | string? factoryMethodName = null; 213 | if (o is BinaryExpression || o is UnaryExpression) { 214 | Globals.FactoryMethodNames.TryGetValue(((Expression)o).NodeType, out factoryMethodName); 215 | } 216 | if (factoryMethodName.IsNullOrWhitespace()) { 217 | var publicType = o.GetType().BaseTypes(false, true).First(x => !x.IsInterface && x.IsPublic); 218 | factoryMethodNamesByType.TryGetValue(publicType, out _factoryMethodNames!); 219 | } else { 220 | _factoryMethodNames = new[] { factoryMethodName }; 221 | } 222 | } 223 | 224 | public EndNodeData EndNodeData => new() { 225 | Closure = Closure, 226 | Name = Name, 227 | Type = ReflectionTypeName, 228 | Value = StringValue 229 | }; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /UI/VisualizerDataControl.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | --------------------------------------------------------------------------------