// 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.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection.Metadata; 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; using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.TreeView; using TomsToolbox.Composition; using TomsToolbox.Essentials; using TomsToolbox.Wpf; #nullable enable namespace ICSharpCode.ILSpy.AssemblyTree { [ExportToolPane] [Shared] public class AssemblyTreeModel : ToolPaneModel { public const string PaneContentId = "assemblyListPane"; private AssemblyListPane? activeView; private AssemblyListTreeNode? assemblyListTreeNode; private readonly DispatcherThrottle refreshThrottle; private readonly NavigationHistory history = new(); private bool isNavigatingHistory; private readonly SettingsService settingsService; private readonly LanguageService languageService; private readonly IExportProvider exportProvider; 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 += 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) { switch (e.PropertyName) { case nameof(SessionSettings.ActiveAssemblyList): ShowAssemblyList(sessionSettings.ActiveAssemblyList); RefreshDecompiledView(); break; case nameof(SessionSettings.Theme): // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) DecompilerTextView.RegisterHighlighting(); RefreshDecompiledView(); break; case nameof(SessionSettings.CurrentCulture): MessageBox.Show(Resources.SettingsChangeRestartRequired, "ILSpy"); break; } } else if (sender is LanguageSettings) { switch (e.PropertyName) { case nameof(LanguageSettings.LanguageId) or nameof(LanguageSettings.LanguageVersionId): RefreshDecompiledView(); break; default: Refresh(); break; } } } public AssemblyList AssemblyList { get; private set; } private SharpTreeNode? root; public SharpTreeNode? Root { get => root; set => SetProperty(ref root, value); } public SharpTreeNode? SelectedItem { get => SelectedItems.FirstOrDefault(); set => SelectedItems = value is null ? [] : [value]; } private SharpTreeNode[] selectedItems = []; public SharpTreeNode[] SelectedItems { get => selectedItems; set { if (selectedItems.SequenceEqual(value)) return; selectedItems = value; OnPropertyChanged(); TreeView_SelectionChanged(); } } public string[]? SelectedPath => GetPathForNode(SelectedItem); private readonly List commandLineLoadedAssemblies = []; private bool HandleCommandLineArguments(CommandLineArguments args) { LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); if (args.Language != null) languageService.Language = languageService.GetLanguage(args.Language); return true; } /// /// Called on startup or when passed arguments via WndProc from a second instance. /// In the format case, updateSettings is non-null; in the latter it is null. /// private async Task HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, UpdateSettings? updateSettings = null) { var sessionSettings = settingsService.SessionSettings; var relevantAssemblies = commandLineLoadedAssemblies.ToList(); commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore await NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, updateSettings, relevantAssemblies); if (args.Search != null) { MessageBus.Send(this, new ShowSearchPageEventArgs(args.Search)); } } public async Task HandleSingleInstanceCommandLineArguments(string[] args) { var cmdArgs = CommandLineArguments.Create(args); await Dispatcher.InvokeAsync(async () => { if (!HandleCommandLineArguments(cmdArgs)) return; var window = Application.Current.MainWindow; if (!cmdArgs.NoActivate && window is { WindowState: WindowState.Minimized }) { window.WindowState = WindowState.Normal; } await HandleCommandLineArgumentsAfterShowList(cmdArgs); }); } private async Task NavigateOnLaunch(string? navigateTo, string[]? activeTreeViewPath, UpdateSettings? updateSettings, List relevantAssemblies) { var initialSelection = SelectedItem; if (navigateTo != null) { bool found = false; if (navigateTo.StartsWith("N:", StringComparison.Ordinal)) { string namespaceName = navigateTo.Substring(2); foreach (LoadedAssembly asm in relevantAssemblies) { var 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(_ => { }); NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName); if (nsNode != null) { found = true; if (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 is { ParentModule.MetadataFile: not null }) { found = true; if (SelectedItem == initialSelection) { await JumpToReferenceAsync(mr); } } } if (!found && SelectedItem == initialSelection) { AvalonEditTextOutput output = new AvalonEditTextOutput(); output.Write($"Cannot find '{navigateTo}' in command line specified assemblies."); DockWorkspace.ShowText(output); } } else if (relevantAssemblies.Count == 1) { // NavigateTo == null and an assembly was given on the command-line: // Select the newly loaded assembly var asmNode = assemblyListTreeNode?.FindAssemblyNode(relevantAssemblies[0]); if (asmNode != null && SelectedItem == initialSelection) { SelectNode(asmNode); } } else if (updateSettings != null) { SharpTreeNode? node = null; if (activeTreeViewPath?.Length > 0) { foreach (var asm in AssemblyList.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(_ => { }); } } node = FindNodeByPath(activeTreeViewPath, true); } if (SelectedItem == initialSelection) { if (node != null) { SelectNode(node); // only if not showing the about page, perform the update check: MessageBus.Send(this, new CheckIfUpdateAvailableEventArgs()); } else { MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage)); } } } } public static IEntity? FindEntityInRelevantAssemblies(string navigateTo, IEnumerable relevantAssemblies) { ITypeReference typeRef; 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 (module != null && 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 : memberRef.Resolve(new SimpleTypeResolveContext(compilation)); } } return null; } private 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; } } public void Initialize() { AssemblyList = settingsService.LoadInitialAssemblyList(); HandleCommandLineArguments(App.CommandLineArguments); var loadPreviousAssemblies = settingsService.MiscSettings.LoadPreviousAssemblies; if (AssemblyList.GetAssemblies().Length == 0 && AssemblyList.ListName == AssemblyListManager.DefaultListName && loadPreviousAssemblies) { LoadInitialAssemblies(AssemblyList); } ShowAssemblyList(AssemblyList); var sessionSettings = settingsService.SessionSettings; if (sessionSettings.ActiveAutoLoadedAssembly != null && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) { AssemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); } Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies); } private async Task OpenAssemblies() { await HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, settingsService.GetSettings()); if (FormatExceptions(App.StartupExceptions.ToArray(), out var output)) { output.Title = "Startup errors"; DockWorkspace.AddTabPage(); DockWorkspace.ShowText(output); } } private static bool FormatExceptions(App.ExceptionData[] exceptions, [NotNullWhen(true)] out AvalonEditTextOutput? output) { output = null; var result = exceptions.FormatExceptions(); if (result.IsNullOrEmpty()) return false; output = new(); output.Write(result); return true; } private void ShowAssemblyList(string name) { AssemblyList list = settingsService.AssemblyListManager.LoadList(name); //Only load a new list when it is a different one if (list.ListName != AssemblyList.ListName) { ShowAssemblyList(list); SelectNode(Root?.Children.FirstOrDefault()); } } private void ShowAssemblyList(AssemblyList assemblyList) { history.Clear(); AssemblyList.CollectionChanged -= assemblyList_CollectionChanged; AssemblyList = assemblyList; assemblyList.CollectionChanged += assemblyList_CollectionChanged; assemblyListTreeNode = new(assemblyList) { Select = x => SelectNode(x) }; Root = assemblyListTreeNode; var mainWindow = Application.Current?.MainWindow; if (mainWindow == null) return; if (assemblyList.ListName == AssemblyListManager.DefaultListName) #if DEBUG mainWindow.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}"; #else mainWindow.Title = "ILSpy"; #endif else #if DEBUG mainWindow.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName; #else mainWindow.Title = "ILSpy - " + assemblyList.ListName; #endif } private void assemblyList_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Reset) { history.RemoveAll(_ => true); } if (e.OldItems != null) { var oldAssemblies = new HashSet(e.OldItems.Cast()); history.RemoveAll(n => n.TreeNodes.Any( nd => nd.AncestorsAndSelf().OfType().Any( a => oldAssemblies.Contains(a.LoadedAssembly)))); } 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); } #region Node Selection public void SelectNode(SharpTreeNode? node, bool inNewTabPage = false) { if (node == null) return; if (node.AncestorsAndSelf().Any(item => item.IsHidden)) { MessageBox.Show(Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation); return; } if (inNewTabPage) { DockWorkspace.AddTabPage(); SelectedItem = null; } if (SelectedItem == node) { Dispatcher.BeginInvoke(RefreshDecompiledView); } else { activeView?.ScrollIntoView(node); SelectedItem = node; Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { activeView?.ScrollIntoView(node); }); } } public void SelectNodes(IEnumerable nodes) { // Ensure nodes exist var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) .ExceptNullItems() .ToArray(); if (!nodesList.Any() || nodesList.Any(n => n.AncestorsAndSelf().Any(a => a.IsHidden))) { return; } foreach (var node in nodesList) { activeView?.ScrollIntoView(node); } SelectedItems = nodesList.ToArray(); } /// /// Retrieves a node using the .ToString() representations of its ancestors. /// public SharpTreeNode? FindNodeByPath(string[]? path, bool returnBestMatch) { if (path == null) return null; var node = Root; var bestMatch = node; foreach (var element in path) { if (node == null) break; bestMatch = node; node.EnsureLazyChildren(); if (node is ILSpyTreeNode ilSpyTreeNode) ilSpyTreeNode.EnsureChildrenFiltered(); node = node.Children.FirstOrDefault(c => c.ToString() == element); } return returnBestMatch ? node ?? bestMatch : node; } /// /// Gets the .ToString() representation of the node's ancestors. /// public static string[]? GetPathForNode(SharpTreeNode? node) { if (node == null) return null; List path = new List(); while (node.Parent != null) { path.Add(node.ToString()); node = node.Parent; } path.Reverse(); return path.ToArray(); } public ILSpyTreeNode? FindTreeNode(object? reference) { if (assemblyListTreeNode == null) return null; 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 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; } } private void JumpToReference(object? sender, NavigateToReferenceEventArgs e) { JumpToReferenceAsync(e.Reference, e.InNewTabPage).HandleExceptions(); IsActive = true; } /// /// Jumps to the specified reference. /// /// /// 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. /// private Task JumpToReferenceAsync(object? reference, bool inNewTabPage = false) { var decompilationTask = Task.CompletedTask; switch (reference) { case Decompiler.Disassembler.OpCodeInfo opCode: GlobalUtils.OpenLink(opCode.Link); break; case EntityReference unresolvedEntity: string protocol = unresolvedEntity.Protocol; var file = unresolvedEntity.ResolveAssembly(AssemblyList); if (file == null) { break; } if (protocol != "decompile") { foreach (var handler in exportProvider.GetExportedValues()) { var node = handler.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: var treeNode = FindTreeNode(reference); if (treeNode != null) SelectNode(treeNode, inNewTabPage); break; } return decompilationTask; } #endregion private void LoadAssemblies(IEnumerable fileNames, List? loadedAssemblies = null, bool focusNode = true) { using (Keyboard.FocusedElement.PreserveFocus(!focusNode)) { AssemblyTreeNode? lastNode = null; var assemblyList = AssemblyList; foreach (string file in fileNames) { var assembly = assemblyList.OpenAssembly(file); if (loadedAssemblies != null) { loadedAssemblies.Add(assembly); } else { var node = assemblyListTreeNode?.FindAssemblyNode(assembly); if (node != null && focusNode) { lastNode = node; activeView?.ScrollIntoView(node); SelectedItems = [.. SelectedItems, node]; } } } if (focusNode && lastNode != null) { activeView?.FocusNode(lastNode); } } } #region Decompile (TreeView_SelectionChanged) private void TreeView_SelectionChanged() { if (SelectedItems.Length <= 0) { // To cancel any pending decompilation requests and show an empty tab DecompileSelectedNodes(); } else { var activeTabPage = DockWorkspace.ActiveTabPage; if (!isNavigatingHistory) { history.Record(new NavigationState(activeTabPage, SelectedItems)); } var delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; if (!delayDecompilationRequestDueToContextMenu) { var decompiledNodes = activeTabPage .GetState() ?.DecompiledNodes ?.Select(n => FindNodeByPath(GetPathForNode(n), true)) .ExceptNullItems() .ToArray() ?? []; if (!decompiledNodes.SequenceEqual(SelectedItems)) { DecompileSelectedNodes(); } } else { // ensure that we are only connected once to the event, else we might get multiple notifications ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed; ContextMenuProvider.ContextMenuClosed += ContextMenuClosed; } } MessageBus.Send(this, new AssemblyTreeSelectionChangedEventArgs()); return; void ContextMenuClosed(object? sender, EventArgs e) { ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed; Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { if (Mouse.RightButton != MouseButtonState.Pressed) { RefreshDecompiledView(); } }); } } public void DecompileSelectedNodes(DecompilerTextViewState? newState = null) { var activeTabPage = DockWorkspace.ActiveTabPage; if (activeTabPage.FrozenContent) { activeTabPage = DockWorkspace.AddTabPage(); } activeTabPage.SupportsLanguageSwitching = true; if (SelectedItems.Length == 1) { if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage)) return; } if (newState?.ViewedUri != null) { NavigateTo(new(newState.ViewedUri, null)); return; } var options = activeTabPage.CreateDecompilationOptions(); options.TextViewState = newState; activeTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); } public void RefreshDecompiledView() { DecompileSelectedNodes(DockWorkspace.ActiveTabPage.GetState() as DecompilerTextViewState); } public Language CurrentLanguage => languageService.Language; public LanguageVersion? CurrentLanguageVersion => languageService.LanguageVersion; public IEnumerable SelectedNodes => GetTopLevelSelection().OfType(); #endregion public void NavigateHistory(bool forward) { try { isNavigatingHistory = true; TabPageModel tabPage = DockWorkspace.ActiveTabPage; var state = tabPage.GetState(); if (state != null) history.UpdateCurrent(new NavigationState(tabPage, state)); var newState = forward ? history.GoForward() : history.GoBack(); TabPageModel activeTabPage = newState.TabPage; if (!DockWorkspace.TabPages.Contains(activeTabPage)) DockWorkspace.AddTabPage(activeTabPage); else DockWorkspace.ActiveTabPage = activeTabPage; SelectNodes(newState.TreeNodes); } finally { isNavigatingHistory = false; } } public bool CanNavigateBack => history.CanNavigateBack; public bool CanNavigateForward => history.CanNavigateForward; private void NavigateTo(RequestNavigateEventArgs e, bool inNewTabPage = false) { if (e.Uri.Scheme == "resource") { if (inNewTabPage) { DockWorkspace.AddTabPage(); } if (e.Uri.Host == "aboutpage") { RecordHistory(); MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage)); 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)) { if (s != null) { using StreamReader r = new StreamReader(s); string? line; while ((line = r.ReadLine()) != null) { output.Write(line); output.WriteLine(); } } } RecordHistory(); DockWorkspace.ShowText(output); e.Handled = true; } void RecordHistory() { if (isNavigatingHistory) return; TabPageModel tabPage = DockWorkspace.ActiveTabPage; var currentState = tabPage.GetState(); if (currentState != null) history.UpdateCurrent(new NavigationState(tabPage, currentState)); UnselectAll(); history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); } } public void Refresh() { refreshThrottle.Tick(); } private void RefreshInternal() { using (Keyboard.FocusedElement.PreserveFocus()) { var path = GetPathForNode(SelectedItem); ShowAssemblyList(settingsService.AssemblyListManager.LoadList(AssemblyList.ListName)); SelectNode(FindNodeByPath(path, true), inNewTabPage: false); RefreshDecompiledView(); } } private void UnselectAll() { SelectedItems = []; } private IEnumerable GetTopLevelSelection() { var selection = this.SelectedItems; var selectionHash = new HashSet(selection); return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); } public void SetActiveView(AssemblyListPane activeView) { this.activeView = activeView; } public void SortAssemblyList() { using (activeView?.LockUpdates()) { AssemblyList.Sort(AssemblyComparer.Instance); } } private class AssemblyComparer : IComparer { public static readonly AssemblyComparer Instance = new(); int IComparer.Compare(LoadedAssembly? x, LoadedAssembly? y) { return string.Compare(x?.ShortName, y?.ShortName, StringComparison.CurrentCulture); } } public void CollapseAll() { using (activeView?.LockUpdates()) { CollapseChildren(Root); } } private static void CollapseChildren(SharpTreeNode? node) { if (node is null) return; foreach (var child in node.Children) { if (!child.IsExpanded) continue; CollapseChildren(child); child.IsExpanded = false; } } public void OpenFiles(string[] fileNames, bool focusNode = true) { if (fileNames == null) throw new ArgumentNullException(nameof(fileNames)); if (focusNode) UnselectAll(); LoadAssemblies(fileNames, focusNode: focusNode); } private void ApplySessionSettings(object? sender, ApplySessionSettingsEventArgs e) { var settings = e.SessionSettings; settings.ActiveAssemblyList = AssemblyList.ListName; settings.ActiveTreeViewPath = SelectedPath; settings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(SelectedItem); } private static string? GetAutoLoadedAssemblyNode(SharpTreeNode? node) { var assemblyTreeNode = node? .AncestorsAndSelf() .OfType() .FirstOrDefault(); var loadedAssembly = assemblyTreeNode?.LoadedAssembly; return loadedAssembly is not { IsLoaded: true, IsAutoLoaded: true } ? null : loadedAssembly.FileName; } private void ActiveTabPageChanged(object? sender, ActiveTabPageChangedEventArgs e) { if (e.ViewState is not { } state) return; if (state.DecompiledNodes != null) { SelectNodes(state.DecompiledNodes); } else { NavigateTo(new(state.ViewedUri, null)); } } private void ResetLayout(object? sender, ResetLayoutEventArgs e) { RefreshDecompiledView(); } } }