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.

1049 lines
30 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  1. // Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Specialized;
  21. using System.ComponentModel;
  22. using System.Composition;
  23. using System.Diagnostics.CodeAnalysis;
  24. using System.IO;
  25. using System.Linq;
  26. using System.Reflection.Metadata;
  27. using System.Reflection.Metadata.Ecma335;
  28. using System.Threading.Tasks;
  29. using System.Windows;
  30. using System.Windows.Documents;
  31. using System.Windows.Input;
  32. using System.Windows.Navigation;
  33. using System.Windows.Threading;
  34. using ICSharpCode.Decompiler.Documentation;
  35. using ICSharpCode.Decompiler.Metadata;
  36. using ICSharpCode.Decompiler.TypeSystem;
  37. using ICSharpCode.Decompiler.TypeSystem.Implementation;
  38. using ICSharpCode.ILSpy.AppEnv;
  39. using ICSharpCode.ILSpy.Properties;
  40. using ICSharpCode.ILSpy.TextView;
  41. using ICSharpCode.ILSpy.TreeNodes;
  42. using ICSharpCode.ILSpy.Updates;
  43. using ICSharpCode.ILSpy.ViewModels;
  44. using ICSharpCode.ILSpyX;
  45. using ICSharpCode.ILSpyX.TreeView;
  46. using TomsToolbox.Composition;
  47. using TomsToolbox.Essentials;
  48. using TomsToolbox.Wpf;
  49. #nullable enable
  50. namespace ICSharpCode.ILSpy.AssemblyTree
  51. {
  52. [ExportToolPane]
  53. [Shared]
  54. public class AssemblyTreeModel : ToolPaneModel
  55. {
  56. public const string PaneContentId = "assemblyListPane";
  57. private AssemblyListPane? activeView;
  58. private AssemblyListTreeNode? assemblyListTreeNode;
  59. private readonly DispatcherThrottle refreshThrottle;
  60. private readonly NavigationHistory<NavigationState> history = new();
  61. private bool isNavigatingHistory;
  62. private readonly SettingsService settingsService;
  63. private readonly LanguageService languageService;
  64. private readonly IExportProvider exportProvider;
  65. public AssemblyTreeModel(SettingsService settingsService, LanguageService languageService, IExportProvider exportProvider)
  66. {
  67. this.settingsService = settingsService;
  68. this.languageService = languageService;
  69. this.exportProvider = exportProvider;
  70. Title = Resources.Assemblies;
  71. ContentId = PaneContentId;
  72. IsCloseable = false;
  73. ShortcutKey = new KeyGesture(Key.F6);
  74. MessageBus<NavigateToReferenceEventArgs>.Subscribers += JumpToReference;
  75. MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e);
  76. MessageBus<ApplySessionSettingsEventArgs>.Subscribers += ApplySessionSettings;
  77. MessageBus<ActiveTabPageChangedEventArgs>.Subscribers += ActiveTabPageChanged;
  78. MessageBus<ResetLayoutEventArgs>.Subscribers += ResetLayout;
  79. MessageBus<NavigateToEventArgs>.Subscribers += (_, e) => NavigateTo(e.Request, e.InNewTabPage);
  80. MessageBus<MainWindowLoadedEventArgs>.Subscribers += (_, _) => {
  81. Initialize();
  82. Show();
  83. };
  84. EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler((_, e) => NavigateTo(e)));
  85. refreshThrottle = new(DispatcherPriority.Background, RefreshInternal);
  86. AssemblyList = settingsService.CreateEmptyAssemblyList();
  87. }
  88. private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e)
  89. {
  90. if (sender is SessionSettings sessionSettings)
  91. {
  92. switch (e.PropertyName)
  93. {
  94. case nameof(SessionSettings.ActiveAssemblyList):
  95. ShowAssemblyList(sessionSettings.ActiveAssemblyList);
  96. RefreshDecompiledView();
  97. break;
  98. case nameof(SessionSettings.Theme):
  99. // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change)
  100. DecompilerTextView.RegisterHighlighting();
  101. RefreshDecompiledView();
  102. break;
  103. case nameof(SessionSettings.CurrentCulture):
  104. MessageBox.Show(Resources.SettingsChangeRestartRequired, "ILSpy");
  105. break;
  106. }
  107. }
  108. else if (sender is LanguageSettings)
  109. {
  110. switch (e.PropertyName)
  111. {
  112. case nameof(LanguageSettings.LanguageId) or nameof(LanguageSettings.LanguageVersionId):
  113. RefreshDecompiledView();
  114. break;
  115. default:
  116. Refresh();
  117. break;
  118. }
  119. }
  120. }
  121. public AssemblyList AssemblyList { get; private set; }
  122. private SharpTreeNode? root;
  123. public SharpTreeNode? Root {
  124. get => root;
  125. set => SetProperty(ref root, value);
  126. }
  127. public SharpTreeNode? SelectedItem {
  128. get => SelectedItems.FirstOrDefault();
  129. set => SelectedItems = value is null ? [] : [value];
  130. }
  131. private SharpTreeNode[] selectedItems = [];
  132. public SharpTreeNode[] SelectedItems {
  133. get => selectedItems;
  134. set {
  135. if (selectedItems.SequenceEqual(value))
  136. return;
  137. selectedItems = value;
  138. OnPropertyChanged();
  139. TreeView_SelectionChanged();
  140. }
  141. }
  142. public string[]? SelectedPath => GetPathForNode(SelectedItem);
  143. private readonly List<LoadedAssembly> commandLineLoadedAssemblies = [];
  144. private bool HandleCommandLineArguments(CommandLineArguments args)
  145. {
  146. LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false);
  147. if (args.Language != null)
  148. languageService.Language = languageService.GetLanguage(args.Language);
  149. return true;
  150. }
  151. /// <summary>
  152. /// Called on startup or when passed arguments via WndProc from a second instance.
  153. /// In the format case, updateSettings is non-null; in the latter it is null.
  154. /// </summary>
  155. private async Task HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, UpdateSettings? updateSettings = null)
  156. {
  157. var sessionSettings = settingsService.SessionSettings;
  158. var relevantAssemblies = commandLineLoadedAssemblies.ToList();
  159. commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore
  160. await NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, updateSettings, relevantAssemblies);
  161. if (args.Search != null)
  162. {
  163. MessageBus.Send(this, new ShowSearchPageEventArgs(args.Search));
  164. }
  165. }
  166. public async Task HandleSingleInstanceCommandLineArguments(string[] args)
  167. {
  168. var cmdArgs = CommandLineArguments.Create(args);
  169. await Dispatcher.InvokeAsync(async () => {
  170. if (!HandleCommandLineArguments(cmdArgs))
  171. return;
  172. var window = Application.Current.MainWindow;
  173. if (!cmdArgs.NoActivate && window is { WindowState: WindowState.Minimized })
  174. {
  175. window.WindowState = WindowState.Normal;
  176. }
  177. await HandleCommandLineArgumentsAfterShowList(cmdArgs);
  178. });
  179. }
  180. private async Task NavigateOnLaunch(string? navigateTo, string[]? activeTreeViewPath, UpdateSettings? updateSettings, List<LoadedAssembly> relevantAssemblies)
  181. {
  182. var initialSelection = SelectedItem;
  183. if (navigateTo != null)
  184. {
  185. bool found = false;
  186. if (navigateTo.StartsWith("N:", StringComparison.Ordinal))
  187. {
  188. string namespaceName = navigateTo.Substring(2);
  189. foreach (LoadedAssembly asm in relevantAssemblies)
  190. {
  191. var asmNode = assemblyListTreeNode?.FindAssemblyNode(asm);
  192. if (asmNode != null)
  193. {
  194. // FindNamespaceNode() blocks the UI if the assembly is not yet loaded,
  195. // so use an async wait instead.
  196. await asm.GetMetadataFileAsync().Catch<Exception>(_ => { });
  197. NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName);
  198. if (nsNode != null)
  199. {
  200. found = true;
  201. if (SelectedItem == initialSelection)
  202. {
  203. SelectNode(nsNode);
  204. }
  205. break;
  206. }
  207. }
  208. }
  209. }
  210. else if (navigateTo == "none")
  211. {
  212. // Don't navigate anywhere; start empty.
  213. // Used by ILSpy VS addin, it'll send us the real location to navigate to via IPC.
  214. found = true;
  215. }
  216. else
  217. {
  218. IEntity? mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies));
  219. // Make sure we wait for assemblies being loaded...
  220. // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal
  221. await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal);
  222. if (mr is { ParentModule.MetadataFile: not null })
  223. {
  224. found = true;
  225. if (SelectedItem == initialSelection)
  226. {
  227. await JumpToReferenceAsync(mr);
  228. }
  229. }
  230. }
  231. if (!found && SelectedItem == initialSelection)
  232. {
  233. AvalonEditTextOutput output = new AvalonEditTextOutput();
  234. output.Write($"Cannot find '{navigateTo}' in command line specified assemblies.");
  235. DockWorkspace.ShowText(output);
  236. }
  237. }
  238. else if (relevantAssemblies.Count == 1)
  239. {
  240. // NavigateTo == null and an assembly was given on the command-line:
  241. // Select the newly loaded assembly
  242. var asmNode = assemblyListTreeNode?.FindAssemblyNode(relevantAssemblies[0]);
  243. if (asmNode != null && SelectedItem == initialSelection)
  244. {
  245. SelectNode(asmNode);
  246. }
  247. }
  248. else if (updateSettings != null)
  249. {
  250. SharpTreeNode? node = null;
  251. if (activeTreeViewPath?.Length > 0)
  252. {
  253. foreach (var asm in AssemblyList.GetAssemblies())
  254. {
  255. if (asm.FileName == activeTreeViewPath[0])
  256. {
  257. // FindNodeByPath() blocks the UI if the assembly is not yet loaded,
  258. // so use an async wait instead.
  259. await asm.GetMetadataFileAsync().Catch<Exception>(_ => { });
  260. }
  261. }
  262. node = FindNodeByPath(activeTreeViewPath, true);
  263. }
  264. if (SelectedItem == initialSelection)
  265. {
  266. if (node != null)
  267. {
  268. SelectNode(node);
  269. // only if not showing the about page, perform the update check:
  270. MessageBus.Send(this, new CheckIfUpdateAvailableEventArgs());
  271. }
  272. else
  273. {
  274. MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage));
  275. }
  276. }
  277. }
  278. }
  279. public static IEntity? FindEntityInRelevantAssemblies(string navigateTo, IEnumerable<LoadedAssembly> relevantAssemblies)
  280. {
  281. ITypeReference typeRef;
  282. IMemberReference? memberRef = null;
  283. if (navigateTo.StartsWith("T:", StringComparison.Ordinal))
  284. {
  285. typeRef = IdStringProvider.ParseTypeName(navigateTo);
  286. }
  287. else
  288. {
  289. memberRef = IdStringProvider.ParseMemberIdString(navigateTo);
  290. typeRef = memberRef.DeclaringTypeReference;
  291. }
  292. foreach (LoadedAssembly asm in relevantAssemblies.ToList())
  293. {
  294. var module = asm.GetMetadataFileOrNull();
  295. if (module != null && CanResolveTypeInPEFile(module, typeRef, out var typeHandle))
  296. {
  297. ICompilation compilation = typeHandle.Kind == HandleKind.ExportedType
  298. ? new DecompilerTypeSystem(module, module.GetAssemblyResolver())
  299. : new SimpleCompilation((PEFile)module, MinimalCorlib.Instance);
  300. return memberRef == null
  301. ? typeRef.Resolve(new SimpleTypeResolveContext(compilation)) as ITypeDefinition
  302. : memberRef.Resolve(new SimpleTypeResolveContext(compilation));
  303. }
  304. }
  305. return null;
  306. }
  307. private static bool CanResolveTypeInPEFile(MetadataFile module, ITypeReference typeRef, out EntityHandle typeHandle)
  308. {
  309. // We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition.
  310. if (module.IsReferenceAssembly())
  311. {
  312. typeHandle = default;
  313. return false;
  314. }
  315. switch (typeRef)
  316. {
  317. case GetPotentiallyNestedClassTypeReference topLevelType:
  318. typeHandle = topLevelType.ResolveInPEFile(module);
  319. return !typeHandle.IsNil;
  320. case NestedTypeReference nestedType:
  321. if (!CanResolveTypeInPEFile(module, nestedType.DeclaringTypeReference, out typeHandle))
  322. return false;
  323. if (typeHandle.Kind == HandleKind.ExportedType)
  324. return true;
  325. var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeHandle);
  326. typeHandle = typeDef.GetNestedTypes().FirstOrDefault(t => {
  327. var td = module.Metadata.GetTypeDefinition(t);
  328. var typeName = ReflectionHelper.SplitTypeParameterCountFromReflectionName(module.Metadata.GetString(td.Name), out int typeParameterCount);
  329. return nestedType.AdditionalTypeParameterCount == typeParameterCount && nestedType.Name == typeName;
  330. });
  331. return !typeHandle.IsNil;
  332. default:
  333. typeHandle = default;
  334. return false;
  335. }
  336. }
  337. public void Initialize()
  338. {
  339. AssemblyList = settingsService.LoadInitialAssemblyList();
  340. HandleCommandLineArguments(App.CommandLineArguments);
  341. var loadPreviousAssemblies = settingsService.MiscSettings.LoadPreviousAssemblies;
  342. if (AssemblyList.GetAssemblies().Length == 0
  343. && AssemblyList.ListName == AssemblyListManager.DefaultListName
  344. && loadPreviousAssemblies)
  345. {
  346. LoadInitialAssemblies(AssemblyList);
  347. }
  348. ShowAssemblyList(AssemblyList);
  349. var sessionSettings = settingsService.SessionSettings;
  350. if (sessionSettings.ActiveAutoLoadedAssembly != null
  351. && File.Exists(sessionSettings.ActiveAutoLoadedAssembly))
  352. {
  353. AssemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true);
  354. }
  355. Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies);
  356. }
  357. private async Task OpenAssemblies()
  358. {
  359. await HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, settingsService.GetSettings<UpdateSettings>());
  360. if (FormatExceptions(App.StartupExceptions.ToArray(), out var output))
  361. {
  362. output.Title = "Startup errors";
  363. DockWorkspace.AddTabPage();
  364. DockWorkspace.ShowText(output);
  365. }
  366. }
  367. private static bool FormatExceptions(App.ExceptionData[] exceptions, [NotNullWhen(true)] out AvalonEditTextOutput? output)
  368. {
  369. output = null;
  370. var result = exceptions.FormatExceptions();
  371. if (result.IsNullOrEmpty())
  372. return false;
  373. output = new();
  374. output.Write(result);
  375. return true;
  376. }
  377. private void ShowAssemblyList(string name)
  378. {
  379. AssemblyList list = settingsService.AssemblyListManager.LoadList(name);
  380. //Only load a new list when it is a different one
  381. if (list.ListName != AssemblyList.ListName)
  382. {
  383. ShowAssemblyList(list);
  384. SelectNode(Root?.Children.FirstOrDefault());
  385. }
  386. }
  387. private void ShowAssemblyList(AssemblyList assemblyList)
  388. {
  389. history.Clear();
  390. AssemblyList.CollectionChanged -= assemblyList_CollectionChanged;
  391. AssemblyList = assemblyList;
  392. assemblyList.CollectionChanged += assemblyList_CollectionChanged;
  393. assemblyListTreeNode = new(assemblyList) {
  394. Select = x => SelectNode(x)
  395. };
  396. Root = assemblyListTreeNode;
  397. var mainWindow = Application.Current?.MainWindow;
  398. if (mainWindow == null)
  399. return;
  400. if (assemblyList.ListName == AssemblyListManager.DefaultListName)
  401. #if DEBUG
  402. mainWindow.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}";
  403. #else
  404. mainWindow.Title = "ILSpy";
  405. #endif
  406. else
  407. #if DEBUG
  408. mainWindow.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName;
  409. #else
  410. mainWindow.Title = "ILSpy - " + assemblyList.ListName;
  411. #endif
  412. }
  413. private void assemblyList_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
  414. {
  415. if (e.Action == NotifyCollectionChangedAction.Reset)
  416. {
  417. history.RemoveAll(_ => true);
  418. }
  419. if (e.OldItems != null)
  420. {
  421. var oldAssemblies = new HashSet<LoadedAssembly>(e.OldItems.Cast<LoadedAssembly>());
  422. history.RemoveAll(n => n.TreeNodes.Any(
  423. nd => nd.AncestorsAndSelf().OfType<AssemblyTreeNode>().Any(
  424. a => oldAssemblies.Contains(a.LoadedAssembly))));
  425. }
  426. MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e));
  427. }
  428. private static void LoadInitialAssemblies(AssemblyList assemblyList)
  429. {
  430. // Called when loading an empty assembly list; so that
  431. // the user can see something initially.
  432. System.Reflection.Assembly[] initialAssemblies = {
  433. typeof(object).Assembly,
  434. typeof(Uri).Assembly,
  435. typeof(System.Linq.Enumerable).Assembly,
  436. typeof(System.Xml.XmlDocument).Assembly,
  437. typeof(System.Windows.Markup.MarkupExtension).Assembly,
  438. typeof(System.Windows.Rect).Assembly,
  439. typeof(System.Windows.UIElement).Assembly,
  440. typeof(System.Windows.FrameworkElement).Assembly
  441. };
  442. foreach (System.Reflection.Assembly asm in initialAssemblies)
  443. assemblyList.OpenAssembly(asm.Location);
  444. }
  445. public AssemblyTreeNode? FindAssemblyNode(LoadedAssembly asm)
  446. {
  447. return assemblyListTreeNode?.FindAssemblyNode(asm);
  448. }
  449. #region Node Selection
  450. public void SelectNode(SharpTreeNode? node, bool inNewTabPage = false)
  451. {
  452. if (node == null)
  453. return;
  454. if (node.AncestorsAndSelf().Any(item => item.IsHidden))
  455. {
  456. MessageBox.Show(Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation);
  457. return;
  458. }
  459. if (inNewTabPage)
  460. {
  461. DockWorkspace.AddTabPage();
  462. SelectedItem = null;
  463. }
  464. if (SelectedItem == node)
  465. {
  466. Dispatcher.BeginInvoke(RefreshDecompiledView);
  467. }
  468. else
  469. {
  470. activeView?.ScrollIntoView(node);
  471. SelectedItem = node;
  472. Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
  473. activeView?.ScrollIntoView(node);
  474. });
  475. }
  476. }
  477. public void SelectNodes(IEnumerable<SharpTreeNode> nodes)
  478. {
  479. // Ensure nodes exist
  480. var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true))
  481. .ExceptNullItems()
  482. .ToArray();
  483. if (!nodesList.Any() || nodesList.Any(n => n.AncestorsAndSelf().Any(a => a.IsHidden)))
  484. {
  485. return;
  486. }
  487. foreach (var node in nodesList)
  488. {
  489. activeView?.ScrollIntoView(node);
  490. }
  491. SelectedItems = nodesList.ToArray();
  492. }
  493. /// <summary>
  494. /// Retrieves a node using the .ToString() representations of its ancestors.
  495. /// </summary>
  496. public SharpTreeNode? FindNodeByPath(string[]? path, bool returnBestMatch)
  497. {
  498. if (path == null)
  499. return null;
  500. var node = Root;
  501. var bestMatch = node;
  502. foreach (var element in path)
  503. {
  504. if (node == null)
  505. break;
  506. bestMatch = node;
  507. node.EnsureLazyChildren();
  508. if (node is ILSpyTreeNode ilSpyTreeNode)
  509. ilSpyTreeNode.EnsureChildrenFiltered();
  510. node = node.Children.FirstOrDefault(c => c.ToString() == element);
  511. }
  512. return returnBestMatch ? node ?? bestMatch : node;
  513. }
  514. /// <summary>
  515. /// Gets the .ToString() representation of the node's ancestors.
  516. /// </summary>
  517. public static string[]? GetPathForNode(SharpTreeNode? node)
  518. {
  519. if (node == null)
  520. return null;
  521. List<string> path = new List<string>();
  522. while (node.Parent != null)
  523. {
  524. path.Add(node.ToString());
  525. node = node.Parent;
  526. }
  527. path.Reverse();
  528. return path.ToArray();
  529. }
  530. public ILSpyTreeNode? FindTreeNode(object? reference)
  531. {
  532. if (assemblyListTreeNode == null)
  533. return null;
  534. switch (reference)
  535. {
  536. case LoadedAssembly lasm:
  537. return assemblyListTreeNode.FindAssemblyNode(lasm);
  538. case MetadataFile asm:
  539. return assemblyListTreeNode.FindAssemblyNode(asm);
  540. case Resource res:
  541. return assemblyListTreeNode.FindResourceNode(res);
  542. case ValueTuple<Resource, string> resName:
  543. return assemblyListTreeNode.FindResourceNode(resName.Item1, resName.Item2);
  544. case ITypeDefinition type:
  545. return assemblyListTreeNode.FindTypeNode(type);
  546. case IField fd:
  547. return assemblyListTreeNode.FindFieldNode(fd);
  548. case IMethod md:
  549. return assemblyListTreeNode.FindMethodNode(md);
  550. case IProperty pd:
  551. return assemblyListTreeNode.FindPropertyNode(pd);
  552. case IEvent ed:
  553. return assemblyListTreeNode.FindEventNode(ed);
  554. case INamespace nd:
  555. return assemblyListTreeNode.FindNamespaceNode(nd);
  556. default:
  557. return null;
  558. }
  559. }
  560. private void JumpToReference(object? sender, NavigateToReferenceEventArgs e)
  561. {
  562. JumpToReferenceAsync(e.Reference, e.InNewTabPage).HandleExceptions();
  563. IsActive = true;
  564. }
  565. /// <summary>
  566. /// Jumps to the specified reference.
  567. /// </summary>
  568. /// <returns>
  569. /// Returns a task that will signal completion when the decompilation of the jump target has finished.
  570. /// The task will be marked as canceled if the decompilation is canceled.
  571. /// </returns>
  572. private Task JumpToReferenceAsync(object? reference, bool inNewTabPage = false)
  573. {
  574. var decompilationTask = Task.CompletedTask;
  575. switch (reference)
  576. {
  577. case Decompiler.Disassembler.OpCodeInfo opCode:
  578. GlobalUtils.OpenLink(opCode.Link);
  579. break;
  580. case EntityReference unresolvedEntity:
  581. string protocol = unresolvedEntity.Protocol;
  582. var file = unresolvedEntity.ResolveAssembly(AssemblyList);
  583. if (file == null)
  584. {
  585. break;
  586. }
  587. if (protocol != "decompile")
  588. {
  589. foreach (var handler in exportProvider.GetExportedValues<IProtocolHandler>())
  590. {
  591. var node = handler.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage);
  592. if (node != null)
  593. {
  594. SelectNode(node, newTabPage || inNewTabPage);
  595. return decompilationTask;
  596. }
  597. }
  598. }
  599. var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle));
  600. if (possibleToken != null)
  601. {
  602. var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached);
  603. reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value);
  604. goto default;
  605. }
  606. break;
  607. default:
  608. var treeNode = FindTreeNode(reference);
  609. if (treeNode != null)
  610. SelectNode(treeNode, inNewTabPage);
  611. break;
  612. }
  613. return decompilationTask;
  614. }
  615. #endregion
  616. private void LoadAssemblies(IEnumerable<string> fileNames, List<LoadedAssembly>? loadedAssemblies = null, bool focusNode = true)
  617. {
  618. using (Keyboard.FocusedElement.PreserveFocus(!focusNode))
  619. {
  620. AssemblyTreeNode? lastNode = null;
  621. var assemblyList = AssemblyList;
  622. foreach (string file in fileNames)
  623. {
  624. var assembly = assemblyList.OpenAssembly(file);
  625. if (loadedAssemblies != null)
  626. {
  627. loadedAssemblies.Add(assembly);
  628. }
  629. else
  630. {
  631. var node = assemblyListTreeNode?.FindAssemblyNode(assembly);
  632. if (node != null && focusNode)
  633. {
  634. lastNode = node;
  635. activeView?.ScrollIntoView(node);
  636. SelectedItems = [.. SelectedItems, node];
  637. }
  638. }
  639. }
  640. if (focusNode && lastNode != null)
  641. {
  642. activeView?.FocusNode(lastNode);
  643. }
  644. }
  645. }
  646. #region Decompile (TreeView_SelectionChanged)
  647. private void TreeView_SelectionChanged()
  648. {
  649. if (SelectedItems.Length <= 0)
  650. {
  651. // To cancel any pending decompilation requests and show an empty tab
  652. DecompileSelectedNodes();
  653. }
  654. else
  655. {
  656. var activeTabPage = DockWorkspace.ActiveTabPage;
  657. if (!isNavigatingHistory)
  658. {
  659. history.Record(new NavigationState(activeTabPage, SelectedItems));
  660. }
  661. var delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed;
  662. if (!delayDecompilationRequestDueToContextMenu)
  663. {
  664. var decompiledNodes = activeTabPage
  665. .GetState()
  666. ?.DecompiledNodes
  667. ?.Select(n => FindNodeByPath(GetPathForNode(n), true))
  668. .ExceptNullItems()
  669. .ToArray() ?? [];
  670. if (!decompiledNodes.SequenceEqual(SelectedItems))
  671. {
  672. DecompileSelectedNodes();
  673. }
  674. }
  675. else
  676. {
  677. // ensure that we are only connected once to the event, else we might get multiple notifications
  678. ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed;
  679. ContextMenuProvider.ContextMenuClosed += ContextMenuClosed;
  680. }
  681. }
  682. MessageBus.Send(this, new AssemblyTreeSelectionChangedEventArgs());
  683. return;
  684. void ContextMenuClosed(object? sender, EventArgs e)
  685. {
  686. ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed;
  687. Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
  688. if (Mouse.RightButton != MouseButtonState.Pressed)
  689. {
  690. RefreshDecompiledView();
  691. }
  692. });
  693. }
  694. }
  695. public void DecompileSelectedNodes(DecompilerTextViewState? newState = null)
  696. {
  697. var activeTabPage = DockWorkspace.ActiveTabPage;
  698. if (activeTabPage.FrozenContent)
  699. {
  700. activeTabPage = DockWorkspace.AddTabPage();
  701. }
  702. activeTabPage.SupportsLanguageSwitching = true;
  703. if (SelectedItems.Length == 1)
  704. {
  705. if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage))
  706. return;
  707. }
  708. if (newState?.ViewedUri != null)
  709. {
  710. NavigateTo(new(newState.ViewedUri, null));
  711. return;
  712. }
  713. var options = activeTabPage.CreateDecompilationOptions();
  714. options.TextViewState = newState;
  715. activeTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options));
  716. }
  717. public void RefreshDecompiledView()
  718. {
  719. DecompileSelectedNodes(DockWorkspace.ActiveTabPage.GetState() as DecompilerTextViewState);
  720. }
  721. public Language CurrentLanguage => languageService.Language;
  722. public LanguageVersion? CurrentLanguageVersion => languageService.LanguageVersion;
  723. public IEnumerable<ILSpyTreeNode> SelectedNodes => GetTopLevelSelection().OfType<ILSpyTreeNode>();
  724. #endregion
  725. public void NavigateHistory(bool forward)
  726. {
  727. try
  728. {
  729. isNavigatingHistory = true;
  730. TabPageModel tabPage = DockWorkspace.ActiveTabPage;
  731. var state = tabPage.GetState();
  732. if (state != null)
  733. history.UpdateCurrent(new NavigationState(tabPage, state));
  734. var newState = forward ? history.GoForward() : history.GoBack();
  735. TabPageModel activeTabPage = newState.TabPage;
  736. if (!DockWorkspace.TabPages.Contains(activeTabPage))
  737. DockWorkspace.AddTabPage(activeTabPage);
  738. else
  739. DockWorkspace.ActiveTabPage = activeTabPage;
  740. SelectNodes(newState.TreeNodes);
  741. }
  742. finally
  743. {
  744. isNavigatingHistory = false;
  745. }
  746. }
  747. public bool CanNavigateBack => history.CanNavigateBack;
  748. public bool CanNavigateForward => history.CanNavigateForward;
  749. private void NavigateTo(RequestNavigateEventArgs e, bool inNewTabPage = false)
  750. {
  751. if (e.Uri.Scheme == "resource")
  752. {
  753. if (inNewTabPage)
  754. {
  755. DockWorkspace.AddTabPage();
  756. }
  757. if (e.Uri.Host == "aboutpage")
  758. {
  759. RecordHistory();
  760. MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage));
  761. e.Handled = true;
  762. return;
  763. }
  764. AvalonEditTextOutput output = new AvalonEditTextOutput {
  765. Address = e.Uri,
  766. Title = e.Uri.AbsolutePath,
  767. EnableHyperlinks = true
  768. };
  769. using (Stream? s = typeof(App).Assembly.GetManifestResourceStream(typeof(App), e.Uri.AbsolutePath))
  770. {
  771. if (s != null)
  772. {
  773. using StreamReader r = new StreamReader(s);
  774. string? line;
  775. while ((line = r.ReadLine()) != null)
  776. {
  777. output.Write(line);
  778. output.WriteLine();
  779. }
  780. }
  781. }
  782. RecordHistory();
  783. DockWorkspace.ShowText(output);
  784. e.Handled = true;
  785. }
  786. void RecordHistory()
  787. {
  788. if (isNavigatingHistory)
  789. return;
  790. TabPageModel tabPage = DockWorkspace.ActiveTabPage;
  791. var currentState = tabPage.GetState();
  792. if (currentState != null)
  793. history.UpdateCurrent(new NavigationState(tabPage, currentState));
  794. UnselectAll();
  795. history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri }));
  796. }
  797. }
  798. public void Refresh()
  799. {
  800. refreshThrottle.Tick();
  801. }
  802. private void RefreshInternal()
  803. {
  804. using (Keyboard.FocusedElement.PreserveFocus())
  805. {
  806. var path = GetPathForNode(SelectedItem);
  807. ShowAssemblyList(settingsService.AssemblyListManager.LoadList(AssemblyList.ListName));
  808. SelectNode(FindNodeByPath(path, true), inNewTabPage: false);
  809. RefreshDecompiledView();
  810. }
  811. }
  812. private void UnselectAll()
  813. {
  814. SelectedItems = [];
  815. }
  816. private IEnumerable<SharpTreeNode> GetTopLevelSelection()
  817. {
  818. var selection = this.SelectedItems;
  819. var selectionHash = new HashSet<SharpTreeNode>(selection);
  820. return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a)));
  821. }
  822. public void SetActiveView(AssemblyListPane activeView)
  823. {
  824. this.activeView = activeView;
  825. }
  826. public void SortAssemblyList()
  827. {
  828. using (activeView?.LockUpdates())
  829. {
  830. AssemblyList.Sort(AssemblyComparer.Instance);
  831. }
  832. }
  833. private class AssemblyComparer : IComparer<LoadedAssembly>
  834. {
  835. public static readonly AssemblyComparer Instance = new();
  836. int IComparer<LoadedAssembly>.Compare(LoadedAssembly? x, LoadedAssembly? y)
  837. {
  838. return string.Compare(x?.ShortName, y?.ShortName, StringComparison.CurrentCulture);
  839. }
  840. }
  841. public void CollapseAll()
  842. {
  843. using (activeView?.LockUpdates())
  844. {
  845. CollapseChildren(Root);
  846. }
  847. }
  848. private static void CollapseChildren(SharpTreeNode? node)
  849. {
  850. if (node is null)
  851. return;
  852. foreach (var child in node.Children)
  853. {
  854. if (!child.IsExpanded)
  855. continue;
  856. CollapseChildren(child);
  857. child.IsExpanded = false;
  858. }
  859. }
  860. public void OpenFiles(string[] fileNames, bool focusNode = true)
  861. {
  862. if (fileNames == null)
  863. throw new ArgumentNullException(nameof(fileNames));
  864. if (focusNode)
  865. UnselectAll();
  866. LoadAssemblies(fileNames, focusNode: focusNode);
  867. }
  868. private void ApplySessionSettings(object? sender, ApplySessionSettingsEventArgs e)
  869. {
  870. var settings = e.SessionSettings;
  871. settings.ActiveAssemblyList = AssemblyList.ListName;
  872. settings.ActiveTreeViewPath = SelectedPath;
  873. settings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(SelectedItem);
  874. }
  875. private static string? GetAutoLoadedAssemblyNode(SharpTreeNode? node)
  876. {
  877. var assemblyTreeNode = node?
  878. .AncestorsAndSelf()
  879. .OfType<AssemblyTreeNode>()
  880. .FirstOrDefault();
  881. var loadedAssembly = assemblyTreeNode?.LoadedAssembly;
  882. return loadedAssembly is not { IsLoaded: true, IsAutoLoaded: true }
  883. ? null
  884. : loadedAssembly.FileName;
  885. }
  886. private void ActiveTabPageChanged(object? sender, ActiveTabPageChangedEventArgs e)
  887. {
  888. if (e.ViewState is not { } state)
  889. return;
  890. if (state.DecompiledNodes != null)
  891. {
  892. SelectNodes(state.DecompiledNodes);
  893. }
  894. else
  895. {
  896. NavigateTo(new(state.ViewedUri, null));
  897. }
  898. }
  899. private void ResetLayout(object? sender, ResetLayoutEventArgs e)
  900. {
  901. RefreshDecompiledView();
  902. }
  903. }
  904. }