diff --git a/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs b/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs new file mode 100644 index 000000000..eb8f20d20 --- /dev/null +++ b/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Avalonia.Headless.NUnit; + +using AwesomeAssertions; + +using ILSpy.AppEnv; +using ILSpy.AssemblyTree; + +using NUnit.Framework; + +namespace ICSharpCode.ILSpy.Tests.AssemblyTree; + +// AssemblyTreeModel is the MEF-shared root of the assembly tree. Initialize() loads +// (or creates) the default assembly list from settings, optionally bootstraps it with +// the three .NET runtime assemblies, and wires Root to an AssemblyListTreeNode. If +// either AssemblyList or Root stays null, the entire pane below is blank — which is +// hard to debug from a UI snapshot, so we catch it at the model layer here. +[TestFixture] +public class AssemblyTreeModelTests +{ + [AvaloniaTest] + public void Initialize_populates_AssemblyList_and_Root() + { + var model = AppComposition.Current.GetExport(); + model.Should().NotBeNull("AssemblyTreeModel is [Export][Shared] in ILSpy.AssemblyTree."); + + model.Initialize(); + + model.AssemblyList.Should().NotBeNull("Initialize loads or creates the default AssemblyList."); + model.Root.Should().NotBeNull("Initialize wires Root to an AssemblyListTreeNode of the loaded list."); + } +} diff --git a/ILSpy/App.axaml.cs b/ILSpy/App.axaml.cs index 8a124ee13..5c0f6a781 100644 --- a/ILSpy/App.axaml.cs +++ b/ILSpy/App.axaml.cs @@ -18,13 +18,16 @@ using System; using System.Composition.Hosting; +using System.Diagnostics; +using System.IO; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using ICSharpCode.ILSpyX.Settings; + using ILSpy.AppEnv; -using ILSpy.ViewModels; using ILSpy.Views; namespace ILSpy @@ -45,6 +48,23 @@ namespace ILSpy CommandLineArguments = CommandLineArguments.Create(Environment.GetCommandLineArgs()[1..]); + ILSpySettings.SettingsFilePathProvider = () => { + if (App.CommandLineArguments.ConfigFile != null) + return App.CommandLineArguments.ConfigFile; + + var assemblyLocation = typeof(MainWindow).Assembly.Location; + if (!String.IsNullOrWhiteSpace(assemblyLocation)) + { + var assemblyDirectory = Path.GetDirectoryName(assemblyLocation); + Debug.Assert(assemblyDirectory != null); + string localPath = Path.Combine(assemblyDirectory, "ILSpy.xml"); + if (File.Exists(localPath)) + return localPath; + } + + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ICSharpCode", "ILSpy.xml"); + }; + try { Composition = AppComposition.Initialize(); diff --git a/ILSpy/AssemblyTree/AssemblyListPane.axaml b/ILSpy/AssemblyTree/AssemblyListPane.axaml new file mode 100644 index 000000000..2485e13a5 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPane.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs new file mode 100644 index 000000000..dfb425444 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPane.axaml.cs @@ -0,0 +1,70 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections; +using System.ComponentModel; + +using Avalonia.Controls; +using Avalonia.Controls.DataGridHierarchical; + +using ICSharpCode.ILSpyX.TreeView; + +namespace ILSpy.AssemblyTree +{ + public partial class AssemblyListPane : UserControl + { + public AssemblyListPane() + { + InitializeComponent(); + } + + protected override void OnDataContextChanged(System.EventArgs e) + { + base.OnDataContextChanged(e); + + if (DataContext is AssemblyTreeModel model) + { + model.PropertyChanged += Model_PropertyChanged; + if (model.Root != null) + BindTree(model.Root); + } + } + + void Model_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(AssemblyTreeModel.Root) && sender is AssemblyTreeModel model && model.Root != null) + BindTree(model.Root); + } + + void BindTree(SharpTreeNode root) + { + var options = new HierarchicalOptions { + ChildrenSelector = node => node.Children, + IsLeafSelector = node => !node.ShowExpander, + IsExpandedSelector = node => node.IsExpanded, + IsExpandedSetter = (node, val) => node.IsExpanded = val, + AutoExpandRoot = true, + }; + + var hierarchicalModel = new HierarchicalModel(options); + hierarchicalModel.SetRoots((IEnumerable)root.Children); + + TreeGrid.HierarchicalModel = hierarchicalModel; + } + } +} diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs new file mode 100644 index 000000000..04cea8e00 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Composition; + +using CommunityToolkit.Mvvm.ComponentModel; + +using ICSharpCode.ILSpyX; +using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpyX.TreeView; + +using ILSpy.TreeNodes; + +namespace ILSpy.AssemblyTree +{ + [Export] + [Shared] + public partial class AssemblyTreeModel : ObservableObject + { + [ObservableProperty] + private SharpTreeNode? root; + + [ObservableProperty] + private SharpTreeNode? selectedItem; + + public AssemblyList? AssemblyList { get; private set; } + + public void Initialize() + { + var settings = ILSpySettings.Load(); + var listManager = new AssemblyListManager(settings); + listManager.CreateDefaultAssemblyLists(); + + AssemblyList = listManager.LoadList(AssemblyListManager.DefaultListName); + + if (AssemblyList.GetAssemblies().Length == 0) + LoadInitialAssemblies(AssemblyList); + + var rootNode = new AssemblyListTreeNode(AssemblyList); + rootNode.IsExpanded = true; + Root = rootNode; + } + + static void LoadInitialAssemblies(AssemblyList assemblyList) + { + System.Reflection.Assembly[] initialAssemblies = { + typeof(object).Assembly, + typeof(Uri).Assembly, + typeof(System.Linq.Enumerable).Assembly, + }; + foreach (var asm in initialAssemblies) + { + if (!string.IsNullOrEmpty(asm.Location)) + assemblyList.OpenAssembly(asm.Location); + } + } + + public void OpenAssembly(string path) + { + AssemblyList?.Open(path); + } + } +} diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs new file mode 100644 index 000000000..98a2eb93e --- /dev/null +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Specialized; +using System.Linq; + +using ICSharpCode.ILSpyX; + +namespace ILSpy.TreeNodes +{ + sealed class AssemblyListTreeNode : ILSpyTreeNode + { + readonly AssemblyList assemblyList; + + public AssemblyList AssemblyList => assemblyList; + + public AssemblyListTreeNode(AssemblyList assemblyList) + { + ArgumentNullException.ThrowIfNull(assemblyList); + this.assemblyList = assemblyList; + + Children.AddRange(assemblyList.GetAssemblies().Select(a => new AssemblyTreeNode(a))); + + assemblyList.CollectionChanged += (_, e) => { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + Children.InsertRange(e.NewStartingIndex, e.NewItems!.Cast().Select(a => new AssemblyTreeNode(a))); + break; + case NotifyCollectionChangedAction.Remove: + Children.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); + break; + case NotifyCollectionChangedAction.Reset: + Children.Clear(); + Children.AddRange(assemblyList.GetAssemblies().Select(a => new AssemblyTreeNode(a))); + break; + } + }; + } + + public override object Text => assemblyList.ListName; + } +} diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs new file mode 100644 index 000000000..c886dd264 --- /dev/null +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -0,0 +1,70 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; + +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpyX; + +namespace ILSpy.TreeNodes +{ + sealed class AssemblyTreeNode : ILSpyTreeNode + { + readonly LoadedAssembly assembly; + + public LoadedAssembly LoadedAssembly => assembly; + + public AssemblyTreeNode(LoadedAssembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + this.assembly = assembly; + LazyLoading = true; + } + + public override object Text => assembly.ShortName; + + protected override void LoadChildren() + { + var result = assembly.GetMetadataFileOrNullAsync().Result; + if (result is not MetadataFile module) + return; + + var metadata = module.Metadata; + var namespaces = metadata.TypeDefinitions + .Where(t => metadata.GetTypeDefinition(t).GetDeclaringType().IsNil) + .Select(t => metadata.GetString(metadata.GetTypeDefinition(t).Namespace)) + .Where(ns => !string.IsNullOrEmpty(ns)) + .Distinct() + .OrderBy(ns => ns); + + foreach (var ns in namespaces) + Children.Add(new NamespaceTreeNode(ns, module)); + + var globalTypes = metadata.TypeDefinitions + .Where(t => { + var td = metadata.GetTypeDefinition(t); + return td.GetDeclaringType().IsNil + && string.IsNullOrEmpty(metadata.GetString(td.Namespace)); + }); + + foreach (var t in globalTypes) + Children.Add(new TypeTreeNode(t, module)); + } + } +} diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs new file mode 100644 index 000000000..ebf52556d --- /dev/null +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using ICSharpCode.ILSpyX.TreeView; + +namespace ILSpy.TreeNodes +{ + public abstract class ILSpyTreeNode : SharpTreeNode + { + } +} diff --git a/ILSpy/TreeNodes/MemberTreeNode.cs b/ILSpy/TreeNodes/MemberTreeNode.cs new file mode 100644 index 000000000..3fb69044f --- /dev/null +++ b/ILSpy/TreeNodes/MemberTreeNode.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace ILSpy.TreeNodes +{ + sealed class MemberTreeNode : ILSpyTreeNode + { + readonly string name; + + public MemberTreeNode(string name) + { + this.name = name; + } + + public override object Text => name; + public override bool ShowExpander => false; + } +} diff --git a/ILSpy/TreeNodes/NamespaceTreeNode.cs b/ILSpy/TreeNodes/NamespaceTreeNode.cs new file mode 100644 index 000000000..ea199d684 --- /dev/null +++ b/ILSpy/TreeNodes/NamespaceTreeNode.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Linq; +using System.Reflection.Metadata; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.TreeNodes +{ + sealed class NamespaceTreeNode : ILSpyTreeNode + { + readonly string name; + readonly MetadataFile module; + + public string Name => name; + + public NamespaceTreeNode(string name, MetadataFile module) + { + this.name = name; + this.module = module; + LazyLoading = true; + } + + public override object Text => name; + + protected override void LoadChildren() + { + var metadata = module.Metadata; + var types = metadata.TypeDefinitions + .Where(t => { + var td = metadata.GetTypeDefinition(t); + return td.GetDeclaringType().IsNil + && metadata.GetString(td.Namespace) == name; + }) + .OrderBy(t => metadata.GetString(metadata.GetTypeDefinition(t).Name)); + + foreach (var t in types) + Children.Add(new TypeTreeNode(t, module)); + } + } +} diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs new file mode 100644 index 000000000..e69f944fc --- /dev/null +++ b/ILSpy/TreeNodes/TypeTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Linq; +using System.Reflection.Metadata; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.TreeNodes +{ + sealed class TypeTreeNode : ILSpyTreeNode + { + readonly TypeDefinitionHandle handle; + readonly MetadataFile module; + + public TypeDefinitionHandle Handle => handle; + public MetadataFile Module => module; + + public TypeTreeNode(TypeDefinitionHandle handle, MetadataFile module) + { + this.handle = handle; + this.module = module; + LazyLoading = true; + } + + public override object Text { + get { + var td = module.Metadata.GetTypeDefinition(handle); + return module.Metadata.GetString(td.Name); + } + } + + protected override void LoadChildren() + { + var td = module.Metadata.GetTypeDefinition(handle); + + foreach (var nestedType in td.GetNestedTypes()) + Children.Add(new TypeTreeNode(nestedType, module)); + + foreach (var method in td.GetMethods()) + { + var md = module.Metadata.GetMethodDefinition(method); + Children.Add(new MemberTreeNode(module.Metadata.GetString(md.Name))); + } + + foreach (var field in td.GetFields()) + { + var fd = module.Metadata.GetFieldDefinition(field); + Children.Add(new MemberTreeNode(module.Metadata.GetString(fd.Name))); + } + + foreach (var prop in td.GetProperties()) + { + var pd = module.Metadata.GetPropertyDefinition(prop); + Children.Add(new MemberTreeNode(module.Metadata.GetString(pd.Name))); + } + + foreach (var evt in td.GetEvents()) + { + var ed = module.Metadata.GetEventDefinition(evt); + Children.Add(new MemberTreeNode(module.Metadata.GetString(ed.Name))); + } + } + } +} diff --git a/ILSpy/ViewModels/MainWindowViewModel.cs b/ILSpy/ViewModels/MainWindowViewModel.cs index dcec7ae68..545e5bd3b 100644 --- a/ILSpy/ViewModels/MainWindowViewModel.cs +++ b/ILSpy/ViewModels/MainWindowViewModel.cs @@ -18,16 +18,22 @@ using System.Composition; +using ILSpy.AssemblyTree; + namespace ILSpy.ViewModels { [Export] [Shared] public partial class MainWindowViewModel : ViewModelBase { - public MainWindowViewModel() - { - } + public AssemblyTreeModel AssemblyTreeModel { get; } public string Title => "ILSpy"; + + [ImportingConstructor] + public MainWindowViewModel(AssemblyTreeModel assemblyTreeModel) + { + AssemblyTreeModel = assemblyTreeModel; + } } } diff --git a/ILSpy/Views/MainWindow.axaml.cs b/ILSpy/Views/MainWindow.axaml.cs index 0ae437301..07a3504bc 100644 --- a/ILSpy/Views/MainWindow.axaml.cs +++ b/ILSpy/Views/MainWindow.axaml.cs @@ -37,6 +37,7 @@ namespace ILSpy.Views public MainWindow(MainWindowViewModel viewModel) : this() { DataContext = viewModel; + Opened += (_, _) => viewModel.AssemblyTreeModel.Initialize(); } } }