mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1629 lines
50 KiB
1629 lines
50 KiB
// 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.Collections.Generic; |
|
using System.Collections.Specialized; |
|
using System.ComponentModel; |
|
using System.Diagnostics; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Reflection.Metadata; |
|
using System.Reflection.Metadata.Ecma335; |
|
using System.Text; |
|
using System.Threading.Tasks; |
|
using System.Windows; |
|
using System.Windows.Controls; |
|
using System.Windows.Data; |
|
using System.Windows.Input; |
|
using System.Windows.Media; |
|
using System.Windows.Navigation; |
|
using System.Windows.Threading; |
|
|
|
using AvalonDock.Layout.Serialization; |
|
|
|
using ICSharpCode.Decompiler; |
|
using ICSharpCode.Decompiler.Documentation; |
|
using ICSharpCode.Decompiler.Metadata; |
|
using ICSharpCode.Decompiler.TypeSystem; |
|
using ICSharpCode.Decompiler.TypeSystem.Implementation; |
|
using ICSharpCode.ILSpy.AppEnv; |
|
using ICSharpCode.ILSpy.Commands; |
|
using ICSharpCode.ILSpy.Controls.TreeView; |
|
using ICSharpCode.ILSpy.Docking; |
|
using ICSharpCode.ILSpy.Options; |
|
using ICSharpCode.ILSpy.Search; |
|
using ICSharpCode.ILSpy.TextView; |
|
using ICSharpCode.ILSpy.Themes; |
|
using ICSharpCode.ILSpy.TreeNodes; |
|
using ICSharpCode.ILSpy.Updates; |
|
using ICSharpCode.ILSpy.Util; |
|
using ICSharpCode.ILSpy.ViewModels; |
|
using ICSharpCode.ILSpyX; |
|
using ICSharpCode.ILSpyX.Extensions; |
|
using ICSharpCode.ILSpyX.FileLoaders; |
|
using ICSharpCode.ILSpyX.Settings; |
|
using ICSharpCode.ILSpyX.TreeView; |
|
|
|
using Microsoft.Win32; |
|
|
|
using TomsToolbox.Composition; |
|
|
|
namespace ICSharpCode.ILSpy |
|
{ |
|
/// <summary> |
|
/// The main window of the application. |
|
/// </summary> |
|
partial class MainWindow : Window |
|
{ |
|
bool refreshInProgress, changingActiveTab; |
|
readonly NavigationHistory<NavigationState> history = new NavigationHistory<NavigationState>(); |
|
|
|
AssemblyList assemblyList; |
|
AssemblyListTreeNode assemblyListTreeNode; |
|
|
|
static MainWindow instance; |
|
|
|
public static MainWindow Instance { |
|
get { return instance; } |
|
} |
|
|
|
public SharpTreeView? AssemblyTreeView { |
|
get { |
|
return FindResource("AssemblyTreeView") as SharpTreeView; |
|
} |
|
} |
|
|
|
public DecompilationOptions CreateDecompilationOptions() |
|
{ |
|
var decompilerView = DockWorkspace.Instance.ActiveTabPage.Content as IProgress<DecompilationProgress>; |
|
return new DecompilationOptions(CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { Progress = decompilerView }; |
|
} |
|
|
|
public MainWindow() |
|
{ |
|
instance = this; |
|
|
|
var sessionSettings = SettingsService.Instance.SessionSettings; |
|
|
|
// Make sure Images are initialized on the UI thread. |
|
this.Icon = Images.ILSpyIcon; |
|
|
|
this.DataContext = new MainWindowViewModel { |
|
Workspace = DockWorkspace.Instance, |
|
SessionSettings = sessionSettings, |
|
AssemblyListManager = SettingsService.Instance.AssemblyListManager |
|
}; |
|
|
|
SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists(); |
|
if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture)) |
|
{ |
|
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(sessionSettings.CurrentCulture); |
|
} |
|
InitializeComponent(); |
|
InitToolPanes(); |
|
DockWorkspace.Instance.InitializeLayout(dockManager); |
|
|
|
MessageBus<SessionSettingsChangedEventArgs>.Subscribers += (sender, e) => SessionSettings_PropertyChanged(sender, e); |
|
MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); |
|
MessageBus<DockWorkspaceActiveTabPageChangedEventArgs>.Subscribers += DockWorkspace_ActiveTabPageChanged; |
|
|
|
InitMainMenu(); |
|
InitWindowMenu(); |
|
InitToolbar(); |
|
InitFileLoaders(); |
|
ContextMenuProvider.Add(AssemblyTreeView); |
|
|
|
this.Loaded += MainWindow_Loaded; |
|
} |
|
|
|
private void DockWorkspace_ActiveTabPageChanged(object? sender, EventArgs e) |
|
{ |
|
DockWorkspace dock = DockWorkspace.Instance; |
|
|
|
var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); |
|
foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>()) |
|
{ |
|
if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) |
|
{ |
|
menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; |
|
} |
|
} |
|
} |
|
|
|
private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) |
|
{ |
|
var sessionSettings = SettingsService.Instance.SessionSettings; |
|
|
|
switch (e.PropertyName) |
|
{ |
|
case nameof(SessionSettings.ActiveAssemblyList): |
|
ShowAssemblyList(sessionSettings.ActiveAssemblyList); |
|
break; |
|
case nameof(SessionSettings.Theme): |
|
// update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) |
|
DecompilerTextView.RegisterHighlighting(); |
|
DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); |
|
break; |
|
case nameof(SessionSettings.CurrentCulture): |
|
MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy"); |
|
break; |
|
} |
|
} |
|
|
|
void SetWindowBounds(Rect bounds) |
|
{ |
|
this.Left = bounds.Left; |
|
this.Top = bounds.Top; |
|
this.Width = bounds.Width; |
|
this.Height = bounds.Height; |
|
} |
|
|
|
#region Toolbar extensibility |
|
|
|
void InitToolbar() |
|
{ |
|
int navigationPos = 0; |
|
int openPos = 1; |
|
var toolbarCommands = App.ExportProvider.GetExports<ICommand, IToolbarCommandMetadata>("ToolbarCommand"); |
|
foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => Properties.Resources.ResourceManager.GetString(c.Metadata.ToolbarCategory))) |
|
{ |
|
if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Navigation")) |
|
{ |
|
foreach (var command in commandGroup) |
|
{ |
|
toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); |
|
openPos++; |
|
} |
|
} |
|
else if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Open")) |
|
{ |
|
foreach (var command in commandGroup) |
|
{ |
|
toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); |
|
} |
|
} |
|
else |
|
{ |
|
toolBar.Items.Add(new Separator()); |
|
foreach (var command in commandGroup) |
|
{ |
|
toolBar.Items.Add(MakeToolbarItem(command)); |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
Button MakeToolbarItem(IExport<ICommand, IToolbarCommandMetadata> command) |
|
{ |
|
return new Button { |
|
Style = ThemeManager.Current.CreateToolBarButtonStyle(), |
|
Command = CommandWrapper.Unwrap(command.Value), |
|
ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), |
|
Tag = command.Metadata.Tag, |
|
Content = new Image { |
|
Width = 16, |
|
Height = 16, |
|
Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) |
|
} |
|
}; |
|
} |
|
#endregion |
|
|
|
#region Main Menu extensibility |
|
|
|
void InitMainMenu() |
|
{ |
|
var mainMenuCommands = App.ExportProvider.GetExports<ICommand, IMainMenuCommandMetadata>("MainMenuCommand"); |
|
// Start by constructing the individual flat menus |
|
var parentMenuItems = new Dictionary<string, MenuItem>(); |
|
var menuGroups = mainMenuCommands.OrderBy(c => c.Metadata.MenuOrder).GroupBy(c => c.Metadata.ParentMenuID); |
|
foreach (var menu in menuGroups) |
|
{ |
|
// Get or add the target menu item and add all items grouped by menu category |
|
var parentMenuItem = GetOrAddParentMenuItem(menu.Key, menu.Key); |
|
foreach (var category in menu.GroupBy(c => c.Metadata.MenuCategory)) |
|
{ |
|
if (parentMenuItem.Items.Count > 0) |
|
{ |
|
parentMenuItem.Items.Add(new Separator() { Tag = category.Key }); |
|
} |
|
foreach (var entry in category) |
|
{ |
|
if (menuGroups.Any(g => g.Key == entry.Metadata.MenuID)) |
|
{ |
|
var menuItem = GetOrAddParentMenuItem(entry.Metadata.MenuID, entry.Metadata.Header); |
|
// replace potential dummy text with real name |
|
menuItem.Header = GetResourceString(entry.Metadata.Header); |
|
parentMenuItem.Items.Add(menuItem); |
|
} |
|
else |
|
{ |
|
MenuItem menuItem = new MenuItem(); |
|
menuItem.Command = CommandWrapper.Unwrap(entry.Value); |
|
menuItem.Tag = entry.Metadata.MenuID; |
|
menuItem.Header = GetResourceString(entry.Metadata.Header); |
|
if (!string.IsNullOrEmpty(entry.Metadata.MenuIcon)) |
|
{ |
|
menuItem.Icon = new Image { |
|
Width = 16, |
|
Height = 16, |
|
Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) |
|
}; |
|
} |
|
|
|
menuItem.IsEnabled = entry.Metadata.IsEnabled; |
|
if (entry.Value is ToggleableCommand toggle) |
|
{ |
|
menuItem.IsCheckable = true; |
|
menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); |
|
} |
|
|
|
menuItem.InputGestureText = entry.Metadata.InputGestureText; |
|
parentMenuItem.Items.Add(menuItem); |
|
} |
|
} |
|
} |
|
} |
|
|
|
foreach (var (key, item) in parentMenuItems) |
|
{ |
|
if (item.Parent == null) |
|
{ |
|
mainMenu.Items.Add(item); |
|
} |
|
} |
|
|
|
MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) |
|
{ |
|
if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem)) |
|
{ |
|
var topLevelMenuItem = mainMenu.Items.OfType<MenuItem>().FirstOrDefault(m => (string)m.Tag == menuID); |
|
if (topLevelMenuItem == null) |
|
{ |
|
parentMenuItem = new MenuItem(); |
|
parentMenuItem.Header = GetResourceString(resourceKey); |
|
parentMenuItem.Tag = menuID; |
|
parentMenuItems.Add(menuID, parentMenuItem); |
|
} |
|
else |
|
{ |
|
parentMenuItems.Add(menuID, topLevelMenuItem); |
|
parentMenuItem = topLevelMenuItem; |
|
} |
|
} |
|
return parentMenuItem; |
|
} |
|
} |
|
|
|
internal static string? GetResourceString(string key) |
|
{ |
|
if (string.IsNullOrEmpty(key)) |
|
{ |
|
return null; |
|
} |
|
string? value = Properties.Resources.ResourceManager.GetString(key); |
|
if (!string.IsNullOrEmpty(value)) |
|
{ |
|
return value; |
|
} |
|
return key; |
|
} |
|
#endregion |
|
|
|
#region Tool Pane extensibility |
|
|
|
private void InitToolPanes() |
|
{ |
|
var toolPanes = App.ExportProvider.GetExportedValues<ToolPaneModel>("ToolPane").OrderBy(item => item.Title); |
|
|
|
DockWorkspace.Instance.ToolPanes.AddRange(toolPanes); |
|
} |
|
|
|
private void InitWindowMenu() |
|
{ |
|
var windowMenuItem = mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); |
|
Separator separatorBeforeTools, separatorBeforeDocuments; |
|
windowMenuItem.Items.Add(separatorBeforeTools = new Separator()); |
|
windowMenuItem.Items.Add(separatorBeforeDocuments = new Separator()); |
|
|
|
var dock = DockWorkspace.Instance; |
|
dock.ToolPanes.CollectionChanged += ToolsChanged; |
|
dock.TabPages.CollectionChanged += TabsChanged; |
|
|
|
ToolsChanged(dock.ToolPanes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
|
TabsChanged(dock.TabPages, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
|
|
|
void ToolsChanged(object? sender, NotifyCollectionChangedEventArgs e) |
|
{ |
|
int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); |
|
int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; |
|
int insertionIndex; |
|
switch (e.Action) |
|
{ |
|
case NotifyCollectionChangedAction.Add: |
|
insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); |
|
foreach (ToolPaneModel pane in e.NewItems) |
|
{ |
|
MenuItem menuItem = CreateMenuItem(pane); |
|
windowMenuItem.Items.Insert(insertionIndex, menuItem); |
|
insertionIndex++; |
|
} |
|
break; |
|
case NotifyCollectionChangedAction.Remove: |
|
foreach (ToolPaneModel pane in e.OldItems) |
|
{ |
|
for (int i = endIndex - 1; i >= startIndex; i--) |
|
{ |
|
MenuItem item = (MenuItem)windowMenuItem.Items[i]; |
|
if (pane == item.Tag) |
|
{ |
|
windowMenuItem.Items.RemoveAt(i); |
|
item.Tag = null; |
|
endIndex--; |
|
break; |
|
} |
|
} |
|
} |
|
break; |
|
case NotifyCollectionChangedAction.Replace: |
|
break; |
|
case NotifyCollectionChangedAction.Move: |
|
break; |
|
case NotifyCollectionChangedAction.Reset: |
|
for (int i = endIndex - 1; i >= startIndex; i--) |
|
{ |
|
MenuItem item = (MenuItem)windowMenuItem.Items[0]; |
|
item.Tag = null; |
|
windowMenuItem.Items.RemoveAt(i); |
|
endIndex--; |
|
} |
|
insertionIndex = endIndex; |
|
foreach (ToolPaneModel pane in dock.ToolPanes) |
|
{ |
|
MenuItem menuItem = CreateMenuItem(pane); |
|
windowMenuItem.Items.Insert(insertionIndex, menuItem); |
|
insertionIndex++; |
|
} |
|
break; |
|
} |
|
|
|
MenuItem CreateMenuItem(ToolPaneModel pane) |
|
{ |
|
MenuItem menuItem = new MenuItem(); |
|
menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); |
|
menuItem.Header = pane.Title; |
|
menuItem.Tag = pane; |
|
var shortcutKey = pane.ShortcutKey; |
|
if (shortcutKey != null) |
|
{ |
|
InputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); |
|
menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(System.Globalization.CultureInfo.CurrentUICulture); |
|
} |
|
if (!string.IsNullOrEmpty(pane.Icon)) |
|
{ |
|
menuItem.Icon = new Image { |
|
Width = 16, |
|
Height = 16, |
|
Source = Images.Load(pane, pane.Icon) |
|
}; |
|
} |
|
|
|
return menuItem; |
|
} |
|
} |
|
|
|
void TabsChanged(object? sender, NotifyCollectionChangedEventArgs e) |
|
{ |
|
int endIndex = windowMenuItem.Items.Count; |
|
int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; |
|
int insertionIndex; |
|
switch (e.Action) |
|
{ |
|
case NotifyCollectionChangedAction.Add: |
|
insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); |
|
foreach (TabPageModel pane in e.NewItems) |
|
{ |
|
MenuItem menuItem = CreateMenuItem(pane); |
|
pane.PropertyChanged += TabPageChanged; |
|
windowMenuItem.Items.Insert(insertionIndex, menuItem); |
|
insertionIndex++; |
|
} |
|
break; |
|
case NotifyCollectionChangedAction.Remove: |
|
foreach (TabPageModel pane in e.OldItems) |
|
{ |
|
for (int i = endIndex - 1; i >= startIndex; i--) |
|
{ |
|
MenuItem item = (MenuItem)windowMenuItem.Items[i]; |
|
if (pane == item.Tag) |
|
{ |
|
windowMenuItem.Items.RemoveAt(i); |
|
pane.PropertyChanged -= TabPageChanged; |
|
item.Tag = null; |
|
endIndex--; |
|
break; |
|
} |
|
} |
|
} |
|
break; |
|
case NotifyCollectionChangedAction.Replace: |
|
break; |
|
case NotifyCollectionChangedAction.Move: |
|
break; |
|
case NotifyCollectionChangedAction.Reset: |
|
for (int i = endIndex - 1; i >= startIndex; i--) |
|
{ |
|
MenuItem item = (MenuItem)windowMenuItem.Items[i]; |
|
windowMenuItem.Items.RemoveAt(i); |
|
((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; |
|
endIndex--; |
|
} |
|
insertionIndex = endIndex; |
|
foreach (TabPageModel pane in dock.TabPages) |
|
{ |
|
MenuItem menuItem = CreateMenuItem(pane); |
|
pane.PropertyChanged += TabPageChanged; |
|
windowMenuItem.Items.Insert(insertionIndex, menuItem); |
|
insertionIndex++; |
|
} |
|
break; |
|
} |
|
|
|
MenuItem CreateMenuItem(TabPageModel pane) |
|
{ |
|
MenuItem menuItem = new MenuItem(); |
|
menuItem.Command = new TabPageCommand(pane); |
|
menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; |
|
menuItem.Tag = pane; |
|
menuItem.IsCheckable = true; |
|
|
|
return menuItem; |
|
} |
|
} |
|
|
|
static void TabPageChanged(object? sender, PropertyChangedEventArgs e) |
|
{ |
|
var windowMenuItem = Instance.mainMenu.Items.OfType<MenuItem>().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); |
|
foreach (MenuItem menuItem in windowMenuItem.Items.OfType<MenuItem>()) |
|
{ |
|
if (menuItem.IsCheckable && menuItem.Tag == sender) |
|
{ |
|
string title = ((TabPageModel)sender).Title; |
|
menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; |
|
} |
|
} |
|
} |
|
} |
|
|
|
public void ShowInTopPane(string title, object content) |
|
{ |
|
var model = DockWorkspace.Instance.ToolPanes.OfType<LegacyToolPaneModel>().FirstOrDefault(p => p.Content == content); |
|
if (model == null) |
|
{ |
|
model = new LegacyToolPaneModel(title, content, LegacyToolPaneLocation.Top); |
|
DockWorkspace.Instance.ToolPanes.Add(model); |
|
} |
|
model.Show(); |
|
} |
|
|
|
public void ShowInBottomPane(string title, object content) |
|
{ |
|
var model = DockWorkspace.Instance.ToolPanes.OfType<LegacyToolPaneModel>().FirstOrDefault(p => p.Content == content); |
|
if (model == null) |
|
{ |
|
model = new LegacyToolPaneModel(title, content, LegacyToolPaneLocation.Bottom); |
|
DockWorkspace.Instance.ToolPanes.Add(model); |
|
} |
|
model.Show(); |
|
} |
|
#endregion |
|
|
|
#region File Loader extensibility |
|
|
|
void InitFileLoaders() |
|
{ |
|
// TODO |
|
foreach (var loader in App.ExportProvider.GetExportedValues<IFileLoader>()) |
|
{ |
|
|
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Message Hook |
|
protected override void OnSourceInitialized(EventArgs e) |
|
{ |
|
base.OnSourceInitialized(e); |
|
PresentationSource source = PresentationSource.FromVisual(this); |
|
|
|
var sessionSettings = SettingsService.Instance.SessionSettings; |
|
|
|
// Validate and Set Window Bounds |
|
Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice); |
|
var boundsRect = new System.Drawing.Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height); |
|
bool boundsOK = false; |
|
foreach (var screen in System.Windows.Forms.Screen.AllScreens) |
|
{ |
|
var intersection = System.Drawing.Rectangle.Intersect(boundsRect, screen.WorkingArea); |
|
if (intersection.Width > 10 && intersection.Height > 10) |
|
boundsOK = true; |
|
} |
|
if (boundsOK) |
|
SetWindowBounds(sessionSettings.WindowBounds); |
|
else |
|
SetWindowBounds(SessionSettings.DefaultWindowBounds); |
|
|
|
this.WindowState = sessionSettings.WindowState; |
|
} |
|
|
|
#endregion |
|
|
|
protected override void OnKeyDown(KeyEventArgs e) |
|
{ |
|
base.OnKeyDown(e); |
|
if (!e.Handled && e.KeyboardDevice.Modifiers == ModifierKeys.Alt && e.Key == Key.System) |
|
{ |
|
switch (e.SystemKey) |
|
{ |
|
case Key.A: |
|
assemblyListComboBox.Focus(); |
|
e.Handled = true; |
|
break; |
|
case Key.L: |
|
languageComboBox.Focus(); |
|
e.Handled = true; |
|
break; |
|
case Key.E: // Alt+V was already taken by _View menu |
|
languageVersionComboBox.Focus(); |
|
e.Handled = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
public AssemblyList CurrentAssemblyList { |
|
get { return assemblyList; } |
|
} |
|
|
|
List<LoadedAssembly> commandLineLoadedAssemblies = new List<LoadedAssembly>(); |
|
|
|
internal async Task HandleSingleInstanceCommandLineArguments(string[] args) |
|
{ |
|
var cmdArgs = CommandLineArguments.Create(args); |
|
|
|
await Dispatcher.InvokeAsync(() => { |
|
if (HandleCommandLineArguments(cmdArgs)) |
|
{ |
|
if (!cmdArgs.NoActivate && WindowState == WindowState.Minimized) |
|
WindowState = WindowState.Normal; |
|
|
|
HandleCommandLineArgumentsAfterShowList(cmdArgs); |
|
} |
|
}); |
|
} |
|
|
|
bool HandleCommandLineArguments(CommandLineArguments args) |
|
{ |
|
LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); |
|
if (args.Language != null) |
|
SettingsService.Instance.SessionSettings.LanguageSettings.Language = Languages.GetLanguage(args.Language); |
|
return true; |
|
} |
|
|
|
/// <summary> |
|
/// Called on startup or when passed arguments via WndProc from a second instance. |
|
/// In the format case, spySettings is non-null; in the latter it is null. |
|
/// </summary> |
|
void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ILSpySettings spySettings = null) |
|
{ |
|
var sessionSettings = SettingsService.Instance.SessionSettings; |
|
|
|
var relevantAssemblies = commandLineLoadedAssemblies.ToList(); |
|
commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore |
|
NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies); |
|
if (args.Search != null) |
|
{ |
|
var searchPane = App.ExportProvider.GetExportedValue<SearchPaneModel>(); |
|
|
|
searchPane.SearchTerm = args.Search; |
|
searchPane.Show(); |
|
} |
|
} |
|
|
|
async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ILSpySettings spySettings, List<LoadedAssembly> relevantAssemblies) |
|
{ |
|
var initialSelection = AssemblyTreeView.SelectedItem; |
|
if (navigateTo != null) |
|
{ |
|
bool found = false; |
|
if (navigateTo.StartsWith("N:", StringComparison.Ordinal)) |
|
{ |
|
string namespaceName = navigateTo.Substring(2); |
|
foreach (LoadedAssembly asm in relevantAssemblies) |
|
{ |
|
AssemblyTreeNode? asmNode = assemblyListTreeNode.FindAssemblyNode(asm); |
|
if (asmNode != null) |
|
{ |
|
// FindNamespaceNode() blocks the UI if the assembly is not yet loaded, |
|
// so use an async wait instead. |
|
await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); |
|
NamespaceTreeNode? nsNode = asmNode.FindNamespaceNode(namespaceName); |
|
if (nsNode != null) |
|
{ |
|
found = true; |
|
if (AssemblyTreeView.SelectedItem == initialSelection) |
|
{ |
|
SelectNode(nsNode); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
else if (navigateTo == "none") |
|
{ |
|
// Don't navigate anywhere; start empty. |
|
// Used by ILSpy VS addin, it'll send us the real location to navigate to via IPC. |
|
found = true; |
|
} |
|
else |
|
{ |
|
IEntity? mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); |
|
// Make sure we wait for assemblies being loaded... |
|
// BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal |
|
await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); |
|
if (mr != null && mr.ParentModule.MetadataFile != null) |
|
{ |
|
found = true; |
|
if (AssemblyTreeView.SelectedItem == initialSelection) |
|
{ |
|
JumpToReference(mr); |
|
} |
|
} |
|
} |
|
if (!found && AssemblyTreeView.SelectedItem == initialSelection) |
|
{ |
|
AvalonEditTextOutput output = new AvalonEditTextOutput(); |
|
output.Write(string.Format("Cannot find '{0}' in command line specified assemblies.", navigateTo)); |
|
DockWorkspace.Instance.ShowText(output); |
|
} |
|
} |
|
else if (relevantAssemblies.Count == 1) |
|
{ |
|
// NavigateTo == null and an assembly was given on the command-line: |
|
// Select the newly loaded assembly |
|
AssemblyTreeNode? asmNode = assemblyListTreeNode.FindAssemblyNode(relevantAssemblies[0]); |
|
if (asmNode != null && AssemblyTreeView.SelectedItem == initialSelection) |
|
{ |
|
SelectNode(asmNode); |
|
} |
|
} |
|
else if (spySettings != null) |
|
{ |
|
SharpTreeNode? node = null; |
|
if (activeTreeViewPath?.Length > 0) |
|
{ |
|
foreach (var asm in CurrentAssemblyList.GetAssemblies()) |
|
{ |
|
if (asm.FileName == activeTreeViewPath[0]) |
|
{ |
|
// FindNodeByPath() blocks the UI if the assembly is not yet loaded, |
|
// so use an async wait instead. |
|
await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); |
|
} |
|
} |
|
node = FindNodeByPath(activeTreeViewPath, true); |
|
} |
|
if (AssemblyTreeView.SelectedItem == initialSelection) |
|
{ |
|
if (node != null) |
|
{ |
|
SelectNode(node); |
|
|
|
// only if not showing the about page, perform the update check: |
|
await ShowMessageIfUpdatesAvailableAsync(spySettings); |
|
} |
|
else |
|
{ |
|
DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); |
|
} |
|
} |
|
} |
|
} |
|
|
|
internal static IEntity? FindEntityInRelevantAssemblies(string navigateTo, IEnumerable<LoadedAssembly> relevantAssemblies) |
|
{ |
|
ITypeReference? typeRef = null; |
|
IMemberReference? memberRef = null; |
|
if (navigateTo.StartsWith("T:", StringComparison.Ordinal)) |
|
{ |
|
typeRef = IdStringProvider.ParseTypeName(navigateTo); |
|
} |
|
else |
|
{ |
|
memberRef = IdStringProvider.ParseMemberIdString(navigateTo); |
|
typeRef = memberRef.DeclaringTypeReference; |
|
} |
|
foreach (LoadedAssembly asm in relevantAssemblies.ToList()) |
|
{ |
|
var module = asm.GetMetadataFileOrNull(); |
|
if (CanResolveTypeInPEFile(module, typeRef, out var typeHandle)) |
|
{ |
|
ICompilation compilation = typeHandle.Kind == HandleKind.ExportedType |
|
? new DecompilerTypeSystem(module, module.GetAssemblyResolver()) |
|
: new SimpleCompilation((PEFile)module, MinimalCorlib.Instance); |
|
return memberRef == null |
|
? typeRef.Resolve(new SimpleTypeResolveContext(compilation)) as ITypeDefinition |
|
: (IEntity?)memberRef.Resolve(new SimpleTypeResolveContext(compilation)); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
static bool CanResolveTypeInPEFile(MetadataFile module, ITypeReference typeRef, out EntityHandle typeHandle) |
|
{ |
|
// We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition. |
|
if (module.IsReferenceAssembly()) |
|
{ |
|
typeHandle = default; |
|
return false; |
|
} |
|
|
|
switch (typeRef) |
|
{ |
|
case GetPotentiallyNestedClassTypeReference topLevelType: |
|
typeHandle = topLevelType.ResolveInPEFile(module); |
|
return !typeHandle.IsNil; |
|
case NestedTypeReference nestedType: |
|
if (!CanResolveTypeInPEFile(module, nestedType.DeclaringTypeReference, out typeHandle)) |
|
return false; |
|
if (typeHandle.Kind == HandleKind.ExportedType) |
|
return true; |
|
var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeHandle); |
|
typeHandle = typeDef.GetNestedTypes().FirstOrDefault(t => { |
|
var td = module.Metadata.GetTypeDefinition(t); |
|
var typeName = ReflectionHelper.SplitTypeParameterCountFromReflectionName(module.Metadata.GetString(td.Name), out int typeParameterCount); |
|
return nestedType.AdditionalTypeParameterCount == typeParameterCount && nestedType.Name == typeName; |
|
}); |
|
return !typeHandle.IsNil; |
|
default: |
|
typeHandle = default; |
|
return false; |
|
} |
|
} |
|
|
|
void MainWindow_Loaded(object sender, RoutedEventArgs e) |
|
{ |
|
DockWorkspace.Instance.TabPages.Add(); |
|
|
|
var loadPreviousAssemblies = Options.MiscSettingsPanel.CurrentMiscSettings.LoadPreviousAssemblies; |
|
|
|
var sessionSettings = SettingsService.Instance.SessionSettings; |
|
|
|
if (loadPreviousAssemblies) |
|
{ |
|
// Load AssemblyList only in Loaded event so that WPF is initialized before we start the CPU-heavy stuff. |
|
// This makes the UI come up a bit faster. |
|
this.assemblyList = SettingsService.Instance.AssemblyListManager.LoadList(sessionSettings.ActiveAssemblyList); |
|
} |
|
else |
|
{ |
|
SettingsService.Instance.AssemblyListManager.ClearAll(); |
|
this.assemblyList = SettingsService.Instance.AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); |
|
} |
|
|
|
HandleCommandLineArguments(App.CommandLineArguments); |
|
|
|
if (assemblyList.GetAssemblies().Length == 0 |
|
&& assemblyList.ListName == AssemblyListManager.DefaultListName |
|
&& loadPreviousAssemblies) |
|
{ |
|
LoadInitialAssemblies(); |
|
} |
|
|
|
ShowAssemblyList(this.assemblyList); |
|
|
|
if (sessionSettings.ActiveAutoLoadedAssembly != null |
|
&& File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) |
|
{ |
|
this.assemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); |
|
} |
|
|
|
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => OpenAssemblies(SettingsService.Instance.SpySettings))); |
|
} |
|
|
|
void OpenAssemblies(ILSpySettings spySettings) |
|
{ |
|
HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, spySettings); |
|
|
|
AvalonEditTextOutput output = new AvalonEditTextOutput(); |
|
if (FormatExceptions(App.StartupExceptions.ToArray(), output)) |
|
DockWorkspace.Instance.ShowText(output); |
|
} |
|
|
|
static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output) |
|
{ |
|
var stringBuilder = new StringBuilder(); |
|
var result = exceptions.FormatExceptions(stringBuilder); |
|
if (result) |
|
{ |
|
output.Write(stringBuilder.ToString()); |
|
} |
|
return result; |
|
} |
|
|
|
#region Update Check |
|
string updateAvailableDownloadUrl; |
|
|
|
public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, bool forceCheck = false) |
|
{ |
|
string? downloadUrl; |
|
if (forceCheck) |
|
{ |
|
downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(spySettings); |
|
} |
|
else |
|
{ |
|
downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesIfEnabledAsync(spySettings); |
|
} |
|
|
|
// The Update Panel is only available for NotifyOfUpdatesStrategy, AutoUpdate will have differing UI requirements |
|
AdjustUpdateUIAfterCheck(downloadUrl, forceCheck); |
|
} |
|
|
|
void updatePanelCloseButtonClick(object sender, RoutedEventArgs e) |
|
{ |
|
updatePanel.Visibility = Visibility.Collapsed; |
|
} |
|
|
|
async void downloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) |
|
{ |
|
if (updateAvailableDownloadUrl != null) |
|
{ |
|
MainWindow.OpenLink(updateAvailableDownloadUrl); |
|
} |
|
else |
|
{ |
|
updatePanel.Visibility = Visibility.Collapsed; |
|
string? downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(ILSpySettings.Load()); |
|
AdjustUpdateUIAfterCheck(downloadUrl, true); |
|
} |
|
} |
|
|
|
void AdjustUpdateUIAfterCheck(string? downloadUrl, bool displayMessage) |
|
{ |
|
updateAvailableDownloadUrl = downloadUrl; |
|
updatePanel.Visibility = displayMessage ? Visibility.Visible : Visibility.Collapsed; |
|
if (downloadUrl != null) |
|
{ |
|
updatePanelMessage.Text = Properties.Resources.ILSpyVersionAvailable; |
|
downloadOrCheckUpdateButton.Content = Properties.Resources.Download; |
|
} |
|
else |
|
{ |
|
updatePanelMessage.Text = Properties.Resources.UpdateILSpyFound; |
|
downloadOrCheckUpdateButton.Content = Properties.Resources.CheckAgain; |
|
} |
|
} |
|
#endregion |
|
|
|
public void ShowAssemblyList(string name) |
|
{ |
|
AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); |
|
//Only load a new list when it is a different one |
|
if (list.ListName != CurrentAssemblyList.ListName) |
|
{ |
|
ShowAssemblyList(list); |
|
SelectNode(AssemblyTreeView.Root); |
|
} |
|
} |
|
|
|
void ShowAssemblyList(AssemblyList assemblyList) |
|
{ |
|
history.Clear(); |
|
if (this.assemblyList != null) |
|
{ |
|
this.assemblyList.CollectionChanged -= assemblyList_Assemblies_CollectionChanged; |
|
} |
|
|
|
this.assemblyList = assemblyList; |
|
assemblyList.CollectionChanged += assemblyList_Assemblies_CollectionChanged; |
|
|
|
assemblyListTreeNode = new AssemblyListTreeNode(assemblyList); |
|
assemblyListTreeNode.Select = x => SelectNode(x, inNewTabPage: false); |
|
AssemblyTreeView.Root = assemblyListTreeNode; |
|
|
|
if (assemblyList.ListName == AssemblyListManager.DefaultListName) |
|
#if DEBUG |
|
this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}"; |
|
#else |
|
this.Title = "ILSpy"; |
|
#endif |
|
else |
|
#if DEBUG |
|
this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName; |
|
#else |
|
this.Title = "ILSpy - " + assemblyList.ListName; |
|
#endif |
|
} |
|
|
|
void assemblyList_Assemblies_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) |
|
{ |
|
if (e.Action == NotifyCollectionChangedAction.Reset) |
|
{ |
|
history.RemoveAll(_ => true); |
|
} |
|
if (e.OldItems != null) |
|
{ |
|
var oldAssemblies = new HashSet<LoadedAssembly>(e.OldItems.Cast<LoadedAssembly>()); |
|
history.RemoveAll(n => n.TreeNodes.Any( |
|
nd => nd.AncestorsAndSelf().OfType<AssemblyTreeNode>().Any( |
|
a => oldAssemblies.Contains(a.LoadedAssembly)))); |
|
} |
|
|
|
MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e)); |
|
} |
|
|
|
void LoadInitialAssemblies() |
|
{ |
|
// 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); |
|
} |
|
|
|
void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) |
|
{ |
|
if (e.PropertyName == "Language" || e.PropertyName == "LanguageVersion") |
|
{ |
|
DecompileSelectedNodes(recordHistory: false); |
|
} |
|
} |
|
|
|
internal AssemblyListTreeNode AssemblyListTreeNode { |
|
get { return assemblyListTreeNode; } |
|
} |
|
|
|
#region Node Selection |
|
|
|
public void SelectNode(SharpTreeNode obj) |
|
{ |
|
SelectNode(obj, false); |
|
} |
|
|
|
public void SelectNode(SharpTreeNode obj, bool inNewTabPage) |
|
{ |
|
SelectNode(obj, inNewTabPage, true); |
|
} |
|
|
|
public void SelectNode(SharpTreeNode obj, bool inNewTabPage, bool setFocus) |
|
{ |
|
if (obj != null) |
|
{ |
|
if (!obj.AncestorsAndSelf().Any(node => node.IsHidden)) |
|
{ |
|
if (inNewTabPage) |
|
{ |
|
DockWorkspace.Instance.TabPages.Add(); |
|
AssemblyTreeView.SelectedItem = null; |
|
} |
|
|
|
// Set both the selection and focus to ensure that keyboard navigation works as expected. |
|
if (setFocus) |
|
{ |
|
AssemblyTreeView.FocusNode(obj); |
|
} |
|
else |
|
{ |
|
AssemblyTreeView.ScrollIntoView(obj); |
|
} |
|
AssemblyTreeView.SelectedItem = obj; |
|
} |
|
else |
|
{ |
|
MessageBox.Show(Properties.Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation); |
|
} |
|
} |
|
} |
|
|
|
public void SelectNodes(IEnumerable<SharpTreeNode> nodes) |
|
{ |
|
SelectNodes(nodes, false); |
|
} |
|
|
|
public void SelectNodes(IEnumerable<SharpTreeNode> nodes, bool inNewTabPage) |
|
{ |
|
SelectNodes(nodes, inNewTabPage, true); |
|
} |
|
|
|
public void SelectNodes(IEnumerable<SharpTreeNode> nodes, bool inNewTabPage, bool setFocus) |
|
{ |
|
SelectNodes(nodes, inNewTabPage, setFocus, false); |
|
} |
|
|
|
internal void SelectNodes(IEnumerable<SharpTreeNode> nodes, bool inNewTabPage, |
|
bool setFocus, bool changingActiveTab) |
|
{ |
|
if (inNewTabPage) |
|
{ |
|
DockWorkspace.Instance.TabPages.Add(); |
|
} |
|
|
|
// Ensure nodes exist |
|
var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) |
|
.Where(n => n != null).ToArray(); |
|
|
|
if (!nodesList.Any() || !nodesList.All(n => !n.AncestorsAndSelf().Any(a => a.IsHidden))) |
|
{ |
|
return; |
|
} |
|
|
|
this.changingActiveTab = changingActiveTab || inNewTabPage; |
|
try |
|
{ |
|
if (setFocus) |
|
{ |
|
AssemblyTreeView.FocusNode(nodesList[0]); |
|
} |
|
else |
|
{ |
|
AssemblyTreeView.ScrollIntoView(nodesList[0]); |
|
} |
|
|
|
AssemblyTreeView.SetSelectedNodes(nodesList); |
|
} |
|
finally |
|
{ |
|
this.changingActiveTab = false; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves a node using the .ToString() representations of its ancestors. |
|
/// </summary> |
|
public SharpTreeNode? FindNodeByPath(string[] path, bool returnBestMatch) |
|
{ |
|
if (path == null) |
|
return null; |
|
SharpTreeNode? node = AssemblyTreeView.Root; |
|
SharpTreeNode? bestMatch = node; |
|
foreach (var element in path) |
|
{ |
|
if (node == null) |
|
break; |
|
bestMatch = node; |
|
node.EnsureLazyChildren(); |
|
var ilSpyTreeNode = node as ILSpyTreeNode; |
|
if (ilSpyTreeNode != null) |
|
ilSpyTreeNode.EnsureChildrenFiltered(); |
|
node = node.Children.FirstOrDefault(c => c.ToString() == element); |
|
} |
|
if (returnBestMatch) |
|
return node ?? bestMatch; |
|
else |
|
return node; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the .ToString() representation of the node's ancestors. |
|
/// </summary> |
|
public static string[]? GetPathForNode(SharpTreeNode node) |
|
{ |
|
if (node == null) |
|
return null; |
|
List<string> path = new List<string>(); |
|
while (node.Parent != null) |
|
{ |
|
path.Add(node.ToString()); |
|
node = node.Parent; |
|
} |
|
path.Reverse(); |
|
return path.ToArray(); |
|
} |
|
|
|
public ILSpyTreeNode? FindTreeNode(object reference) |
|
{ |
|
switch (reference) |
|
{ |
|
case LoadedAssembly lasm: |
|
return assemblyListTreeNode.FindAssemblyNode(lasm); |
|
case MetadataFile asm: |
|
return assemblyListTreeNode.FindAssemblyNode(asm); |
|
case Resource res: |
|
return assemblyListTreeNode.FindResourceNode(res); |
|
case ValueTuple<Resource, string> resName: |
|
return assemblyListTreeNode.FindResourceNode(resName.Item1, resName.Item2); |
|
case ITypeDefinition type: |
|
return assemblyListTreeNode.FindTypeNode(type); |
|
case IField fd: |
|
return assemblyListTreeNode.FindFieldNode(fd); |
|
case IMethod md: |
|
return assemblyListTreeNode.FindMethodNode(md); |
|
case IProperty pd: |
|
return assemblyListTreeNode.FindPropertyNode(pd); |
|
case IEvent ed: |
|
return assemblyListTreeNode.FindEventNode(ed); |
|
case INamespace nd: |
|
return AssemblyListTreeNode.FindNamespaceNode(nd); |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
public void JumpToReference(object reference) |
|
{ |
|
JumpToReference(reference, inNewTabPage: false); |
|
} |
|
|
|
public void JumpToReference(object reference, bool inNewTabPage) |
|
{ |
|
JumpToReferenceAsync(reference, inNewTabPage).HandleExceptions(); |
|
} |
|
|
|
/// <summary> |
|
/// Jumps to the specified reference. |
|
/// </summary> |
|
/// <returns> |
|
/// Returns a task that will signal completion when the decompilation of the jump target has finished. |
|
/// The task will be marked as canceled if the decompilation is canceled. |
|
/// </returns> |
|
public Task JumpToReferenceAsync(object reference) |
|
{ |
|
return JumpToReferenceAsync(reference, inNewTabPage: false); |
|
} |
|
|
|
public Task JumpToReferenceAsync(object reference, bool inNewTabPage) |
|
{ |
|
decompilationTask = TaskHelper.CompletedTask; |
|
switch (reference) |
|
{ |
|
case Decompiler.Disassembler.OpCodeInfo opCode: |
|
OpenLink(opCode.Link); |
|
break; |
|
case EntityReference unresolvedEntity: |
|
string protocol = unresolvedEntity.Protocol ?? "decompile"; |
|
var file = unresolvedEntity.ResolveAssembly(assemblyList); |
|
if (file == null) |
|
{ |
|
break; |
|
} |
|
if (protocol != "decompile") |
|
{ |
|
var protocolHandlers = App.ExportProvider.GetExports<IProtocolHandler>(); |
|
foreach (var handler in protocolHandlers) |
|
{ |
|
var node = handler.Value.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage); |
|
if (node != null) |
|
{ |
|
SelectNode(node, newTabPage || inNewTabPage); |
|
return decompilationTask; |
|
} |
|
} |
|
} |
|
var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle)); |
|
if (possibleToken != null) |
|
{ |
|
var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached); |
|
reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value); |
|
goto default; |
|
} |
|
break; |
|
default: |
|
ILSpyTreeNode? treeNode = FindTreeNode(reference); |
|
if (treeNode != null) |
|
SelectNode(treeNode, inNewTabPage); |
|
break; |
|
} |
|
return decompilationTask; |
|
} |
|
|
|
public static void OpenLink(string link) |
|
{ |
|
try |
|
{ |
|
Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true }); |
|
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body |
|
} |
|
catch (Exception) |
|
{ |
|
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body |
|
// Process.Start can throw several errors (not all of them documented), |
|
// just ignore all of them. |
|
} |
|
} |
|
|
|
public static void ExecuteCommand(string fileName, string arguments) |
|
{ |
|
try |
|
{ |
|
Process.Start(fileName, arguments); |
|
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body |
|
} |
|
catch (Exception) |
|
{ |
|
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body |
|
// Process.Start can throw several errors (not all of them documented), |
|
// just ignore all of them. |
|
} |
|
} |
|
#endregion |
|
|
|
#region Open/Refresh |
|
void OpenCommandExecuted(object sender, ExecutedRoutedEventArgs e) |
|
{ |
|
e.Handled = true; |
|
OpenFileDialog dlg = new OpenFileDialog(); |
|
dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd;*.wasm|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*"; |
|
dlg.Multiselect = true; |
|
dlg.RestoreDirectory = true; |
|
if (dlg.ShowDialog() == true) |
|
{ |
|
OpenFiles(dlg.FileNames); |
|
} |
|
} |
|
|
|
public void OpenFiles(string[] fileNames, bool focusNode = true) |
|
{ |
|
if (fileNames == null) |
|
throw new ArgumentNullException(nameof(fileNames)); |
|
|
|
if (focusNode) |
|
AssemblyTreeView.UnselectAll(); |
|
|
|
LoadAssemblies(fileNames, focusNode: focusNode); |
|
} |
|
|
|
void LoadAssemblies(IEnumerable<string> fileNames, List<LoadedAssembly>? loadedAssemblies = null, bool focusNode = true) |
|
{ |
|
SharpTreeNode? lastNode = null; |
|
foreach (string file in fileNames) |
|
{ |
|
var asm = assemblyList.OpenAssembly(file); |
|
if (asm != null) |
|
{ |
|
if (loadedAssemblies != null) |
|
{ |
|
loadedAssemblies.Add(asm); |
|
} |
|
else |
|
{ |
|
var node = assemblyListTreeNode.FindAssemblyNode(asm); |
|
if (node != null && focusNode) |
|
{ |
|
AssemblyTreeView.SelectedItems.Add(node); |
|
lastNode = node; |
|
} |
|
} |
|
} |
|
|
|
if (lastNode != null && focusNode) |
|
AssemblyTreeView.FocusNode(lastNode); |
|
} |
|
} |
|
|
|
void RefreshCommandExecuted(object sender, ExecutedRoutedEventArgs e) |
|
{ |
|
try |
|
{ |
|
refreshInProgress = true; |
|
var path = GetPathForNode(AssemblyTreeView.SelectedItem as SharpTreeNode); |
|
ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(assemblyList.ListName)); |
|
SelectNode(FindNodeByPath(path, true), inNewTabPage: false, AssemblyTreeView.IsFocused); |
|
} |
|
finally |
|
{ |
|
refreshInProgress = false; |
|
} |
|
} |
|
|
|
void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) |
|
{ |
|
DockWorkspace.Instance.ShowToolPane(SearchPaneModel.PaneContentId); |
|
} |
|
#endregion |
|
|
|
#region Decompile (TreeView_SelectionChanged) |
|
bool delayDecompilationRequestDueToContextMenu; |
|
|
|
protected override void OnContextMenuClosing(ContextMenuEventArgs e) |
|
{ |
|
base.OnContextMenuClosing(e); |
|
|
|
if (delayDecompilationRequestDueToContextMenu) |
|
{ |
|
delayDecompilationRequestDueToContextMenu = false; |
|
var state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; |
|
DecompileSelectedNodes(state); |
|
} |
|
} |
|
|
|
void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) |
|
{ |
|
DecompilerTextViewState? state = null; |
|
if (refreshInProgress || changingActiveTab) |
|
{ |
|
state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; |
|
} |
|
|
|
this.delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; |
|
if (!changingActiveTab && !delayDecompilationRequestDueToContextMenu) |
|
{ |
|
DecompileSelectedNodes(state); |
|
} |
|
|
|
SelectionChanged?.Invoke(sender, e); |
|
} |
|
|
|
Task decompilationTask; |
|
bool ignoreDecompilationRequests; |
|
|
|
void DecompileSelectedNodes(DecompilerTextViewState? newState = null, bool recordHistory = true) |
|
{ |
|
if (ignoreDecompilationRequests) |
|
return; |
|
|
|
if (AssemblyTreeView.SelectedItems.Count == 0 && refreshInProgress) |
|
return; |
|
|
|
if (recordHistory) |
|
{ |
|
var tabPage = DockWorkspace.Instance.ActiveTabPage; |
|
var currentState = tabPage.GetState(); |
|
if (currentState != null) |
|
history.UpdateCurrent(new NavigationState(tabPage, currentState)); |
|
history.Record(new NavigationState(tabPage, AssemblyTreeView.SelectedItems.OfType<SharpTreeNode>())); |
|
} |
|
|
|
DockWorkspace.Instance.ActiveTabPage.SupportsLanguageSwitching = true; |
|
|
|
if (AssemblyTreeView.SelectedItems.Count == 1) |
|
{ |
|
ILSpyTreeNode? node = AssemblyTreeView.SelectedItem as ILSpyTreeNode; |
|
if (node != null && node.View(DockWorkspace.Instance.ActiveTabPage)) |
|
return; |
|
} |
|
if (newState?.ViewedUri != null) |
|
{ |
|
NavigateTo(new RequestNavigateEventArgs(newState.ViewedUri, null), recordHistory: false); |
|
return; |
|
} |
|
var options = MainWindow.Instance.CreateDecompilationOptions(); |
|
options.TextViewState = newState; |
|
decompilationTask = DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync( |
|
textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options) |
|
); |
|
} |
|
|
|
void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) |
|
{ |
|
e.Handled = true; |
|
e.CanExecute = SaveCodeContextMenuEntry.CanExecute(SelectedNodes.ToList()); |
|
} |
|
|
|
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e) |
|
{ |
|
SaveCodeContextMenuEntry.Execute(SelectedNodes.ToList()); |
|
} |
|
|
|
public void RefreshDecompiledView() |
|
{ |
|
try |
|
{ |
|
refreshInProgress = true; |
|
DecompileSelectedNodes(); |
|
} |
|
finally |
|
{ |
|
refreshInProgress = false; |
|
} |
|
} |
|
|
|
public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; |
|
|
|
public LanguageVersion CurrentLanguageVersion => SettingsService.Instance.SessionSettings.LanguageSettings.LanguageVersion; |
|
|
|
public event SelectionChangedEventHandler SelectionChanged; |
|
|
|
public IEnumerable<ILSpyTreeNode> SelectedNodes { |
|
get { |
|
return AssemblyTreeView.GetTopLevelSelection().OfType<ILSpyTreeNode>(); |
|
} |
|
} |
|
#endregion |
|
|
|
#region Back/Forward navigation |
|
void BackCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) |
|
{ |
|
e.Handled = true; |
|
e.CanExecute = history.CanNavigateBack; |
|
} |
|
|
|
void BackCommandExecuted(object sender, ExecutedRoutedEventArgs e) |
|
{ |
|
if (history.CanNavigateBack) |
|
{ |
|
e.Handled = true; |
|
NavigateHistory(false); |
|
} |
|
} |
|
|
|
void ForwardCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) |
|
{ |
|
e.Handled = true; |
|
e.CanExecute = history.CanNavigateForward; |
|
} |
|
|
|
void ForwardCommandExecuted(object sender, ExecutedRoutedEventArgs e) |
|
{ |
|
if (history.CanNavigateForward) |
|
{ |
|
e.Handled = true; |
|
NavigateHistory(true); |
|
} |
|
} |
|
|
|
void NavigateHistory(bool forward) |
|
{ |
|
TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; |
|
var state = tabPage.GetState(); |
|
if (state != null) |
|
history.UpdateCurrent(new NavigationState(tabPage, state)); |
|
var newState = forward ? history.GoForward() : history.GoBack(); |
|
|
|
ignoreDecompilationRequests = true; |
|
AssemblyTreeView.SelectedItems.Clear(); |
|
DockWorkspace.Instance.ActiveTabPage = newState.TabPage; |
|
foreach (var node in newState.TreeNodes) |
|
{ |
|
AssemblyTreeView.SelectedItems.Add(node); |
|
} |
|
if (newState.TreeNodes.Any()) |
|
AssemblyTreeView.FocusNode(newState.TreeNodes.First()); |
|
ignoreDecompilationRequests = false; |
|
DecompileSelectedNodes(newState.ViewState as DecompilerTextViewState, false); |
|
} |
|
#endregion |
|
|
|
internal void NavigateTo(RequestNavigateEventArgs e, bool recordHistory = true, bool inNewTabPage = false) |
|
{ |
|
if (e.Uri.Scheme == "resource") |
|
{ |
|
if (inNewTabPage) |
|
{ |
|
DockWorkspace.Instance.TabPages.Add(); |
|
} |
|
|
|
if (e.Uri.Host == "aboutpage") |
|
{ |
|
RecordHistory(); |
|
DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); |
|
e.Handled = true; |
|
return; |
|
} |
|
AvalonEditTextOutput output = new AvalonEditTextOutput { |
|
Address = e.Uri, |
|
Title = e.Uri.AbsolutePath, |
|
EnableHyperlinks = true |
|
}; |
|
using (Stream? s = typeof(App).Assembly.GetManifestResourceStream(typeof(App), e.Uri.AbsolutePath)) |
|
{ |
|
using (StreamReader r = new StreamReader(s)) |
|
{ |
|
string? line; |
|
while ((line = r.ReadLine()) != null) |
|
{ |
|
output.Write(line); |
|
output.WriteLine(); |
|
} |
|
} |
|
} |
|
RecordHistory(); |
|
DockWorkspace.Instance.ShowText(output); |
|
e.Handled = true; |
|
} |
|
|
|
void RecordHistory() |
|
{ |
|
if (!recordHistory) |
|
return; |
|
TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; |
|
var currentState = tabPage.GetState(); |
|
if (currentState != null) |
|
history.UpdateCurrent(new NavigationState(tabPage, currentState)); |
|
ignoreDecompilationRequests = true; |
|
UnselectAll(); |
|
ignoreDecompilationRequests = false; |
|
history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); |
|
} |
|
} |
|
|
|
protected override void OnStateChanged(EventArgs e) |
|
{ |
|
base.OnStateChanged(e); |
|
// store window state in settings only if it's not minimized |
|
if (this.WindowState != System.Windows.WindowState.Minimized) |
|
SettingsService.Instance.SessionSettings.WindowState = this.WindowState; |
|
} |
|
|
|
protected override void OnClosing(CancelEventArgs e) |
|
{ |
|
base.OnClosing(e); |
|
var sessionSettings = SettingsService.Instance.SessionSettings; |
|
|
|
sessionSettings.ActiveAssemblyList = assemblyList.ListName; |
|
sessionSettings.ActiveTreeViewPath = GetPathForNode(AssemblyTreeView.SelectedItem as SharpTreeNode); |
|
sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeView.SelectedItem as SharpTreeNode); |
|
sessionSettings.WindowBounds = this.RestoreBounds; |
|
sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); |
|
sessionSettings.Save(); |
|
} |
|
|
|
private string? GetAutoLoadedAssemblyNode(SharpTreeNode node) |
|
{ |
|
if (node == null) |
|
return null; |
|
while (!(node is TreeNodes.AssemblyTreeNode) && node.Parent != null) |
|
{ |
|
node = node.Parent; |
|
} |
|
//this should be an assembly node |
|
var assyNode = node as TreeNodes.AssemblyTreeNode; |
|
var loadedAssy = assyNode.LoadedAssembly; |
|
if (!(loadedAssy.IsLoaded && loadedAssy.IsAutoLoaded)) |
|
return null; |
|
|
|
return loadedAssy.FileName; |
|
} |
|
|
|
public void UnselectAll() |
|
{ |
|
AssemblyTreeView.UnselectAll(); |
|
} |
|
|
|
public void SetStatus(string status, Brush foreground) |
|
{ |
|
if (this.statusBar.Visibility == Visibility.Collapsed) |
|
this.statusBar.Visibility = Visibility.Visible; |
|
this.statusLabel.Foreground = foreground; |
|
this.statusLabel.Text = status; |
|
} |
|
|
|
public ItemCollection GetMainMenuItems() |
|
{ |
|
return mainMenu.Items; |
|
} |
|
|
|
public ItemCollection GetToolBarItems() |
|
{ |
|
return toolBar.Items; |
|
} |
|
} |
|
}
|
|
|