From 2c6f06e85401ae57505c9298a494ef3772cd17d1 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Thu, 3 Oct 2024 16:31:11 +0200 Subject: [PATCH 1/4] Fix #3293: Right panel shows old info in case all is deleted in the left-hand tree --- ICSharpCode.ILSpyX/AssemblyListManager.cs | 14 ++--- ILSpy/App.xaml.cs | 3 - ILSpy/AssemblyTree/AssemblyTreeModel.cs | 36 ++++-------- .../RemoveAssembliesWithLoadErrors.cs | 29 ++++++++-- ILSpy/Docking/CloseAllDocumentsCommand.cs | 7 +-- ILSpy/Docking/DockWorkspace.cs | 55 +++++++++++-------- ILSpy/MainWindow.xaml.cs | 10 +++- ILSpy/Util/MenuService.cs | 2 +- ILSpy/Util/SettingsService.cs | 5 ++ 9 files changed, 86 insertions(+), 75 deletions(-) diff --git a/ICSharpCode.ILSpyX/AssemblyListManager.cs b/ICSharpCode.ILSpyX/AssemblyListManager.cs index 35e1021ce9..772062c9e2 100644 --- a/ICSharpCode.ILSpyX/AssemblyListManager.cs +++ b/ICSharpCode.ILSpyX/AssemblyListManager.cs @@ -113,14 +113,16 @@ public bool RenameList(string selectedAssemblyList, string newListName) public void SaveList(AssemblyList list) { this.settingsProvider.Update( - delegate (XElement root) { + root => { XElement? doc = root.Element("AssemblyLists"); if (doc == null) { doc = new XElement("AssemblyLists"); root.Add(doc); } - XElement? listElement = doc.Elements("List").FirstOrDefault(e => (string?)e.Attribute("name") == list.ListName); + + XElement? listElement = doc.Elements("List") + .FirstOrDefault(e => (string?)e.Attribute("name") == list.ListName); if (listElement != null) listElement.ReplaceWith(list.SaveAsXml()); else @@ -163,13 +165,9 @@ public void ClearAll() { AssemblyLists.Clear(); this.settingsProvider.Update( - delegate (XElement root) { + root => { XElement? doc = root.Element("AssemblyLists"); - if (doc == null) - { - return; - } - doc.Remove(); + doc?.Remove(); }); } diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index d00eb42045..0665244f3f 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -216,9 +216,6 @@ protected override void OnStartup(StartupEventArgs e) } MainWindow = new MainWindow(); - MainWindow.Loaded += (sender, args) => { - ExportProvider.GetExportedValue().Initialize(); - }; MainWindow.Show(); } diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index de8237b081..7986967a29 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -86,6 +86,8 @@ public AssemblyTreeModel() SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick(); refreshThrottle = new DispatcherThrottle(DispatcherPriority.Background, RefreshInternal); + + AssemblyList = SettingsService.Instance.CreateEmptyAssemblyList(); } private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -121,7 +123,7 @@ private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e } } - public AssemblyList? AssemblyList { get; private set; } + public AssemblyList AssemblyList { get; private set; } private SharpTreeNode? root; public SharpTreeNode? Root { @@ -205,7 +207,7 @@ private async void NavigateOnLaunch(string? navigateTo, string[]? activeTreeView { // FindNamespaceNode() blocks the UI if the assembly is not yet loaded, // so use an async wait instead. - await asm.GetMetadataFileAsync().Catch(ex => { }); + await asm.GetMetadataFileAsync().Catch(_ => { }); NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName); if (nsNode != null) { @@ -260,7 +262,7 @@ private async void NavigateOnLaunch(string? navigateTo, string[]? activeTreeView else if (spySettings != null) { SharpTreeNode? node = null; - if (activeTreeViewPath?.Length > 0 && AssemblyList != null) + if (activeTreeViewPath?.Length > 0) { foreach (var asm in AssemblyList.GetAssemblies()) { @@ -268,7 +270,7 @@ private async void NavigateOnLaunch(string? navigateTo, string[]? activeTreeView { // FindNodeByPath() blocks the UI if the assembly is not yet loaded, // so use an async wait instead. - await asm.GetMetadataFileAsync().Catch(ex => { }); + await asm.GetMetadataFileAsync().Catch(_ => { }); } } node = FindNodeByPath(activeTreeViewPath, true); @@ -401,7 +403,7 @@ private void ShowAssemblyList(string name) { AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); //Only load a new list when it is a different one - if (list.ListName != AssemblyList?.ListName) + if (list.ListName != AssemblyList.ListName) { ShowAssemblyList(list); SelectNode(Root); @@ -411,12 +413,9 @@ private void ShowAssemblyList(string name) private void ShowAssemblyList(AssemblyList assemblyList) { history.Clear(); - if (this.AssemblyList != null) - { - this.AssemblyList.CollectionChanged -= assemblyList_CollectionChanged; - } - this.AssemblyList = assemblyList; + AssemblyList.CollectionChanged -= assemblyList_CollectionChanged; + AssemblyList = assemblyList; assemblyList.CollectionChanged += assemblyList_CollectionChanged; @@ -527,11 +526,6 @@ public void SelectNodes(IEnumerable nodes) return; } - if (SelectedItems.SequenceEqual(nodesList)) - { - return; - } - if (this.isNavigatingHistory) { SelectedItems.Clear(); @@ -649,8 +643,6 @@ private Task JumpToReferenceAsync(object? reference, bool inNewTabPage = false) MainWindow.OpenLink(opCode.Link); break; case EntityReference unresolvedEntity: - if (AssemblyList is null) - break; string protocol = unresolvedEntity.Protocol; var file = unresolvedEntity.ResolveAssembly(AssemblyList); if (file == null) @@ -696,8 +688,6 @@ private void LoadAssemblies(IEnumerable fileNames, List? AssemblyTreeNode? lastNode = null; var assemblyList = AssemblyList; - if (assemblyList is null) - return; foreach (string file in fileNames) { @@ -907,11 +897,7 @@ private void RefreshInternal() { var path = GetPathForNode(SelectedItem); - if (AssemblyList != null) - { - ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName)); - } - + ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName)); SelectNode(FindNodeByPath(path, true), inNewTabPage: false); RefreshDecompiledView(); @@ -940,7 +926,7 @@ public void SortAssemblyList() { using (activeView?.LockUpdates()) { - AssemblyList?.Sort(AssemblyComparer.Instance); + AssemblyList.Sort(AssemblyComparer.Instance); } } diff --git a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs index 9cc357374a..b0bc068c88 100644 --- a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs +++ b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Linq; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,18 +28,26 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] class RemoveAssembliesWithLoadErrors : SimpleCommand { + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public RemoveAssembliesWithLoadErrors(AssemblyTreeModel assemblyTreeModel) + { + this.assemblyTreeModel = assemblyTreeModel; + } + public override bool CanExecute(object parameter) { - return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; + return assemblyTreeModel.AssemblyList.GetAssemblies().Any(l => l.HasLoadError); } public override void Execute(object parameter) { - foreach (var asm in MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()) + foreach (var assembly in assemblyTreeModel.AssemblyList.GetAssemblies()) { - if (!asm.HasLoadError) + if (!assembly.HasLoadError) continue; - var node = MainWindow.Instance.AssemblyTreeModel.FindAssemblyNode(asm); + var node = MainWindow.Instance.AssemblyTreeModel.FindAssemblyNode(assembly); if (node != null && node.CanDelete()) node.Delete(); } @@ -49,14 +58,22 @@ public override void Execute(object parameter) [PartCreationPolicy(CreationPolicy.Shared)] class ClearAssemblyList : SimpleCommand { + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public ClearAssemblyList(AssemblyTreeModel assemblyTreeModel) + { + this.assemblyTreeModel = assemblyTreeModel; + } + public override bool CanExecute(object parameter) { - return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Count > 0; + return assemblyTreeModel.AssemblyList.Count > 0; } public override void Execute(object parameter) { - MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Clear(); + assemblyTreeModel.AssemblyList.Clear(); } } } diff --git a/ILSpy/Docking/CloseAllDocumentsCommand.cs b/ILSpy/Docking/CloseAllDocumentsCommand.cs index 2c0ceb41be..14169498f4 100644 --- a/ILSpy/Docking/CloseAllDocumentsCommand.cs +++ b/ILSpy/Docking/CloseAllDocumentsCommand.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.Composition; using ICSharpCode.ILSpy.Properties; diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 9ca68cc266..057cd0f147 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -51,13 +52,17 @@ public class DockWorkspace : ObservableObject, ILayoutUpdateStrategy public static readonly DockWorkspace Instance = new(); private readonly ObservableCollection tabPages = []; - private readonly ObservableCollection toolPanes = []; private DockWorkspace() { this.tabPages.CollectionChanged += TabPages_CollectionChanged; TabPages = new(tabPages); - ToolPanes = new(toolPanes); + + ToolPanes = exportProvider + .GetExportedValues("ToolPane") + .OrderBy(item => item.Title) + .ToArray() + .AsReadOnly(); // Make sure there is at least one tab open AddTabPage(); @@ -83,11 +88,16 @@ private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedE .ExceptNullItems() .Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly)); - if (!found && tabPages.Count > 1) + if (!found) { tabPages.Remove(tab); } } + + if (tabPages.Count == 0) + { + AddTabPage(); + } } private void TabPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -117,11 +127,11 @@ public void AddTabPage(TabPageModel tabPage = null) public ReadOnlyObservableCollection TabPages { get; } - public ReadOnlyObservableCollection ToolPanes { get; } + public ReadOnlyCollection ToolPanes { get; } public bool ShowToolPane(string contentId) { - var pane = toolPanes.FirstOrDefault(p => p.ContentId == contentId); + var pane = ToolPanes.FirstOrDefault(p => p.ContentId == contentId); if (pane != null) { pane.Show(); @@ -132,10 +142,15 @@ public bool ShowToolPane(string contentId) public void Remove(PaneModel model) { - if (model is TabPageModel document) - tabPages.Remove(document); - if (model is ToolPaneModel tool) - tool.IsVisible = false; + switch (model) + { + case TabPageModel document: + tabPages.Remove(document); + break; + case ToolPaneModel tool: + tool.IsVisible = false; + break; + } } private TabPageModel activeTabPage = null; @@ -166,10 +181,6 @@ public TabPageModel ActiveTabPage { public void InitializeLayout(DockingManager manager) { - var panes = exportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); - - this.toolPanes.AddRange(panes); - manager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); serializer.LayoutSerializationCallback += LayoutSerializationCallback; @@ -188,13 +199,13 @@ void LayoutSerializationCallback(object sender, LayoutSerializationCallbackEvent switch (e.Model) { case LayoutAnchorable la: - e.Content = this.toolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); + e.Content = this.ToolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); e.Cancel = e.Content == null; la.CanDockAsTabbedDocument = false; - if (!e.Cancel) + if (e.Content is ToolPaneModel toolPaneModel) { - e.Cancel = ((ToolPaneModel)e.Content).IsVisible; - ((ToolPaneModel)e.Content).IsVisible = true; + e.Cancel = toolPaneModel.IsVisible; + toolPaneModel.IsVisible = true; } break; default: @@ -220,16 +231,14 @@ internal void ShowNodes(AvalonEditTextOutput output, TreeNodes.ILSpyTreeNode[] n internal void CloseAllTabs() { - foreach (var doc in tabPages.ToArray()) - { - if (doc.IsCloseable) - tabPages.Remove(doc); - } + var activePage = ActiveTabPage; + + tabPages.RemoveWhere(page => page != activePage); } internal void ResetLayout() { - foreach (var pane in toolPanes) + foreach (var pane in ToolPanes) { pane.IsVisible = false; } diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index c5232ef831..53fd1f30fa 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -25,6 +25,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Threading; using AvalonDock.Layout.Serialization; @@ -70,11 +71,14 @@ public MainWindow() InitializeComponent(); - mainWindowViewModel.Workspace.InitializeLayout(dockManager); + InitFileLoaders(); - MenuService.Instance.Init(mainMenu, toolBar, InputBindings); + Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { + mainWindowViewModel.Workspace.InitializeLayout(dockManager); + MenuService.Instance.Init(mainMenu, toolBar, InputBindings); - InitFileLoaders(); + Dispatcher.BeginInvoke(DispatcherPriority.Background, AssemblyTreeModel.Initialize); + }); } void SetWindowBounds(Rect bounds) diff --git a/ILSpy/Util/MenuService.cs b/ILSpy/Util/MenuService.cs index d364ad4389..a42e8c35ab 100644 --- a/ILSpy/Util/MenuService.cs +++ b/ILSpy/Util/MenuService.cs @@ -142,7 +142,7 @@ void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) windowMenuItem.Items.Clear(); - var toolItems = dockWorkspace.ToolPanes.ObservableSelect(toolPane => CreateMenuItem(toolPane, inputBindings)); + var toolItems = dockWorkspace.ToolPanes.Select(toolPane => CreateMenuItem(toolPane, inputBindings)).ToArray(); var tabItems = dockWorkspace.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dockWorkspace)); var allItems = new ObservableCompositeCollection(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems); diff --git a/ILSpy/Util/SettingsService.cs b/ILSpy/Util/SettingsService.cs index def1ccf211..bd2923e2e5 100644 --- a/ILSpy/Util/SettingsService.cs +++ b/ILSpy/Util/SettingsService.cs @@ -152,6 +152,11 @@ public AssemblyList LoadInitialAssemblyList() } } + public AssemblyList CreateEmptyAssemblyList() + { + return AssemblyListManager.CreateList(string.Empty); + } + private bool reloading; public void Reload() From 5149e4e77f08c832adde5500e7a4750b36f9154f Mon Sep 17 00:00:00 2001 From: tom-englert Date: Fri, 4 Oct 2024 17:28:12 +0200 Subject: [PATCH 2/4] Optimize selection handling --- Directory.Packages.props | 8 +- ILSpy/Analyzers/AnalyzerTreeView.xaml | 3 +- ILSpy/Analyzers/AnalyzerTreeViewModel.cs | 24 +++--- ILSpy/AssemblyTree/AssemblyListPane.xaml | 3 +- ILSpy/AssemblyTree/AssemblyTreeModel.cs | 93 +++++++++++---------- ILSpy/Commands/DecompileInNewViewCommand.cs | 27 ++++-- ILSpy/Controls/TreeView/SharpTreeView.cs | 2 + ILSpy/Docking/DockWorkspace.cs | 12 ++- ILSpy/MainWindow.xaml.cs | 5 +- 9 files changed, 106 insertions(+), 71 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f2fd119b5d..bcedfd1c10 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,12 +45,12 @@ - - - + + + - + \ No newline at end of file diff --git a/ILSpy/Analyzers/AnalyzerTreeView.xaml b/ILSpy/Analyzers/AnalyzerTreeView.xaml index 28bba030c8..d23119f127 100644 --- a/ILSpy/Analyzers/AnalyzerTreeView.xaml +++ b/ILSpy/Analyzers/AnalyzerTreeView.xaml @@ -12,8 +12,7 @@ ShowRoot="False" BorderThickness="0" Root="{Binding Root}" - toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems}" - SelectedItem="{Binding SelectedItem, Mode=TwoWay}" + toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems, Mode=TwoWay}" SelectionChanged="AnalyzerTreeView_OnSelectionChanged"> diff --git a/ILSpy/Analyzers/AnalyzerTreeViewModel.cs b/ILSpy/Analyzers/AnalyzerTreeViewModel.cs index 7a746c0889..5c666d38f4 100644 --- a/ILSpy/Analyzers/AnalyzerTreeViewModel.cs +++ b/ILSpy/Analyzers/AnalyzerTreeViewModel.cs @@ -17,8 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel.Composition; using System.Linq; using System.Windows; @@ -38,8 +36,6 @@ namespace ICSharpCode.ILSpy.Analyzers [Export] public class AnalyzerTreeViewModel : ToolPaneModel { - private AnalyzerTreeNode selectedItem; - public const string PaneContentId = "analyzerPane"; public AnalyzerTreeViewModel() @@ -52,14 +48,20 @@ public AnalyzerTreeViewModel() public AnalyzerRootNode Root { get; } = new(); - public AnalyzerTreeNode SelectedItem { - get => selectedItem; - set => SetProperty(ref selectedItem, value); - } - public ICommand AnalyzeCommand => new DelegateCommand(AnalyzeSelected); - public ObservableCollection SelectedItems { get; } = []; + private AnalyzerTreeNode[] selectedItems = []; + + public AnalyzerTreeNode[] SelectedItems { + get => selectedItems ?? []; + set { + if (SelectedItems.SequenceEqual(value)) + return; + + selectedItems = value; + OnPropertyChanged(); + } + } private void AnalyzeSelected() { @@ -87,7 +89,7 @@ void AddOrSelect(AnalyzerTreeNode node) } target.IsExpanded = true; - this.SelectedItem = target; + this.SelectedItems = [target]; } public void Analyze(IEntity entity) diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml b/ILSpy/AssemblyTree/AssemblyListPane.xaml index 7222fdabff..49ea9a7817 100644 --- a/ILSpy/AssemblyTree/AssemblyListPane.xaml +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml @@ -15,8 +15,7 @@ AllowDrop="True" BorderThickness="0" Visibility="Visible" Root="{Binding Root}" - SelectedItem="{Binding SelectedItem, Mode=TwoWay}" - toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems}"> + toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems, Mode=TwoWay}">