From 9e74d29c85a1d77bdd02087937b707210310926d Mon Sep 17 00:00:00 2001 From: Lex Li Date: Wed, 31 Dec 2025 02:38:34 -0500 Subject: [PATCH 1/7] Refactor for cross platform port (#3641) --- ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs | 17 ++ .../TreeView/SharpTreeNodeCollection.cs | 47 +++++- ILSpy/AboutPage.cs | 4 + ILSpy/AssemblyTree/AssemblyTreeModel.cs | 69 +++----- ILSpy/AssemblyTree/AssemblyTreeModel.wpf.cs | 83 ++++++++++ ILSpy/Commands/Pdb2XmlCommand.cs | 2 +- ILSpy/Docking/DockWorkspace.cs | 121 +------------- ILSpy/Docking/DockWorkspace.wpf.cs | 150 ++++++++++++++++++ ILSpy/ILSpy.csproj | 4 +- ILSpy/ISmartTextOutput.cs | 38 ----- ILSpy/Languages/CSharpLanguage.cs | 40 +---- ILSpy/Languages/CSharpLanguage.wpf.cs | 75 +++++++++ ILSpy/Metadata/DataDirectoriesTreeNode.cs | 6 +- ILSpy/Metadata/DebugDirectoryTreeNode.cs | 6 +- ILSpy/Metadata/FlagsContentFilter.cs | 22 +++ ILSpy/Metadata/FlagsFilterControl.xaml.cs | 20 --- ILSpy/Metadata/Helper.wpf.cs | 43 +++++ ILSpy/Metadata/Helpers.cs | 43 ++--- .../HexFilterControl.ContentFilter.cs | 31 ++++ ILSpy/Metadata/HexFilterControl.xaml.cs | 23 --- ILSpy/Metadata/MetadataTableTreeNode.cs | 27 +--- ILSpy/Metadata/MetadataTableTreeNode.wpf.cs | 32 ++++ ILSpy/Metadata/MetadataTreeNode.cs | 3 + ILSpy/Options/ExportOptionPageAttribute.cs | 30 ++++ ILSpy/Options/IOptionPage.cs | 29 ++++ ILSpy/Options/IOptionsMetadata.cs | 25 +++ ILSpy/Options/OptionsDialog.xaml.cs | 38 ----- ILSpy/Options/ShowOptionsCommand.cs | 56 +++++++ ILSpy/Search/SearchPane.xaml.cs | 24 --- ILSpy/Search/SearchPaneModel.cs | 3 +- ILSpy/Search/ShowSearchCommand.cs | 51 ++++++ ILSpy/SmartTextOutputExtensions.cs | 61 +++++++ ILSpy/TextView/BracketHighlightRenderer.cs | 54 +------ ILSpy/TextView/BracketSearchResult.cs | 43 +++++ ILSpy/TextView/DecompilerTextView.cs | 28 ---- ILSpy/TextView/DefaultBracketSearcher.cs | 32 ++++ ILSpy/TextView/IBracketSearcher.cs | 34 ++++ ILSpy/TextView/ReferenceElementGenerator.cs | 33 ---- ILSpy/TextView/ViewState.cs | 56 +++++++ ILSpy/TextView/VisualLineReferenceText.cs | 50 ++++++ ILSpy/TextView/VisualLineReferenceText.wpf.cs | 37 +++++ ILSpy/TreeNodes/AssemblyTreeNode.cs | 22 ++- ILSpy/TreeNodes/ResourceListTreeNode.cs | 16 +- .../ResourceNodes/ImageResourceEntryNode.cs | 27 +--- .../ImageResourceEntryNode.wpf.cs | 55 +++++++ ILSpy/Util/GlobalUtils.cs | 2 +- ILSpy/Util/GlobalUtils.wpf.cs | 28 ++++ ILSpy/ViewModels/Pane.cs | 49 ++++++ ILSpy/ViewModels/PaneModel.cs | 28 ---- ILSpy/ViewModels/TabPageModel.cs | 83 +--------- ILSpy/ViewModels/TabPageModelExtensions.cs | 89 +++++++++++ .../ViewModels/TabPageModelExtensions.wpf.cs | 44 +++++ ILSpy/ViewModels/ToolPaneModel.cs | 9 ++ ILSpy/Views/GacEntry.cs | 87 ++++++++++ ILSpy/Views/OpenFromGacDialog.xaml.cs | 59 ------- 55 files changed, 1472 insertions(+), 716 deletions(-) create mode 100644 ILSpy/AssemblyTree/AssemblyTreeModel.wpf.cs create mode 100644 ILSpy/Docking/DockWorkspace.wpf.cs create mode 100644 ILSpy/Languages/CSharpLanguage.wpf.cs create mode 100644 ILSpy/Metadata/FlagsContentFilter.cs create mode 100644 ILSpy/Metadata/Helper.wpf.cs create mode 100644 ILSpy/Metadata/HexFilterControl.ContentFilter.cs create mode 100644 ILSpy/Metadata/MetadataTableTreeNode.wpf.cs create mode 100644 ILSpy/Options/ExportOptionPageAttribute.cs create mode 100644 ILSpy/Options/IOptionPage.cs create mode 100644 ILSpy/Options/IOptionsMetadata.cs create mode 100644 ILSpy/Options/ShowOptionsCommand.cs create mode 100644 ILSpy/Search/ShowSearchCommand.cs create mode 100644 ILSpy/SmartTextOutputExtensions.cs create mode 100644 ILSpy/TextView/BracketSearchResult.cs create mode 100644 ILSpy/TextView/DefaultBracketSearcher.cs create mode 100644 ILSpy/TextView/IBracketSearcher.cs create mode 100644 ILSpy/TextView/ViewState.cs create mode 100644 ILSpy/TextView/VisualLineReferenceText.cs create mode 100644 ILSpy/TextView/VisualLineReferenceText.wpf.cs create mode 100644 ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.wpf.cs create mode 100644 ILSpy/Util/GlobalUtils.wpf.cs create mode 100644 ILSpy/ViewModels/Pane.cs create mode 100644 ILSpy/ViewModels/TabPageModelExtensions.cs create mode 100644 ILSpy/ViewModels/TabPageModelExtensions.wpf.cs create mode 100644 ILSpy/Views/GacEntry.cs diff --git a/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs b/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs index a63fc98174..ea54bd4e18 100644 --- a/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs +++ b/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs @@ -323,9 +323,26 @@ public bool LazyLoading { } RaisePropertyChanged(nameof(LazyLoading)); RaisePropertyChanged(nameof(ShowExpander)); + RaisePropertyChanged(nameof(ViewChildren)); } } + /// + /// Workaround for cross platform treeview bindings. + /// + public System.Collections.IEnumerable ViewChildren { + get { + if (LazyLoading && Children.Count == 0) + return new[] { new LoadingTreeNode() }; + return Children; + } + } + + class LoadingTreeNode : SharpTreeNode + { + public override object Text => "Loading..."; + } + bool canExpandRecursively = true; /// diff --git a/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs b/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs index a778531554..b938474455 100644 --- a/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs +++ b/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; @@ -29,7 +30,7 @@ namespace ICSharpCode.ILSpyX.TreeView /// /// Collection that validates that inserted nodes do not have another parent. /// - public sealed class SharpTreeNodeCollection : IList, INotifyCollectionChanged + public sealed class SharpTreeNodeCollection : IList, IList, INotifyCollectionChanged { readonly SharpTreeNode parent; List list = new List(); @@ -94,6 +95,20 @@ bool ICollection.IsReadOnly { get { return false; } } + #region IList Members + + public bool IsFixedSize => ((IList)list).IsFixedSize; + + public bool IsReadOnly => ((IList)list).IsReadOnly; + + public bool IsSynchronized => ((ICollection)list).IsSynchronized; + + public object SyncRoot => ((ICollection)list).SyncRoot; + + object IList.this[int index] { get => ((IList)list)[index]; set => ((IList)list)[index] = value; } + + #endregion + public int IndexOf(SharpTreeNode node) { if (node == null || node.modelParent != parent) @@ -236,5 +251,35 @@ public void RemoveAll(Predicate match) RemoveRange(firstToRemove, list.Count - firstToRemove); } } + + public int Add(object value) + { + return ((IList)list).Add(value); + } + + public bool Contains(object value) + { + return ((IList)list).Contains(value); + } + + public int IndexOf(object value) + { + return ((IList)list).IndexOf(value); + } + + public void Insert(int index, object value) + { + ((IList)list).Insert(index, value); + } + + public void Remove(object value) + { + ((IList)list).Remove(value); + } + + public void CopyTo(Array array, int index) + { + ((ICollection)list).CopyTo(array, index); + } } } diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index f53e1e7470..09b9c84967 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -192,7 +192,11 @@ static void ShowAvailableVersion(AvailableVersionInfo availableVersion, StackPan stackPanel.Children.Add( new Image { Width = 16, Height = 16, +#if CROSS_PLATFORM + Source = Images.LoadImage(Images.OK), +#else Source = Images.OK, +#endif Margin = new Thickness(4, 0, 4, 0) }); stackPanel.Children.Add( diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index fecdac55e7..778d73ec39 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -29,7 +29,6 @@ using System.Reflection.Metadata.Ecma335; using System.Threading.Tasks; using System.Windows; -using System.Windows.Documents; using System.Windows.Input; using System.Windows.Navigation; using System.Windows.Threading; @@ -57,7 +56,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree { [ExportToolPane] [Shared] - public class AssemblyTreeModel : ToolPaneModel + public partial class AssemblyTreeModel : ToolPaneModel { public const string PaneContentId = "assemblyListPane"; @@ -74,36 +73,6 @@ public class AssemblyTreeModel : ToolPaneModel private static Dispatcher UIThreadDispatcher => Application.Current.Dispatcher; - public AssemblyTreeModel(SettingsService settingsService, LanguageService languageService, IExportProvider exportProvider) - { - this.settingsService = settingsService; - this.languageService = languageService; - this.exportProvider = exportProvider; - - Title = Resources.Assemblies; - ContentId = PaneContentId; - IsCloseable = false; - ShortcutKey = new KeyGesture(Key.F6); - - MessageBus.Subscribers += JumpToReference; - MessageBus.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); - MessageBus.Subscribers += ApplySessionSettings; - MessageBus.Subscribers += ActiveTabPageChanged; - MessageBus.Subscribers += (_, e) => history.RemoveAll(s => !DockWorkspace.TabPages.Contains(s.TabPage)); - MessageBus.Subscribers += ResetLayout; - MessageBus.Subscribers += (_, e) => NavigateTo(e.Request, e.InNewTabPage); - MessageBus.Subscribers += (_, _) => { - Initialize(); - Show(); - }; - - EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler((_, e) => NavigateTo(e))); - - refreshThrottle = new(DispatcherPriority.Background, RefreshInternal); - - AssemblyList = settingsService.CreateEmptyAssemblyList(); - } - private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (sender is SessionSettings sessionSettings) @@ -161,6 +130,9 @@ public SharpTreeNode[] SelectedItems { var oldSelection = selectedItems; selectedItems = value; OnPropertyChanged(); + #if CROSS_PLATFORM + OnPropertyChanged(nameof(SelectedItem)); + #endif TreeView_SelectionChanged(oldSelection, selectedItems); } } @@ -494,24 +466,6 @@ private void assemblyList_CollectionChanged(object? sender, NotifyCollectionChan MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e)); } - private static void LoadInitialAssemblies(AssemblyList assemblyList) - { - // Called when loading an empty assembly list; so that - // the user can see something initially. - System.Reflection.Assembly[] initialAssemblies = { - typeof(object).Assembly, - typeof(Uri).Assembly, - typeof(System.Linq.Enumerable).Assembly, - typeof(System.Xml.XmlDocument).Assembly, - typeof(System.Windows.Markup.MarkupExtension).Assembly, - typeof(System.Windows.Rect).Assembly, - typeof(System.Windows.UIElement).Assembly, - typeof(System.Windows.FrameworkElement).Assembly - }; - foreach (System.Reflection.Assembly asm in initialAssemblies) - assemblyList.OpenAssembly(asm.Location); - } - public AssemblyTreeNode? FindAssemblyNode(LoadedAssembly asm) { return assemblyListTreeNode?.FindAssemblyNode(asm); @@ -542,10 +496,16 @@ public void SelectNode(SharpTreeNode? node, bool inNewTabPage = false) } else { + #if CROSS_PLATFORM + ExpandAncestors(node); + #endif activeView?.ScrollIntoView(node); SelectedItem = node; UIThreadDispatcher.BeginInvoke(DispatcherPriority.Background, () => { + #if CROSS_PLATFORM + SelectedItem = node; + #endif activeView?.ScrollIntoView(node); }); } @@ -1011,6 +971,15 @@ private IEnumerable GetTopLevelSelection() return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); } + void ExpandAncestors(SharpTreeNode node) + { + foreach (var ancestor in node.Ancestors().Reverse()) + { + ancestor.EnsureLazyChildren(); + ancestor.IsExpanded = true; + } + } + public void SetActiveView(AssemblyListPane activeView) { this.activeView = activeView; diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.wpf.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.wpf.cs new file mode 100644 index 0000000000..9c3f49cb1d --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.wpf.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2019 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.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Navigation; +using System.Windows.Threading; + +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpyX; + +using TomsToolbox.Composition; + +namespace ICSharpCode.ILSpy.AssemblyTree +{ + public partial class AssemblyTreeModel + { + public AssemblyTreeModel(SettingsService settingsService, LanguageService languageService, IExportProvider exportProvider) + { + this.settingsService = settingsService; + this.languageService = languageService; + this.exportProvider = exportProvider; + + Title = Resources.Assemblies; + ContentId = PaneContentId; + IsCloseable = false; + ShortcutKey = new KeyGesture(Key.F6); + + MessageBus.Subscribers += JumpToReference; + MessageBus.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); + MessageBus.Subscribers += ApplySessionSettings; + MessageBus.Subscribers += ActiveTabPageChanged; + MessageBus.Subscribers += (_, e) => history.RemoveAll(s => !DockWorkspace.TabPages.Contains(s.TabPage)); + MessageBus.Subscribers += ResetLayout; + MessageBus.Subscribers += (_, e) => NavigateTo(e.Request, e.InNewTabPage); + MessageBus.Subscribers += (_, _) => { + Initialize(); + Show(); + }; + + EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler((_, e) => NavigateTo(e))); + + refreshThrottle = new(DispatcherPriority.Background, RefreshInternal); + + AssemblyList = settingsService.CreateEmptyAssemblyList(); + } + + private static void LoadInitialAssemblies(AssemblyList assemblyList) + { + // Called when loading an empty assembly list; so that + // the user can see something initially. + System.Reflection.Assembly[] initialAssemblies = { + typeof(object).Assembly, + typeof(Uri).Assembly, + typeof(System.Linq.Enumerable).Assembly, + typeof(System.Xml.XmlDocument).Assembly, + typeof(System.Windows.Markup.MarkupExtension).Assembly, + typeof(System.Windows.Rect).Assembly, + typeof(System.Windows.UIElement).Assembly, + typeof(System.Windows.FrameworkElement).Assembly + }; + foreach (System.Reflection.Assembly asm in initialAssemblies) + assemblyList.OpenAssembly(asm.Location); + } + } +} \ No newline at end of file diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs index 7258d06f65..fb554c3210 100644 --- a/ILSpy/Commands/Pdb2XmlCommand.cs +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -16,7 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -#if DEBUG +#if DEBUG && WINDOWS using System.Collections.Generic; using System.Composition; diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 07de6b3e1d..38f9c083e6 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -22,19 +22,13 @@ using System.Collections.Specialized; using System.Composition; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Windows.Data; using System.Windows.Threading; -using AvalonDock; using AvalonDock.Layout; -using AvalonDock.Layout.Serialization; using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.ILSpy.Analyzers; -using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; @@ -44,9 +38,9 @@ namespace ICSharpCode.ILSpy.Docking { - [Export] + [Export(typeof(DockWorkspace))] [Shared] - public class DockWorkspace : ObservableObjectBase, ILayoutUpdateStrategy + public partial class DockWorkspace : ObservableObjectBase, ILayoutUpdateStrategy { private readonly IExportProvider exportProvider; @@ -55,8 +49,6 @@ public class DockWorkspace : ObservableObjectBase, ILayoutUpdateStrategy readonly SessionSettings sessionSettings; - private DockingManager DockingManager => exportProvider.GetExportedValue(); - public DockWorkspace(SettingsService settingsService, IExportProvider exportProvider) { this.exportProvider = exportProvider; @@ -175,56 +167,6 @@ public TabPageModel ActiveTabPage { MessageBus.Send(this, new ActiveTabPageChangedEventArgs(value?.GetState())); } } - - public PaneModel ActivePane { - get => DockingManager.ActiveContent as PaneModel; - set => DockingManager.ActiveContent = value; - } - - public void InitializeLayout() - { - if (tabPages.Count == 0) - { - // Make sure there is at least one tab open - AddTabPage(); - } - - DockingManager.LayoutUpdateStrategy = this; - XmlLayoutSerializer serializer = new XmlLayoutSerializer(DockingManager); - serializer.LayoutSerializationCallback += LayoutSerializationCallback; - try - { - sessionSettings.DockLayout.Deserialize(serializer); - } - finally - { - serializer.LayoutSerializationCallback -= LayoutSerializationCallback; - } - - DockingManager.SetBinding(DockingManager.AnchorablesSourceProperty, new Binding(nameof(ToolPanes))); - DockingManager.SetBinding(DockingManager.DocumentsSourceProperty, new Binding(nameof(TabPages))); - } - - void LayoutSerializationCallback(object sender, LayoutSerializationCallbackEventArgs e) - { - switch (e.Model) - { - case LayoutAnchorable la: - e.Content = this.ToolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); - e.Cancel = e.Content == null; - la.CanDockAsTabbedDocument = false; - if (e.Content is ToolPaneModel toolPaneModel) - { - e.Cancel = toolPaneModel.IsVisible; - toolPaneModel.IsVisible = true; - } - break; - default: - e.Cancel = true; - break; - } - } - public void ShowText(AvalonEditTextOutput textOutput) { ActiveTabPage.ShowTextView(textView => textView.ShowText(textOutput)); @@ -252,65 +194,6 @@ internal void CloseAllTabs() tabPages.RemoveWhere(page => page != activePage); } - internal void ResetLayout() - { - foreach (var pane in ToolPanes) - { - pane.IsVisible = false; - } - CloseAllTabs(); - sessionSettings.DockLayout.Reset(); - InitializeLayout(); - - App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, () => MessageBus.Send(this, new ResetLayoutEventArgs())); - } - - static readonly PropertyInfo previousContainerProperty = typeof(LayoutContent).GetProperty("PreviousContainer", BindingFlags.NonPublic | BindingFlags.Instance); - - public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer) - { - if (!(anchorableToShow.Content is LegacyToolPaneModel legacyContent)) - return false; - anchorableToShow.CanDockAsTabbedDocument = false; - - LayoutAnchorablePane previousContainer; - switch (legacyContent.Location) - { - case LegacyToolPaneLocation.Top: - previousContainer = GetContainer(); - previousContainer.Children.Add(anchorableToShow); - return true; - case LegacyToolPaneLocation.Bottom: - previousContainer = GetContainer(); - previousContainer.Children.Add(anchorableToShow); - return true; - default: - return false; - } - - LayoutAnchorablePane GetContainer() - { - var anchorable = layout.Descendents().OfType().FirstOrDefault(x => x.Content is T) - ?? layout.Hidden.First(x => x.Content is T); - return (LayoutAnchorablePane)previousContainerProperty.GetValue(anchorable) ?? (LayoutAnchorablePane)anchorable.Parent; - } - } - - public void AfterInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableShown) - { - anchorableShown.IsActive = true; - anchorableShown.IsSelected = true; - } - - public bool BeforeInsertDocument(LayoutRoot layout, LayoutDocument anchorableToShow, ILayoutContainer destinationContainer) - { - return false; - } - - public void AfterInsertDocument(LayoutRoot layout, LayoutDocument anchorableShown) - { - } - // Dummy property to make the XAML designer happy, the model is provided by the AvalonDock PaneStyleSelectors, not by the DockWorkspace, but the designer assumes the data context in the PaneStyleSelectors is the DockWorkspace. public PaneModel Model { get; } = null; } diff --git a/ILSpy/Docking/DockWorkspace.wpf.cs b/ILSpy/Docking/DockWorkspace.wpf.cs new file mode 100644 index 0000000000..fddb2e1a3a --- /dev/null +++ b/ILSpy/Docking/DockWorkspace.wpf.cs @@ -0,0 +1,150 @@ +// Copyright (c) 2019 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; +using System.Windows.Data; +using System.Windows.Threading; + +using AvalonDock; +using AvalonDock.Layout; +using AvalonDock.Layout.Serialization; + +using ICSharpCode.ILSpy.Analyzers; +using ICSharpCode.ILSpy.Search; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Docking +{ + /// + /// WPF-specific extensions to . + /// + partial class DockWorkspace + { + private DockingManager DockingManager => exportProvider.GetExportedValue(); + + void LayoutSerializationCallback(object sender, LayoutSerializationCallbackEventArgs e) + { + switch (e.Model) + { + case LayoutAnchorable la: + e.Content = this.ToolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); + e.Cancel = e.Content == null; + la.CanDockAsTabbedDocument = false; + if (e.Content is ToolPaneModel toolPaneModel) + { + e.Cancel = toolPaneModel.IsVisible; + toolPaneModel.IsVisible = true; + } + break; + default: + e.Cancel = true; + break; + } + } + + + public PaneModel ActivePane { + get => DockingManager.ActiveContent as PaneModel; + set => DockingManager.ActiveContent = value; + } + + public void InitializeLayout() + { + if (tabPages.Count == 0) + { + // Make sure there is at least one tab open + AddTabPage(); + } + + DockingManager.LayoutUpdateStrategy = this; + XmlLayoutSerializer serializer = new XmlLayoutSerializer(DockingManager); + serializer.LayoutSerializationCallback += LayoutSerializationCallback; + try + { + sessionSettings.DockLayout.Deserialize(serializer); + } + finally + { + serializer.LayoutSerializationCallback -= LayoutSerializationCallback; + } + + DockingManager.SetBinding(DockingManager.AnchorablesSourceProperty, new Binding(nameof(ToolPanes))); + DockingManager.SetBinding(DockingManager.DocumentsSourceProperty, new Binding(nameof(TabPages))); + } + + internal void ResetLayout() + { + foreach (var pane in ToolPanes) + { + pane.IsVisible = false; + } + CloseAllTabs(); + sessionSettings.DockLayout.Reset(); + InitializeLayout(); + + App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, () => MessageBus.Send(this, new ResetLayoutEventArgs())); + } + + static readonly PropertyInfo previousContainerProperty = typeof(LayoutContent).GetProperty("PreviousContainer", BindingFlags.NonPublic | BindingFlags.Instance); + + public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer) + { + if (!(anchorableToShow.Content is LegacyToolPaneModel legacyContent)) + return false; + anchorableToShow.CanDockAsTabbedDocument = false; + + LayoutAnchorablePane previousContainer; + switch (legacyContent.Location) + { + case LegacyToolPaneLocation.Top: + previousContainer = GetContainer(); + previousContainer.Children.Add(anchorableToShow); + return true; + case LegacyToolPaneLocation.Bottom: + previousContainer = GetContainer(); + previousContainer.Children.Add(anchorableToShow); + return true; + default: + return false; + } + + LayoutAnchorablePane GetContainer() + { + var anchorable = layout.Descendents().OfType().FirstOrDefault(x => x.Content is T) + ?? layout.Hidden.First(x => x.Content is T); + return (LayoutAnchorablePane)previousContainerProperty.GetValue(anchorable) ?? (LayoutAnchorablePane)anchorable.Parent; + } + } + + public void AfterInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableShown) + { + anchorableShown.IsActive = true; + anchorableShown.IsSelected = true; + } + + public bool BeforeInsertDocument(LayoutRoot layout, LayoutDocument anchorableToShow, ILayoutContainer destinationContainer) + { + return false; + } + + public void AfterInsertDocument(LayoutRoot layout, LayoutDocument anchorableShown) + { + } + } +} \ No newline at end of file diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index c68fa53213..f27a6034c5 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -125,8 +125,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -File BuildTools/sort-resx.ps1 - - + + diff --git a/ILSpy/ISmartTextOutput.cs b/ILSpy/ISmartTextOutput.cs index 5ceaff2597..62f283696a 100644 --- a/ILSpy/ISmartTextOutput.cs +++ b/ILSpy/ISmartTextOutput.cs @@ -18,13 +18,9 @@ using System; using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Decompiler; -using ICSharpCode.ILSpy.Themes; namespace ICSharpCode.ILSpy { @@ -46,38 +42,4 @@ public interface ISmartTextOutput : ITextOutput /// string Title { get; set; } } - - public static class SmartTextOutputExtensions - { - /// - /// Creates a button. - /// - public static void AddButton(this ISmartTextOutput output, ImageSource icon, string text, RoutedEventHandler click) - { - output.AddUIElement( - delegate { - Button button = ThemeManager.Current.CreateButton(); - button.Cursor = Cursors.Arrow; - button.Margin = new Thickness(2); - button.Padding = new Thickness(9, 1, 9, 1); - button.MinWidth = 73; - if (icon != null) - { - button.Content = new StackPanel { - Orientation = Orientation.Horizontal, - Children = { - new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) }, - new TextBlock { Text = text } - } - }; - } - else - { - button.Content = text; - } - button.Click += click; - return button; - }); - } - } } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 2e0ef396d8..bd8ba2864d 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -55,7 +55,7 @@ namespace ICSharpCode.ILSpy /// [Export(typeof(Language))] [Shared] - public class CSharpLanguage : Language + public partial class CSharpLanguage : Language { string name = "C#"; bool showAllMembers = false; @@ -387,44 +387,6 @@ void AddReferenceAssemblyWarningMessage(MetadataFile module, ITextOutput output) AddWarningMessage(module, output, line1); } - void AddWarningMessage(MetadataFile module, ITextOutput output, string line1, string line2 = null, - string buttonText = null, System.Windows.Media.ImageSource buttonImage = null, RoutedEventHandler buttonClickHandler = null) - { - if (output is ISmartTextOutput fancyOutput) - { - string text = line1; - if (!string.IsNullOrEmpty(line2)) - text += Environment.NewLine + line2; - fancyOutput.AddUIElement(() => new StackPanel { - Margin = new Thickness(5), - Orientation = Orientation.Horizontal, - Children = { - new Image { - Width = 32, - Height = 32, - Source = Images.Load(this, "Images/Warning") - }, - new TextBlock { - Margin = new Thickness(5, 0, 0, 0), - Text = text - } - } - }); - fancyOutput.WriteLine(); - if (buttonText != null && buttonClickHandler != null) - { - fancyOutput.AddButton(buttonImage, buttonText, buttonClickHandler); - fancyOutput.WriteLine(); - } - } - else - { - WriteCommentLine(output, line1); - if (!string.IsNullOrEmpty(line2)) - WriteCommentLine(output, line2); - } - } - public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options) { var module = assembly.GetMetadataFileOrNull(); diff --git a/ILSpy/Languages/CSharpLanguage.wpf.cs b/ILSpy/Languages/CSharpLanguage.wpf.cs new file mode 100644 index 0000000000..61d378a200 --- /dev/null +++ b/ILSpy/Languages/CSharpLanguage.wpf.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2011 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.Windows; +using System.Windows.Controls; + +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.ILSpy +{ + + /// + /// C# decompiler integration into ILSpy. + /// Note: if you're interested in using the decompiler without the ILSpy UI, + /// please directly use the CSharpDecompiler class. + /// + partial class CSharpLanguage + { + void AddWarningMessage(MetadataFile module, ITextOutput output, string line1, string line2 = null, + string buttonText = null, System.Windows.Media.ImageSource buttonImage = null, RoutedEventHandler buttonClickHandler = null) + { + if (output is ISmartTextOutput fancyOutput) + { + string text = line1; + if (!string.IsNullOrEmpty(line2)) + text += Environment.NewLine + line2; + fancyOutput.AddUIElement(() => new StackPanel { + Margin = new Thickness(5), + Orientation = Orientation.Horizontal, + Children = { + new Image { + Width = 32, + Height = 32, + Source = Images.Load(this, "Images/Warning") + }, + new TextBlock { + Margin = new Thickness(5, 0, 0, 0), + Text = text + } + } + }); + fancyOutput.WriteLine(); + if (buttonText != null && buttonClickHandler != null) + { + fancyOutput.AddButton(buttonImage, buttonText, buttonClickHandler); + fancyOutput.WriteLine(); + } + } + else + { + WriteCommentLine(output, line1); + if (!string.IsNullOrEmpty(line2)) + WriteCommentLine(output, line2); + } + } + } +} diff --git a/ILSpy/Metadata/DataDirectoriesTreeNode.cs b/ILSpy/Metadata/DataDirectoriesTreeNode.cs index 0b5d4f31c0..665258a3d9 100644 --- a/ILSpy/Metadata/DataDirectoriesTreeNode.cs +++ b/ILSpy/Metadata/DataDirectoriesTreeNode.cs @@ -36,10 +36,12 @@ public DataDirectoriesTreeNode(PEFile module) public override object Text => "Data Directories"; public override object NavigationText => $"{Text} ({module.Name})"; - +#if CROSS_PLATFORM + public override object Icon => Images.DirectoryTable; +#else public override object Icon => Images.ListFolder; public override object ExpandedIcon => Images.ListFolderOpen; - +#endif public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); diff --git a/ILSpy/Metadata/DebugDirectoryTreeNode.cs b/ILSpy/Metadata/DebugDirectoryTreeNode.cs index 6acab3d493..1050cdefc0 100644 --- a/ILSpy/Metadata/DebugDirectoryTreeNode.cs +++ b/ILSpy/Metadata/DebugDirectoryTreeNode.cs @@ -41,10 +41,12 @@ public DebugDirectoryTreeNode(PEFile module) public override object Text => "Debug Directory"; public override object NavigationText => $"{Text} ({module.Name})"; - +#if CROSS_PLATFORM + public override object Icon => Images.DirectoryTable; +#else public override object Icon => Images.ListFolder; public override object ExpandedIcon => Images.ListFolderOpen; - +#endif public override bool View(ViewModels.TabPageModel tabPage) { tabPage.Title = Text.ToString(); diff --git a/ILSpy/Metadata/FlagsContentFilter.cs b/ILSpy/Metadata/FlagsContentFilter.cs new file mode 100644 index 0000000000..1aa8b97aba --- /dev/null +++ b/ILSpy/Metadata/FlagsContentFilter.cs @@ -0,0 +1,22 @@ +using DataGridExtensions; + +namespace ICSharpCode.ILSpy.Metadata +{ + public class FlagsContentFilter : IContentFilter + { + public int Mask { get; } + + public FlagsContentFilter(int mask) + { + this.Mask = mask; + } + + public bool IsMatch(object value) + { + if (value == null) + return true; + + return Mask == -1 || (Mask & (int)value) != 0; + } + } +} diff --git a/ILSpy/Metadata/FlagsFilterControl.xaml.cs b/ILSpy/Metadata/FlagsFilterControl.xaml.cs index 3d81943344..38e8e665be 100644 --- a/ILSpy/Metadata/FlagsFilterControl.xaml.cs +++ b/ILSpy/Metadata/FlagsFilterControl.xaml.cs @@ -3,8 +3,6 @@ using System.Windows; using System.Windows.Controls; -using DataGridExtensions; - using ICSharpCode.Decompiler.Util; namespace ICSharpCode.ILSpy.Metadata @@ -104,22 +102,4 @@ private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e Filter = new FlagsContentFilter(mask); } } - - public class FlagsContentFilter : IContentFilter - { - public int Mask { get; } - - public FlagsContentFilter(int mask) - { - this.Mask = mask; - } - - public bool IsMatch(object value) - { - if (value == null) - return true; - - return Mask == -1 || (Mask & (int)value) != 0; - } - } } diff --git a/ILSpy/Metadata/Helper.wpf.cs b/ILSpy/Metadata/Helper.wpf.cs new file mode 100644 index 0000000000..ffa67ed0cc --- /dev/null +++ b/ILSpy/Metadata/Helper.wpf.cs @@ -0,0 +1,43 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; + +namespace ICSharpCode.ILSpy.Metadata +{ + static partial class Helpers + { + private static DataTemplate GetOrCreateLinkCellTemplate(string name, PropertyDescriptor descriptor, Binding binding) + { + if (linkCellTemplates.TryGetValue(name, out var template)) + { + return template; + } + + var tb = new FrameworkElementFactory(typeof(TextBlock)); + var hyper = new FrameworkElementFactory(typeof(Hyperlink)); + tb.AppendChild(hyper); + hyper.AddHandler(Hyperlink.ClickEvent, new RoutedEventHandler(Hyperlink_Click)); + var run = new FrameworkElementFactory(typeof(Run)); + hyper.AppendChild(run); + run.SetBinding(Run.TextProperty, binding); + + DataTemplate dataTemplate = new DataTemplate() { VisualTree = tb }; + linkCellTemplates.Add(name, dataTemplate); + return dataTemplate; + + void Hyperlink_Click(object sender, RoutedEventArgs e) + { + var hyperlink = (Hyperlink)sender; + var onClickMethod = descriptor.ComponentType.GetMethod("On" + name + "Click", BindingFlags.Instance | BindingFlags.Public); + if (onClickMethod != null) + { + onClickMethod.Invoke(hyperlink.DataContext, Array.Empty()); + } + } + } + } +} \ No newline at end of file diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index b192ce30d1..65ffb6f58b 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -46,7 +46,7 @@ namespace ICSharpCode.ILSpy.Metadata { - static class Helpers + static partial class Helpers { public static DataGrid PrepareDataGrid(TabPageModel tabPage, ILSpyTreeNode selectedNode) { @@ -71,7 +71,9 @@ public static DataGrid PrepareDataGrid(TabPageModel tabPage, ILSpyTreeNode selec ContextMenuProvider.Add(view); DataGridFilter.SetIsAutoFilterEnabled(view, true); DataGridFilter.SetContentFilterFactory(view, new RegexContentFilterFactory()); + #if !CROSS_PLATFORM AdvancedScrollWheelBehavior.SetAttach(view, AdvancedScrollWheelMode.WithoutAnimation); + #endif } DataGridFilter.GetFilter(view).Clear(); view.RowDetailsTemplateSelector = null; @@ -110,14 +112,23 @@ internal static void View_AutoGeneratingColumn(object sender, DataGridAutoGenera case "RVA": case "StartOffset": case "Length": + #if CROSS_PLATFORM + binding.Converter = HexFormatConverter.Instance; + #else binding.StringFormat = "X8"; + #endif e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]); break; case "RowDetails": e.Cancel = true; break; case "Value" when e.PropertyDescriptor is PropertyDescriptor dp && dp.ComponentType == typeof(Entry): + #if CROSS_PLATFORM + binding.Path = "."; + e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["DefaultFilter"]); + #else binding.Path = new PropertyPath("."); + #endif binding.Converter = ByteWidthConverter.Instance; break; default: @@ -165,36 +176,6 @@ DataGridColumn GetColumn() static readonly Dictionary linkCellTemplates = new Dictionary(); - private static DataTemplate GetOrCreateLinkCellTemplate(string name, PropertyDescriptor descriptor, Binding binding) - { - if (linkCellTemplates.TryGetValue(name, out var template)) - { - return template; - } - - var tb = new FrameworkElementFactory(typeof(TextBlock)); - var hyper = new FrameworkElementFactory(typeof(Hyperlink)); - tb.AppendChild(hyper); - hyper.AddHandler(Hyperlink.ClickEvent, new RoutedEventHandler(Hyperlink_Click)); - var run = new FrameworkElementFactory(typeof(Run)); - hyper.AppendChild(run); - run.SetBinding(Run.TextProperty, binding); - - DataTemplate dataTemplate = new DataTemplate() { VisualTree = tb }; - linkCellTemplates.Add(name, dataTemplate); - return dataTemplate; - - void Hyperlink_Click(object sender, RoutedEventArgs e) - { - var hyperlink = (Hyperlink)sender; - var onClickMethod = descriptor.ComponentType.GetMethod("On" + name + "Click", BindingFlags.Instance | BindingFlags.Public); - if (onClickMethod != null) - { - onClickMethod.Invoke(hyperlink.DataContext, Array.Empty()); - } - } - } - static void ApplyAttributes(PropertyDescriptor descriptor, Binding binding, DataGridColumn column) { if (descriptor.PropertyType.IsEnum) diff --git a/ILSpy/Metadata/HexFilterControl.ContentFilter.cs b/ILSpy/Metadata/HexFilterControl.ContentFilter.cs new file mode 100644 index 0000000000..c8f6b1a8bf --- /dev/null +++ b/ILSpy/Metadata/HexFilterControl.ContentFilter.cs @@ -0,0 +1,31 @@ +using System; + +using DataGridExtensions; + +namespace ICSharpCode.ILSpy.Metadata +{ + public partial class HexFilterControl + { + class ContentFilter : IContentFilter + { + readonly string filter; + + public ContentFilter(string filter) + { + this.filter = filter; + } + + public bool IsMatch(object value) + { + if (string.IsNullOrWhiteSpace(filter)) + return true; + if (value == null) + return false; + + return string.Format("{0:x8}", value).IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; + } + + public string Value => filter; + } + } +} diff --git a/ILSpy/Metadata/HexFilterControl.xaml.cs b/ILSpy/Metadata/HexFilterControl.xaml.cs index 9e41e2717f..ce8fe1b688 100644 --- a/ILSpy/Metadata/HexFilterControl.xaml.cs +++ b/ILSpy/Metadata/HexFilterControl.xaml.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -61,27 +60,5 @@ void Filter_Changed(object newValue) textBox.Text = (newValue as ContentFilter)?.Value ?? string.Empty; } - - class ContentFilter : IContentFilter - { - readonly string filter; - - public ContentFilter(string filter) - { - this.filter = filter; - } - - public bool IsMatch(object value) - { - if (string.IsNullOrWhiteSpace(filter)) - return true; - if (value == null) - return false; - - return string.Format("{0:x8}", value).IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; - } - - public string Value => filter; - } } } diff --git a/ILSpy/Metadata/MetadataTableTreeNode.cs b/ILSpy/Metadata/MetadataTableTreeNode.cs index ccc13c60de..2879085970 100644 --- a/ILSpy/Metadata/MetadataTableTreeNode.cs +++ b/ILSpy/Metadata/MetadataTableTreeNode.cs @@ -30,7 +30,7 @@ namespace ICSharpCode.ILSpy.Metadata { - internal abstract class MetadataTableTreeNode : ILSpyTreeNode + internal abstract partial class MetadataTableTreeNode : ILSpyTreeNode { protected readonly MetadataFile metadataFile; protected int scrollTarget; @@ -54,29 +54,6 @@ internal void ScrollTo(Handle handle) this.scrollTarget = metadataFile.Metadata.GetRowNumber((EntityHandle)handle); } - protected void ScrollRowIntoView(DataGrid view, int row) - { - if (!view.IsLoaded) - { - view.Loaded += View_Loaded; - } - else - { - View_Loaded(view, new System.Windows.RoutedEventArgs()); - } - if (view.Items.Count > row && row >= 0) - view.Dispatcher.BeginInvoke(() => view.SelectItem(view.Items[row]), DispatcherPriority.Background); - } - - private void View_Loaded(object sender, System.Windows.RoutedEventArgs e) - { - DataGrid view = (DataGrid)sender; - var sv = view.FindVisualChild(); - sv.ScrollToVerticalOffset(scrollTarget - 1); - view.Loaded -= View_Loaded; - this.scrollTarget = default; - } - protected static string GenerateTooltip(ref string tooltip, MetadataFile module, EntityHandle handle) { if (tooltip == null) @@ -202,4 +179,4 @@ public override void Decompile(Language language, ITextOutput output, Decompilat output.WriteLine($"Unsupported table '{(int)Kind:X2} {Kind}' contains {metadataFile.Metadata.GetTableRowCount(Kind)} rows."); } } -} \ No newline at end of file +} diff --git a/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs b/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs new file mode 100644 index 0000000000..ecb646becf --- /dev/null +++ b/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs @@ -0,0 +1,32 @@ +using System.Windows.Controls; +using System.Windows.Threading; + +namespace ICSharpCode.ILSpy.Metadata +{ + partial class MetadataTableTreeNode + { + + protected void ScrollRowIntoView(DataGrid view, int row) + { + if (!view.IsLoaded) + { + view.Loaded += View_Loaded; + } + else + { + View_Loaded(view, new System.Windows.RoutedEventArgs()); + } + if (view.Items.Count > row && row >= 0) + view.Dispatcher.BeginInvoke(() => view.SelectItem(view.Items[row]), DispatcherPriority.Background); + } + + private void View_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + DataGrid view = (DataGrid)sender; + var sv = view.FindVisualChild(); + sv.ScrollToVerticalOffset(scrollTarget - 1); + view.Loaded -= View_Loaded; + this.scrollTarget = default; + } + } +} \ No newline at end of file diff --git a/ILSpy/Metadata/MetadataTreeNode.cs b/ILSpy/Metadata/MetadataTreeNode.cs index b82a38eb13..771ed946b7 100644 --- a/ILSpy/Metadata/MetadataTreeNode.cs +++ b/ILSpy/Metadata/MetadataTreeNode.cs @@ -153,6 +153,9 @@ class ByteWidthConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + if (object.ReferenceEquals(value, null)) + return string.Empty; + return string.Format("{0:X" + 2 * ((Entry)value).Size + "}", ((Entry)value).Value); } diff --git a/ILSpy/Options/ExportOptionPageAttribute.cs b/ILSpy/Options/ExportOptionPageAttribute.cs new file mode 100644 index 0000000000..394c76f55b --- /dev/null +++ b/ILSpy/Options/ExportOptionPageAttribute.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2011 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; + +namespace ICSharpCode.ILSpy.Options +{ + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class)] + public sealed class ExportOptionPageAttribute() : ExportAttribute("OptionPages", typeof(IOptionPage)), IOptionsMetadata + { + public int Order { get; set; } + } +} \ No newline at end of file diff --git a/ILSpy/Options/IOptionPage.cs b/ILSpy/Options/IOptionPage.cs new file mode 100644 index 0000000000..c7b1b931a7 --- /dev/null +++ b/ILSpy/Options/IOptionPage.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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 ICSharpCode.ILSpy.Options +{ + public interface IOptionPage + { + string Title { get; } + + void Load(SettingsSnapshot settings); + + void LoadDefaults(); + } +} \ No newline at end of file diff --git a/ILSpy/Options/IOptionsMetadata.cs b/ILSpy/Options/IOptionsMetadata.cs new file mode 100644 index 0000000000..98a3af20b9 --- /dev/null +++ b/ILSpy/Options/IOptionsMetadata.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2011 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 ICSharpCode.ILSpy.Options +{ + public interface IOptionsMetadata + { + int Order { get; } + } +} \ No newline at end of file diff --git a/ILSpy/Options/OptionsDialog.xaml.cs b/ILSpy/Options/OptionsDialog.xaml.cs index 4b4ab131a6..5edfaa2cb2 100644 --- a/ILSpy/Options/OptionsDialog.xaml.cs +++ b/ILSpy/Options/OptionsDialog.xaml.cs @@ -35,42 +35,4 @@ public OptionsDialog(SettingsService settingsService) InitializeComponent(); } } - - public interface IOptionsMetadata - { - int Order { get; } - } - - public interface IOptionPage - { - string Title { get; } - - void Load(SettingsSnapshot settings); - - void LoadDefaults(); - } - - [MetadataAttribute] - [AttributeUsage(AttributeTargets.Class)] - public sealed class ExportOptionPageAttribute() : ExportAttribute("OptionPages", typeof(IOptionPage)), IOptionsMetadata - { - public int Order { get; set; } - } - - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)] - [Shared] - sealed class ShowOptionsCommand(AssemblyTreeModel assemblyTreeModel, SettingsService settingsService, MainWindow mainWindow) : SimpleCommand - { - public override void Execute(object parameter) - { - OptionsDialog dlg = new(settingsService) { - Owner = mainWindow - }; - - if (dlg.ShowDialog() == true) - { - assemblyTreeModel.Refresh(); - } - } - } } \ No newline at end of file diff --git a/ILSpy/Options/ShowOptionsCommand.cs b/ILSpy/Options/ShowOptionsCommand.cs new file mode 100644 index 0000000000..50e173430c --- /dev/null +++ b/ILSpy/Options/ShowOptionsCommand.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2011 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.Composition; + +using ICSharpCode.ILSpy.AssemblyTree; +using ICSharpCode.ILSpy.Properties; + +namespace ICSharpCode.ILSpy.Options +{ + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)] + [Shared] + sealed class ShowOptionsCommand(AssemblyTreeModel assemblyTreeModel, SettingsService settingsService, MainWindow mainWindow) : SimpleCommand + { + public override void Execute(object parameter) + { + OptionsDialog dlg = new(settingsService); + + #if CROSS_PLATFORM + // On cross-platform schedule showing the dialog on the UI dispatcher + // and await its result asynchronously. Do not block the calling thread. + System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(async () => { + bool? asyncResult = await dlg.ShowDialogAsync(mainWindow); + if (asyncResult == true) + { + System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(() => assemblyTreeModel.Refresh()); + } + }); + #else + // WPF path: set owner and show dialog synchronously as before. + dlg.Owner = mainWindow; + bool? result = dlg.ShowDialog(); + if (result == true) + { + assemblyTreeModel.Refresh(); + } + #endif + } + } +} \ No newline at end of file diff --git a/ILSpy/Search/SearchPane.xaml.cs b/ILSpy/Search/SearchPane.xaml.cs index 4659c2a484..0644e4e7cb 100644 --- a/ILSpy/Search/SearchPane.xaml.cs +++ b/ILSpy/Search/SearchPane.xaml.cs @@ -34,7 +34,6 @@ using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AssemblyTree; -using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Abstractions; @@ -579,27 +578,4 @@ AbstractSearchStrategy GetSearchStrategy(SearchRequest request) } } } - - [ExportToolbarCommand(ToolTip = nameof(Properties.Resources.SearchCtrlShiftFOrCtrlE), ToolbarIcon = "Images/Search", ToolbarCategory = nameof(Properties.Resources.View), ToolbarOrder = 100)] - [Shared] - sealed class ShowSearchCommand : CommandWrapper - { - private readonly DockWorkspace dockWorkspace; - - public ShowSearchCommand(DockWorkspace dockWorkspace) - : base(NavigationCommands.Search) - { - this.dockWorkspace = dockWorkspace; - var gestures = NavigationCommands.Search.InputGestures; - - gestures.Clear(); - gestures.Add(new KeyGesture(Key.F, ModifierKeys.Control | ModifierKeys.Shift)); - gestures.Add(new KeyGesture(Key.E, ModifierKeys.Control)); - } - - protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) - { - dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId); - } - } } \ No newline at end of file diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index 4cf4e8b809..aee16d4e65 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -32,9 +32,10 @@ public class SearchModeModel public ImageSource Image { get; init; } } + [Export] [ExportToolPane] [Shared] - public class SearchPaneModel : ToolPaneModel + public partial class SearchPaneModel : ToolPaneModel { public const string PaneContentId = "searchPane"; diff --git a/ILSpy/Search/ShowSearchCommand.cs b/ILSpy/Search/ShowSearchCommand.cs new file mode 100644 index 0000000000..3a32be8faf --- /dev/null +++ b/ILSpy/Search/ShowSearchCommand.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2011 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 System.Windows.Input; + +using ICSharpCode.ILSpy.Docking; + +namespace ICSharpCode.ILSpy.Search +{ + [ExportToolbarCommand(ToolTip = nameof(Properties.Resources.SearchCtrlShiftFOrCtrlE), ToolbarIcon = "Images/Search", ToolbarCategory = nameof(Properties.Resources.View), ToolbarOrder = 100)] + [Shared] + sealed class ShowSearchCommand : CommandWrapper + { + private readonly DockWorkspace dockWorkspace; + + public ShowSearchCommand(DockWorkspace dockWorkspace) + : base(NavigationCommands.Search) + { + this.dockWorkspace = dockWorkspace; + var gestures = NavigationCommands.Search.InputGestures; + + gestures.Clear(); + gestures.Add(new KeyGesture(Key.F, ModifierKeys.Control | ModifierKeys.Shift)); + gestures.Add(new KeyGesture(Key.E, ModifierKeys.Control)); + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + Console.WriteLine($"ShowSearchCommand: Executing ShowToolPane for SearchPaneModel.PaneContentId using dockWorkspace type: {dockWorkspace?.GetType().FullName}"); + var result = dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId); + Console.WriteLine($"ShowSearchCommand: ShowToolPane returned: {result}"); + } + } +} \ No newline at end of file diff --git a/ILSpy/SmartTextOutputExtensions.cs b/ILSpy/SmartTextOutputExtensions.cs new file mode 100644 index 0000000000..9651a3e1a0 --- /dev/null +++ b/ILSpy/SmartTextOutputExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2011 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.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +using ICSharpCode.ILSpy.Themes; + +namespace ICSharpCode.ILSpy +{ + public static class SmartTextOutputExtensions + { + /// + /// Creates a button. + /// + public static void AddButton(this ISmartTextOutput output, ImageSource icon, string text, RoutedEventHandler click) + { + output.AddUIElement( + delegate { + Button button = ThemeManager.Current.CreateButton(); + button.Cursor = Cursors.Arrow; + button.Margin = new Thickness(2); + button.Padding = new Thickness(9, 1, 9, 1); + button.MinWidth = 73; + if (icon != null) + { + button.Content = new StackPanel { + Orientation = Orientation.Horizontal, + Children = { + new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) }, + new TextBlock { Text = text } + } + }; + } + else + { + button.Content = text; + } + button.Click += click; + return button; + }); + } + } +} diff --git a/ILSpy/TextView/BracketHighlightRenderer.cs b/ILSpy/TextView/BracketHighlightRenderer.cs index 4046c333a4..258034e8ad 100644 --- a/ILSpy/TextView/BracketHighlightRenderer.cs +++ b/ILSpy/TextView/BracketHighlightRenderer.cs @@ -24,50 +24,6 @@ namespace ICSharpCode.ILSpy.TextView { - /// - /// Allows language specific search for matching brackets. - /// - public interface IBracketSearcher - { - /// - /// Searches for a matching bracket from the given offset to the start of the document. - /// - /// A BracketSearchResult that contains the positions and lengths of the brackets. Return null if there is nothing to highlight. - BracketSearchResult SearchBracket(IDocument document, int offset); - } - - public class DefaultBracketSearcher : IBracketSearcher - { - public static readonly DefaultBracketSearcher DefaultInstance = new DefaultBracketSearcher(); - - public BracketSearchResult SearchBracket(IDocument document, int offset) - { - return null; - } - } - - /// - /// Describes a pair of matching brackets found by . - /// - public class BracketSearchResult - { - public int OpeningBracketOffset { get; private set; } - - public int OpeningBracketLength { get; private set; } - - public int ClosingBracketOffset { get; private set; } - - public int ClosingBracketLength { get; private set; } - - public BracketSearchResult(int openingBracketOffset, int openingBracketLength, - int closingBracketOffset, int closingBracketLength) - { - this.OpeningBracketOffset = openingBracketOffset; - this.OpeningBracketLength = openingBracketLength; - this.ClosingBracketOffset = closingBracketOffset; - this.ClosingBracketLength = closingBracketLength; - } - } public class BracketHighlightRenderer : IBackgroundRenderer { @@ -92,8 +48,14 @@ public BracketHighlightRenderer(ICSharpCode.AvalonEdit.Rendering.TextView textVi if (textView == null) throw new ArgumentNullException("textView"); - this.borderPen = (Pen)textView.FindResource(Themes.ResourceKeys.BracketHighlightBorderPen); - this.backgroundBrush = (SolidColorBrush)textView.FindResource(Themes.ResourceKeys.BracketHighlightBackgroundBrush); + // resource loading safe guard + var borderPenResource = textView.FindResource(Themes.ResourceKeys.BracketHighlightBorderPen); + if (borderPenResource is Pen p) + this.borderPen = p; + + var backgroundBrushResource = textView.FindResource(Themes.ResourceKeys.BracketHighlightBackgroundBrush); + if (backgroundBrushResource is SolidColorBrush b) + this.backgroundBrush = b; this.textView = textView; diff --git a/ILSpy/TextView/BracketSearchResult.cs b/ILSpy/TextView/BracketSearchResult.cs new file mode 100644 index 0000000000..293613db0b --- /dev/null +++ b/ILSpy/TextView/BracketSearchResult.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2014 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 ICSharpCode.ILSpy.TextView +{ + /// + /// Describes a pair of matching brackets found by . + /// + public class BracketSearchResult + { + public int OpeningBracketOffset { get; private set; } + + public int OpeningBracketLength { get; private set; } + + public int ClosingBracketOffset { get; private set; } + + public int ClosingBracketLength { get; private set; } + + public BracketSearchResult(int openingBracketOffset, int openingBracketLength, + int closingBracketOffset, int closingBracketLength) + { + this.OpeningBracketOffset = openingBracketOffset; + this.OpeningBracketLength = openingBracketLength; + this.ClosingBracketOffset = closingBracketOffset; + this.ClosingBracketLength = closingBracketLength; + } + } +} \ No newline at end of file diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 06169ed3e0..50e553dc9c 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -1357,34 +1357,6 @@ public FoldingManager? FoldingManager { #endregion } - [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] - public class ViewState : IEquatable - { - public HashSet? DecompiledNodes; - public Uri? ViewedUri; - - public virtual bool Equals(ViewState? other) - { - return other != null - && ViewedUri == other.ViewedUri - && NullSafeSetEquals(DecompiledNodes, other.DecompiledNodes); - - static bool NullSafeSetEquals(HashSet? a, HashSet? b) - { - if (a == b) - return true; - if (a == null || b == null) - return false; - return a.SetEquals(b); - } - } - - protected virtual string GetDebuggerDisplay() - { - return $"Nodes = {DecompiledNodes?.Count.ToString() ?? ""}, ViewedUri = {ViewedUri?.ToString() ?? ""}"; - } - } - public class DecompilerTextViewState : ViewState { private List<(int StartOffset, int EndOffset)>? ExpandedFoldings; diff --git a/ILSpy/TextView/DefaultBracketSearcher.cs b/ILSpy/TextView/DefaultBracketSearcher.cs new file mode 100644 index 0000000000..3481984b9f --- /dev/null +++ b/ILSpy/TextView/DefaultBracketSearcher.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2014 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.AvalonEdit.Document; + +namespace ICSharpCode.ILSpy.TextView +{ + public class DefaultBracketSearcher : IBracketSearcher + { + public static readonly DefaultBracketSearcher DefaultInstance = new DefaultBracketSearcher(); + + public BracketSearchResult SearchBracket(IDocument document, int offset) + { + return null; + } + } +} \ No newline at end of file diff --git a/ILSpy/TextView/IBracketSearcher.cs b/ILSpy/TextView/IBracketSearcher.cs new file mode 100644 index 0000000000..51da2da1e4 --- /dev/null +++ b/ILSpy/TextView/IBracketSearcher.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2014 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.AvalonEdit.Document; + +namespace ICSharpCode.ILSpy.TextView +{ + /// + /// Allows language specific search for matching brackets. + /// + public interface IBracketSearcher + { + /// + /// Searches for a matching bracket from the given offset to the start of the document. + /// + /// A BracketSearchResult that contains the positions and lengths of the brackets. Return null if there is nothing to highlight. + BracketSearchResult SearchBracket(IDocument document, int offset); + } +} \ No newline at end of file diff --git a/ILSpy/TextView/ReferenceElementGenerator.cs b/ILSpy/TextView/ReferenceElementGenerator.cs index 650b2485f8..47735e04ed 100644 --- a/ILSpy/TextView/ReferenceElementGenerator.cs +++ b/ILSpy/TextView/ReferenceElementGenerator.cs @@ -72,37 +72,4 @@ public override VisualLineElement ConstructElement(int offset) return null; } } - - /// - /// VisualLineElement that represents a piece of text and is a clickable link. - /// - sealed class VisualLineReferenceText : VisualLineText - { - readonly ReferenceElementGenerator parent; - readonly ReferenceSegment referenceSegment; - - /// - /// Creates a visual line text element with the specified length. - /// It uses the and its - /// to find the actual text string. - /// - public VisualLineReferenceText(VisualLine parentVisualLine, int length, ReferenceElementGenerator parent, ReferenceSegment referenceSegment) : base(parentVisualLine, length) - { - this.parent = parent; - this.referenceSegment = referenceSegment; - } - - /// - protected override void OnQueryCursor(QueryCursorEventArgs e) - { - e.Handled = true; - e.Cursor = referenceSegment.IsLocal ? Cursors.Arrow : Cursors.Hand; - } - - /// - protected override VisualLineText CreateInstance(int length) - { - return new VisualLineReferenceText(ParentVisualLine, length, parent, referenceSegment); - } - } } diff --git a/ILSpy/TextView/ViewState.cs b/ILSpy/TextView/ViewState.cs new file mode 100644 index 0000000000..869106cce3 --- /dev/null +++ b/ILSpy/TextView/ViewState.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2011 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. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using ICSharpCode.ILSpy.TreeNodes; + +namespace ICSharpCode.ILSpy.TextView +{ + [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] + public class ViewState : IEquatable + { + public HashSet? DecompiledNodes; + public Uri? ViewedUri; + + public virtual bool Equals(ViewState? other) + { + return other != null + && ViewedUri == other.ViewedUri + && NullSafeSetEquals(DecompiledNodes, other.DecompiledNodes); + + static bool NullSafeSetEquals(HashSet? a, HashSet? b) + { + if (a == b) + return true; + if (a == null || b == null) + return false; + return a.SetEquals(b); + } + } + + protected virtual string GetDebuggerDisplay() + { + return $"Nodes = {DecompiledNodes?.Count.ToString() ?? ""}, ViewedUri = {ViewedUri?.ToString() ?? ""}"; + } + } +} diff --git a/ILSpy/TextView/VisualLineReferenceText.cs b/ILSpy/TextView/VisualLineReferenceText.cs new file mode 100644 index 0000000000..c94d4e2d65 --- /dev/null +++ b/ILSpy/TextView/VisualLineReferenceText.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2011 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.Windows.Input; + +using ICSharpCode.AvalonEdit.Rendering; + +namespace ICSharpCode.ILSpy.TextView +{ + /// + /// VisualLineElement that represents a piece of text and is a clickable link. + /// + sealed partial class VisualLineReferenceText : VisualLineText + { + readonly ReferenceElementGenerator parent; + readonly ReferenceSegment referenceSegment; + + /// + /// Creates a visual line text element with the specified length. + /// It uses the and its + /// to find the actual text string. + /// + public VisualLineReferenceText(VisualLine parentVisualLine, int length, ReferenceElementGenerator parent, ReferenceSegment referenceSegment) : base(parentVisualLine, length) + { + this.parent = parent; + this.referenceSegment = referenceSegment; + } + + /// + protected override VisualLineText CreateInstance(int length) + { + return new VisualLineReferenceText(ParentVisualLine, length, parent, referenceSegment); + } + } +} diff --git a/ILSpy/TextView/VisualLineReferenceText.wpf.cs b/ILSpy/TextView/VisualLineReferenceText.wpf.cs new file mode 100644 index 0000000000..4cda0f7310 --- /dev/null +++ b/ILSpy/TextView/VisualLineReferenceText.wpf.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2011 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.Windows.Input; + +using ICSharpCode.AvalonEdit.Rendering; + +namespace ICSharpCode.ILSpy.TextView +{ + /// + /// VisualLineElement that represents a piece of text and is a clickable link. + /// + partial class VisualLineReferenceText + { + /// + protected override void OnQueryCursor(QueryCursorEventArgs e) + { + e.Handled = true; + e.Cursor = referenceSegment.IsLocal ? Cursors.Arrow : Cursors.Hand; + } + } +} \ No newline at end of file diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 2150edbbdf..a41c78ae98 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -140,30 +140,50 @@ public override object ToolTip { var metadata = module?.Metadata; if (metadata?.IsAssembly == true && metadata.TryGetFullAssemblyName(out var assemblyName)) { +#if CROSS_PLATFORM + tooltip.Inlines.Add(new Bold().Add(new Run("Name: "))); +#else tooltip.Inlines.Add(new Bold(new Run("Name: "))); +#endif tooltip.Inlines.Add(new Run(assemblyName)); tooltip.Inlines.Add(new LineBreak()); } +#if CROSS_PLATFORM + tooltip.Inlines.Add(new Bold().Add(new Run("Location: "))); +#else tooltip.Inlines.Add(new Bold(new Run("Location: "))); +#endif tooltip.Inlines.Add(new Run(LoadedAssembly.FileName)); if (module != null) { if (module is PEFile peFile) { tooltip.Inlines.Add(new LineBreak()); +#if CROSS_PLATFORM + tooltip.Inlines.Add(new Bold().Add(new Run("Architecture: "))); +#else tooltip.Inlines.Add(new Bold(new Run("Architecture: "))); +#endif tooltip.Inlines.Add(new Run(Language.GetPlatformDisplayName(peFile))); } string runtimeName = Language.GetRuntimeDisplayName(module); if (runtimeName != null) { tooltip.Inlines.Add(new LineBreak()); +#if CROSS_PLATFORM + tooltip.Inlines.Add(new Bold().Add(new Run("Runtime: "))); +#else tooltip.Inlines.Add(new Bold(new Run("Runtime: "))); +#endif tooltip.Inlines.Add(new Run(runtimeName)); } var debugInfo = LoadedAssembly.GetDebugInfoOrNull(); tooltip.Inlines.Add(new LineBreak()); +#if CROSS_PLATFORM + tooltip.Inlines.Add(new Bold().Add(new Run("Debug info: "))); +#else tooltip.Inlines.Add(new Bold(new Run("Debug info: "))); +#endif tooltip.Inlines.Add(new Run(debugInfo?.Description ?? "none")); } } @@ -742,7 +762,7 @@ public void Execute(TextViewContext context) var path = Path.GetDirectoryName(node.LoadedAssembly.FileName); if (Directory.Exists(path)) { - GlobalUtils.ExecuteCommand("cmd.exe", $"/k \"cd /d {path}\""); + GlobalUtils.OpenTerminalAt(path); } } } diff --git a/ILSpy/TreeNodes/ResourceListTreeNode.cs b/ILSpy/TreeNodes/ResourceListTreeNode.cs index 1746528176..90befaa4ca 100644 --- a/ILSpy/TreeNodes/ResourceListTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceListTreeNode.cs @@ -42,11 +42,25 @@ public ResourceListTreeNode(MetadataFile module) public override object Text => Resources._Resources; public override object NavigationText => $"{Text} ({module.Name})"; +#if CROSS_PLATFORM + public override object Icon => IsExpanded ? Images.FolderOpen : Images.FolderClosed; + protected override void OnExpanding() + { + base.OnExpanding(); + RaisePropertyChanged(nameof(Icon)); + } + + protected override void OnCollapsing() + { + base.OnCollapsing(); + RaisePropertyChanged(nameof(Icon)); + } +#else public override object Icon => Images.FolderClosed; public override object ExpandedIcon => Images.FolderOpen; - +#endif protected override void LoadChildren() { foreach (Resource r in module.Resources.OrderBy(m => m.Name, NaturalStringComparer.Instance)) diff --git a/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.cs index f33d3126cb..8d7cb859aa 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.cs @@ -20,7 +20,6 @@ using System.Composition; using System.IO; using System.Windows.Controls; -using System.Windows.Media.Imaging; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Properties; @@ -48,7 +47,7 @@ public ITreeNode CreateNode(Resource resource) } } - sealed class ImageResourceEntryNode : ResourceEntryNode + sealed partial class ImageResourceEntryNode : ResourceEntryNode { public ImageResourceEntryNode(string key, Func openStream) : base(key, openStream) @@ -56,29 +55,5 @@ public ImageResourceEntryNode(string key, Func openStream) } public override object Icon => Images.ResourceImage; - - public override bool View(TabPageModel tabPage) - { - try - { - AvalonEditTextOutput output = new AvalonEditTextOutput(); - BitmapImage image = new BitmapImage(); - image.BeginInit(); - image.StreamSource = OpenStream(); - image.EndInit(); - output.AddUIElement(() => new Image { Source = image }); - output.WriteLine(); - output.AddButton(Images.Save, Resources.Save, delegate { - Save(null); - }); - tabPage.ShowTextView(textView => textView.ShowNode(output, this)); - tabPage.SupportsLanguageSwitching = false; - return true; - } - catch (Exception) - { - return false; - } - } } } diff --git a/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.wpf.cs b/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.wpf.cs new file mode 100644 index 0000000000..1f2e54e106 --- /dev/null +++ b/ILSpy/TreeNodes/ResourceNodes/ImageResourceEntryNode.wpf.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2011 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.Windows.Controls; +using System.Windows.Media.Imaging; + +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.TreeNodes +{ + partial class ImageResourceEntryNode + { + public override bool View(TabPageModel tabPage) + { + try + { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + BitmapImage image = new BitmapImage(); + image.BeginInit(); + image.StreamSource = OpenStream(); + image.EndInit(); + output.AddUIElement(() => new Image { Source = image }); + output.WriteLine(); + output.AddButton(Images.Save, Resources.Save, delegate { + Save(null); + }); + tabPage.ShowTextView(textView => textView.ShowNode(output, this)); + tabPage.SupportsLanguageSwitching = false; + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/ILSpy/Util/GlobalUtils.cs b/ILSpy/Util/GlobalUtils.cs index b4c64a736d..6a98df376d 100644 --- a/ILSpy/Util/GlobalUtils.cs +++ b/ILSpy/Util/GlobalUtils.cs @@ -21,7 +21,7 @@ namespace ICSharpCode.ILSpy.Util { - static class GlobalUtils + static partial class GlobalUtils { public static void OpenLink(string link) { diff --git a/ILSpy/Util/GlobalUtils.wpf.cs b/ILSpy/Util/GlobalUtils.wpf.cs new file mode 100644 index 0000000000..a35b89f558 --- /dev/null +++ b/ILSpy/Util/GlobalUtils.wpf.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2024 Tom Englert 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 ICSharpCode.ILSpy.Util +{ + partial class GlobalUtils + { + public static void OpenTerminalAt(string path) + { + ExecuteCommand("cmd.exe", $"/k \"cd /d {path}\""); + } + } +} \ No newline at end of file diff --git a/ILSpy/ViewModels/Pane.cs b/ILSpy/ViewModels/Pane.cs new file mode 100644 index 0000000000..9493dd16aa --- /dev/null +++ b/ILSpy/ViewModels/Pane.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2019 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.Windows; + +namespace ICSharpCode.ILSpy.ViewModels +{ + public static class Pane + { + // Helper properties to enable binding state properties from the model to the view. + + public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached( + "IsActive", typeof(bool), typeof(Pane), new FrameworkPropertyMetadata(default(bool))); + public static void SetIsActive(DependencyObject element, bool value) + { + element.SetValue(IsActiveProperty, value); + } + public static bool GetIsActive(DependencyObject element) + { + return (bool)element.GetValue(IsActiveProperty); + } + + public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.RegisterAttached( + "IsVisible", typeof(bool), typeof(Pane), new FrameworkPropertyMetadata(default(bool))); + public static void SetIsVisible(DependencyObject element, bool value) + { + element.SetValue(IsVisibleProperty, value); + } + public static bool GetIsVisible(DependencyObject element) + { + return (bool)element.GetValue(IsVisibleProperty); + } + } +} diff --git a/ILSpy/ViewModels/PaneModel.cs b/ILSpy/ViewModels/PaneModel.cs index 7c2633e7d7..a0939cf1ff 100644 --- a/ILSpy/ViewModels/PaneModel.cs +++ b/ILSpy/ViewModels/PaneModel.cs @@ -18,7 +18,6 @@ using System; using System.ComponentModel; -using System.Windows; using System.Windows.Input; using ICSharpCode.ILSpy.Docking; @@ -122,31 +121,4 @@ public string Title { } } } - - public static class Pane - { - // Helper properties to enable binding state properties from the model to the view. - - public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached( - "IsActive", typeof(bool), typeof(Pane), new FrameworkPropertyMetadata(default(bool))); - public static void SetIsActive(DependencyObject element, bool value) - { - element.SetValue(IsActiveProperty, value); - } - public static bool GetIsActive(DependencyObject element) - { - return (bool)element.GetValue(IsActiveProperty); - } - - public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.RegisterAttached( - "IsVisible", typeof(bool), typeof(Pane), new FrameworkPropertyMetadata(default(bool))); - public static void SetIsVisible(DependencyObject element, bool value) - { - element.SetValue(IsVisibleProperty, value); - } - public static bool GetIsVisible(DependencyObject element) - { - return (bool)element.GetValue(IsVisibleProperty); - } - } } diff --git a/ILSpy/ViewModels/TabPageModel.cs b/ILSpy/ViewModels/TabPageModel.cs index ece302b4c0..74e06632fe 100644 --- a/ILSpy/ViewModels/TabPageModel.cs +++ b/ILSpy/ViewModels/TabPageModel.cs @@ -16,13 +16,10 @@ // 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 System.Linq; -using System.Threading.Tasks; using System.Windows; -using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.TextView; using TomsToolbox.Composition; @@ -34,8 +31,14 @@ namespace ICSharpCode.ILSpy.ViewModels { [Export] [NonShared] +#if CROSS_PLATFORM + public class TabPageModel : Dock.Model.TomsToolbox.Controls.Document + { + protected static DockWorkspace DockWorkspace => App.ExportProvider.GetExportedValue(); +#else public class TabPageModel : PaneModel { +#endif public IExportProvider ExportProvider { get; } public TabPageModel(IExportProvider exportProvider) @@ -71,80 +74,6 @@ public object? Content { } } - public static class TabPageModelExtensions - { - public static Task ShowTextViewAsync(this TabPageModel tabPage, Func> action) - { - if (tabPage.Content is not DecompilerTextView textView) - { - textView = new DecompilerTextView(tabPage.ExportProvider); - tabPage.Content = textView; - } - tabPage.Title = Properties.Resources.Decompiling; - return action(textView); - } - - public static Task ShowTextViewAsync(this TabPageModel tabPage, Func action) - { - if (tabPage.Content is not DecompilerTextView textView) - { - textView = new DecompilerTextView(tabPage.ExportProvider); - tabPage.Content = textView; - } - string oldTitle = tabPage.Title; - tabPage.Title = Properties.Resources.Decompiling; - try - { - return action(textView); - } - finally - { - if (tabPage.Title == Properties.Resources.Decompiling) - { - tabPage.Title = oldTitle; - } - } - } - - public static void ShowTextView(this TabPageModel tabPage, Action action) - { - if (tabPage.Content is not DecompilerTextView textView) - { - textView = new DecompilerTextView(tabPage.ExportProvider); - tabPage.Content = textView; - } - string oldTitle = tabPage.Title; - tabPage.Title = Properties.Resources.Decompiling; - action(textView); - if (tabPage.Title == Properties.Resources.Decompiling) - { - tabPage.Title = oldTitle; - } - } - - public static void Focus(this TabPageModel tabPage) - { - if (tabPage.Content is not FrameworkElement content) - return; - - var focusable = content - .VisualDescendantsAndSelf() - .OfType() - .FirstOrDefault(item => item.Focusable); - - focusable?.Focus(); - } - - public static DecompilationOptions CreateDecompilationOptions(this TabPageModel tabPage) - { - var exportProvider = tabPage.ExportProvider; - var languageService = exportProvider.GetExportedValue(); - var settingsService = exportProvider.GetExportedValue(); - - return new(languageService.LanguageVersion, settingsService.DecompilerSettings, settingsService.DisplaySettings) { Progress = tabPage.Content as IProgress }; - } - } - public interface IHaveState { ViewState? GetState(); diff --git a/ILSpy/ViewModels/TabPageModelExtensions.cs b/ILSpy/ViewModels/TabPageModelExtensions.cs new file mode 100644 index 0000000000..f57b63b63d --- /dev/null +++ b/ILSpy/ViewModels/TabPageModelExtensions.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2019 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.Threading.Tasks; + +using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.TextView; + +#nullable enable + +namespace ICSharpCode.ILSpy.ViewModels +{ + public static partial class TabPageModelExtensions + { + public static Task ShowTextViewAsync(this TabPageModel tabPage, Func> action) + { + if (tabPage.Content is not DecompilerTextView textView) + { + textView = new DecompilerTextView(tabPage.ExportProvider); + tabPage.Content = textView; + } + tabPage.Title = Properties.Resources.Decompiling; + return action(textView); + } + + public static Task ShowTextViewAsync(this TabPageModel tabPage, Func action) + { + if (tabPage.Content is not DecompilerTextView textView) + { + textView = new DecompilerTextView(tabPage.ExportProvider); + tabPage.Content = textView; + } + string oldTitle = tabPage.Title; + tabPage.Title = Properties.Resources.Decompiling; + try + { + return action(textView); + } + finally + { + if (tabPage.Title == Properties.Resources.Decompiling) + { + tabPage.Title = oldTitle; + } + } + } + + public static void ShowTextView(this TabPageModel tabPage, Action action) + { + if (tabPage.Content is not DecompilerTextView textView) + { + textView = new DecompilerTextView(tabPage.ExportProvider); + tabPage.Content = textView; + } + string oldTitle = tabPage.Title; + tabPage.Title = Properties.Resources.Decompiling; + action(textView); + if (tabPage.Title == Properties.Resources.Decompiling) + { + tabPage.Title = oldTitle; + } + } + + public static DecompilationOptions CreateDecompilationOptions(this TabPageModel tabPage) + { + var exportProvider = tabPage.ExportProvider; + var languageService = exportProvider.GetExportedValue(); + var settingsService = exportProvider.GetExportedValue(); + + return new(languageService.LanguageVersion, settingsService.DecompilerSettings, settingsService.DisplaySettings) { Progress = tabPage.Content as IProgress }; + } + } +} \ No newline at end of file diff --git a/ILSpy/ViewModels/TabPageModelExtensions.wpf.cs b/ILSpy/ViewModels/TabPageModelExtensions.wpf.cs new file mode 100644 index 0000000000..c2bdad079f --- /dev/null +++ b/ILSpy/ViewModels/TabPageModelExtensions.wpf.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2019 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.Windows; + +using TomsToolbox.Wpf; + +#nullable enable + + +namespace ICSharpCode.ILSpy.ViewModels +{ + public static partial class TabPageModelExtensions + { + public static void Focus(this TabPageModel tabPage) + { + if (tabPage.Content is not FrameworkElement content) + return; + + var focusable = content + .VisualDescendantsAndSelf() + .OfType() + .FirstOrDefault(item => item.Focusable); + + focusable?.Focus(); + } + } +} diff --git a/ILSpy/ViewModels/ToolPaneModel.cs b/ILSpy/ViewModels/ToolPaneModel.cs index ba75fc8617..5624aa79e1 100644 --- a/ILSpy/ViewModels/ToolPaneModel.cs +++ b/ILSpy/ViewModels/ToolPaneModel.cs @@ -20,12 +20,21 @@ namespace ICSharpCode.ILSpy.ViewModels { +#if CROSS_PLATFORM + public abstract class ToolPaneModel : Dock.Model.TomsToolbox.Controls.Tool + { + protected static DockWorkspace DockWorkspace => App.ExportProvider.GetExportedValue(); +#else public abstract class ToolPaneModel : PaneModel { +#endif public virtual void Show() { this.IsActive = true; this.IsVisible = true; +#if CROSS_PLATFORM + DockWorkspace.ActivateToolPane(ContentId); +#endif } public KeyGesture ShortcutKey { get; protected set; } diff --git a/ILSpy/Views/GacEntry.cs b/ILSpy/Views/GacEntry.cs new file mode 100644 index 0000000000..5a76fa3f9c --- /dev/null +++ b/ILSpy/Views/GacEntry.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2011 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.Text; + +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.ILSpy +{ + public partial class OpenFromGacDialog + { + sealed class GacEntry + { + readonly AssemblyNameReference r; + readonly string fileName; + string formattedVersion; + + public GacEntry(AssemblyNameReference r, string fileName) + { + this.r = r; + this.fileName = fileName; + } + + public string FullName { + get { return r.FullName; } + } + + public string ShortName { + get { return r.Name; } + } + + public string FileName { + get { return fileName; } + } + + public Version Version { + get { return r.Version; } + } + + public string FormattedVersion { + get { + if (formattedVersion == null) + formattedVersion = Version.ToString(); + return formattedVersion; + } + } + + public string Culture { + get { + if (string.IsNullOrEmpty(r.Culture)) + return "neutral"; + return r.Culture; + } + } + + public string PublicKeyToken { + get { + StringBuilder s = new StringBuilder(); + foreach (byte b in r.PublicKeyToken) + s.Append(b.ToString("x2")); + return s.ToString(); + } + } + + public override string ToString() + { + return r.FullName; + } + } + } +} \ No newline at end of file diff --git a/ILSpy/Views/OpenFromGacDialog.xaml.cs b/ILSpy/Views/OpenFromGacDialog.xaml.cs index 0d4ae80fa5..680d67df7c 100644 --- a/ILSpy/Views/OpenFromGacDialog.xaml.cs +++ b/ILSpy/Views/OpenFromGacDialog.xaml.cs @@ -21,7 +21,6 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; -using System.Text; using System.Threading; using System.Windows; using System.Windows.Controls; @@ -62,64 +61,6 @@ protected override void OnClosing(CancelEventArgs e) } #region Fetch Gac Contents - sealed class GacEntry - { - readonly AssemblyNameReference r; - readonly string fileName; - string formattedVersion; - - public GacEntry(AssemblyNameReference r, string fileName) - { - this.r = r; - this.fileName = fileName; - } - - public string FullName { - get { return r.FullName; } - } - - public string ShortName { - get { return r.Name; } - } - - public string FileName { - get { return fileName; } - } - - public Version Version { - get { return r.Version; } - } - - public string FormattedVersion { - get { - if (formattedVersion == null) - formattedVersion = Version.ToString(); - return formattedVersion; - } - } - - public string Culture { - get { - if (string.IsNullOrEmpty(r.Culture)) - return "neutral"; - return r.Culture; - } - } - - public string PublicKeyToken { - get { - StringBuilder s = new StringBuilder(); - foreach (byte b in r.PublicKeyToken) - s.Append(b.ToString("x2")); - return s.ToString(); - } - } - - public override string ToString() - { - return r.FullName; - } - } void FetchGacContents() { From d5830147fdf155a16cc17de099c874deae7dac49 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Wed, 31 Dec 2025 03:50:58 -0500 Subject: [PATCH 2/7] Fix whitespace --- ILSpy/AssemblyTree/AssemblyTreeModel.cs | 12 ++++++------ ILSpy/Metadata/Helper.wpf.cs | 6 +++--- ILSpy/Metadata/Helpers.cs | 16 ++++++++-------- ILSpy/Metadata/HexFilterControl.ContentFilter.cs | 2 +- ILSpy/Metadata/MetadataTableTreeNode.wpf.cs | 6 +++--- ILSpy/Options/ShowOptionsCommand.cs | 6 +++--- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index 778d73ec39..0d22f9b281 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -130,9 +130,9 @@ public SharpTreeNode[] SelectedItems { var oldSelection = selectedItems; selectedItems = value; OnPropertyChanged(); - #if CROSS_PLATFORM +#if CROSS_PLATFORM OnPropertyChanged(nameof(SelectedItem)); - #endif +#endif TreeView_SelectionChanged(oldSelection, selectedItems); } } @@ -496,16 +496,16 @@ public void SelectNode(SharpTreeNode? node, bool inNewTabPage = false) } else { - #if CROSS_PLATFORM +#if CROSS_PLATFORM ExpandAncestors(node); - #endif +#endif activeView?.ScrollIntoView(node); SelectedItem = node; UIThreadDispatcher.BeginInvoke(DispatcherPriority.Background, () => { - #if CROSS_PLATFORM +#if CROSS_PLATFORM SelectedItem = node; - #endif +#endif activeView?.ScrollIntoView(node); }); } diff --git a/ILSpy/Metadata/Helper.wpf.cs b/ILSpy/Metadata/Helper.wpf.cs index ffa67ed0cc..c099ce59b2 100644 --- a/ILSpy/Metadata/Helper.wpf.cs +++ b/ILSpy/Metadata/Helper.wpf.cs @@ -8,8 +8,8 @@ namespace ICSharpCode.ILSpy.Metadata { - static partial class Helpers - { + static partial class Helpers + { private static DataTemplate GetOrCreateLinkCellTemplate(string name, PropertyDescriptor descriptor, Binding binding) { if (linkCellTemplates.TryGetValue(name, out var template)) @@ -39,5 +39,5 @@ void Hyperlink_Click(object sender, RoutedEventArgs e) } } } - } + } } \ No newline at end of file diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index 65ffb6f58b..bada8bc476 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -71,9 +71,9 @@ public static DataGrid PrepareDataGrid(TabPageModel tabPage, ILSpyTreeNode selec ContextMenuProvider.Add(view); DataGridFilter.SetIsAutoFilterEnabled(view, true); DataGridFilter.SetContentFilterFactory(view, new RegexContentFilterFactory()); - #if !CROSS_PLATFORM +#if !CROSS_PLATFORM AdvancedScrollWheelBehavior.SetAttach(view, AdvancedScrollWheelMode.WithoutAnimation); - #endif +#endif } DataGridFilter.GetFilter(view).Clear(); view.RowDetailsTemplateSelector = null; @@ -112,23 +112,23 @@ internal static void View_AutoGeneratingColumn(object sender, DataGridAutoGenera case "RVA": case "StartOffset": case "Length": - #if CROSS_PLATFORM +#if CROSS_PLATFORM binding.Converter = HexFormatConverter.Instance; - #else +#else binding.StringFormat = "X8"; - #endif +#endif e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]); break; case "RowDetails": e.Cancel = true; break; case "Value" when e.PropertyDescriptor is PropertyDescriptor dp && dp.ComponentType == typeof(Entry): - #if CROSS_PLATFORM +#if CROSS_PLATFORM binding.Path = "."; e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["DefaultFilter"]); - #else +#else binding.Path = new PropertyPath("."); - #endif +#endif binding.Converter = ByteWidthConverter.Instance; break; default: diff --git a/ILSpy/Metadata/HexFilterControl.ContentFilter.cs b/ILSpy/Metadata/HexFilterControl.ContentFilter.cs index c8f6b1a8bf..5a769ec5ba 100644 --- a/ILSpy/Metadata/HexFilterControl.ContentFilter.cs +++ b/ILSpy/Metadata/HexFilterControl.ContentFilter.cs @@ -4,7 +4,7 @@ namespace ICSharpCode.ILSpy.Metadata { - public partial class HexFilterControl + public partial class HexFilterControl { class ContentFilter : IContentFilter { diff --git a/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs b/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs index ecb646becf..67fc16ad52 100644 --- a/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs +++ b/ILSpy/Metadata/MetadataTableTreeNode.wpf.cs @@ -3,8 +3,8 @@ namespace ICSharpCode.ILSpy.Metadata { - partial class MetadataTableTreeNode - { + partial class MetadataTableTreeNode + { protected void ScrollRowIntoView(DataGrid view, int row) { @@ -28,5 +28,5 @@ private void View_Loaded(object sender, System.Windows.RoutedEventArgs e) view.Loaded -= View_Loaded; this.scrollTarget = default; } - } + } } \ No newline at end of file diff --git a/ILSpy/Options/ShowOptionsCommand.cs b/ILSpy/Options/ShowOptionsCommand.cs index 50e173430c..858bdcafdf 100644 --- a/ILSpy/Options/ShowOptionsCommand.cs +++ b/ILSpy/Options/ShowOptionsCommand.cs @@ -32,7 +32,7 @@ public override void Execute(object parameter) { OptionsDialog dlg = new(settingsService); - #if CROSS_PLATFORM +#if CROSS_PLATFORM // On cross-platform schedule showing the dialog on the UI dispatcher // and await its result asynchronously. Do not block the calling thread. System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(async () => { @@ -42,7 +42,7 @@ public override void Execute(object parameter) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(() => assemblyTreeModel.Refresh()); } }); - #else +#else // WPF path: set owner and show dialog synchronously as before. dlg.Owner = mainWindow; bool? result = dlg.ShowDialog(); @@ -50,7 +50,7 @@ public override void Execute(object parameter) { assemblyTreeModel.Refresh(); } - #endif +#endif } } } \ No newline at end of file From d12f94280abd49098b472a07b808437df37839e1 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Tue, 13 Jan 2026 22:27:48 -0500 Subject: [PATCH 3/7] Add DockGroup assignment for cross-platform compatibility in DebugStepsPaneModel --- ILSpy/ViewModels/DebugStepsPaneModel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ILSpy/ViewModels/DebugStepsPaneModel.cs b/ILSpy/ViewModels/DebugStepsPaneModel.cs index f2a2810c71..03b021eecc 100644 --- a/ILSpy/ViewModels/DebugStepsPaneModel.cs +++ b/ILSpy/ViewModels/DebugStepsPaneModel.cs @@ -33,6 +33,10 @@ public DebugStepsPaneModel() { ContentId = PaneContentId; Title = Properties.Resources.DebugSteps; +#if CROSS_PLATFORM + // Declare this tool belongs to the LeftDock group (same as AssemblyTreeModel and AnalyzerTreeViewModel) + DockGroup = "LeftDock"; +#endif } } } From 6c5a35c13450c3542aa16008556cdb12c512562c Mon Sep 17 00:00:00 2001 From: Lex Li Date: Wed, 14 Jan 2026 19:36:58 -0500 Subject: [PATCH 4/7] Enhance SharpTreeNodeCollection to enforce type safety for IList operations --- .../TreeView/SharpTreeNodeCollection.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs b/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs index b938474455..63b03e7780 100644 --- a/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs +++ b/ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs @@ -105,7 +105,7 @@ bool ICollection.IsReadOnly { public object SyncRoot => ((ICollection)list).SyncRoot; - object IList.this[int index] { get => ((IList)list)[index]; set => ((IList)list)[index] = value; } + object IList.this[int index] { get => this[index]; set => this[index] = (SharpTreeNode)value; } #endregion @@ -254,32 +254,49 @@ public void RemoveAll(Predicate match) public int Add(object value) { - return ((IList)list).Add(value); + if (value == null) + throw new ArgumentNullException(nameof(value)); + if (!(value is SharpTreeNode node)) + throw new ArgumentException("Value must be a SharpTreeNode", nameof(value)); + Add(node); + return list.IndexOf(node); } public bool Contains(object value) { - return ((IList)list).Contains(value); + return value is SharpTreeNode node && Contains(node); } public int IndexOf(object value) { - return ((IList)list).IndexOf(value); + return value is SharpTreeNode node ? IndexOf(node) : -1; } public void Insert(int index, object value) { - ((IList)list).Insert(index, value); + if (value == null) + throw new ArgumentNullException(nameof(value)); + if (!(value is SharpTreeNode node)) + throw new ArgumentException("Value must be a SharpTreeNode", nameof(value)); + Insert(index, node); } public void Remove(object value) { - ((IList)list).Remove(value); + if (value is SharpTreeNode node) + Remove(node); } public void CopyTo(Array array, int index) { - ((ICollection)list).CopyTo(array, index); + if (array is SharpTreeNode[] nodes) + { + CopyTo(nodes, index); + } + else + { + ((ICollection)list).CopyTo(array, index); + } } } } From b771a902a4015b9a2756e266f4a1d62df8c1feb8 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Wed, 14 Jan 2026 19:43:05 -0500 Subject: [PATCH 5/7] Remove debug logging from ShowSearchCommand execution --- ILSpy/Search/ShowSearchCommand.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ILSpy/Search/ShowSearchCommand.cs b/ILSpy/Search/ShowSearchCommand.cs index 3a32be8faf..0cfe9f7991 100644 --- a/ILSpy/Search/ShowSearchCommand.cs +++ b/ILSpy/Search/ShowSearchCommand.cs @@ -43,9 +43,7 @@ public ShowSearchCommand(DockWorkspace dockWorkspace) protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) { - Console.WriteLine($"ShowSearchCommand: Executing ShowToolPane for SearchPaneModel.PaneContentId using dockWorkspace type: {dockWorkspace?.GetType().FullName}"); - var result = dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId); - Console.WriteLine($"ShowSearchCommand: ShowToolPane returned: {result}"); + dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId); } } } \ No newline at end of file From 85d8ba15c69743d2fee893857e6f20b94c41f783 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Wed, 14 Jan 2026 19:51:30 -0500 Subject: [PATCH 6/7] Remove unnecessary Export attribute from SearchModeModel --- ILSpy/Search/SearchPaneModel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index aee16d4e65..b9f23d55fe 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -32,7 +32,6 @@ public class SearchModeModel public ImageSource Image { get; init; } } - [Export] [ExportToolPane] [Shared] public partial class SearchPaneModel : ToolPaneModel From c735e204b9baddead3a034b302b0d6ef1763edf8 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Wed, 14 Jan 2026 19:57:00 -0500 Subject: [PATCH 7/7] Remove specific type from Export attribute in DockWorkspace --- ILSpy/Docking/DockWorkspace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 38f9c083e6..724f1ed63d 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy.Docking { - [Export(typeof(DockWorkspace))] + [Export] [Shared] public partial class DockWorkspace : ObservableObjectBase, ILayoutUpdateStrategy {