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.
652 lines
19 KiB
652 lines
19 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.IO; |
|
using System.Linq; |
|
using System.Threading.Tasks; |
|
using System.Windows; |
|
using System.Windows.Controls; |
|
using System.Windows.Documents; |
|
|
|
using ICSharpCode.Decompiler; |
|
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; |
|
using ICSharpCode.Decompiler.Metadata; |
|
using ICSharpCode.Decompiler.TypeSystem; |
|
using ICSharpCode.ILSpy.Properties; |
|
using ICSharpCode.ILSpy.ViewModels; |
|
using ICSharpCode.ILSpyX; |
|
using ICSharpCode.ILSpyX.PdbProvider; |
|
using ICSharpCode.TreeView; |
|
|
|
using Microsoft.Win32; |
|
|
|
using TypeDefinitionHandle = System.Reflection.Metadata.TypeDefinitionHandle; |
|
|
|
namespace ICSharpCode.ILSpy.TreeNodes |
|
{ |
|
/// <summary> |
|
/// Tree node representing an assembly. |
|
/// This class is responsible for loading both namespace and type nodes. |
|
/// </summary> |
|
public sealed class AssemblyTreeNode : ILSpyTreeNode |
|
{ |
|
readonly Dictionary<string, NamespaceTreeNode> namespaces = new Dictionary<string, NamespaceTreeNode>(); |
|
readonly Dictionary<TypeDefinitionHandle, TypeTreeNode> typeDict = new Dictionary<TypeDefinitionHandle, TypeTreeNode>(); |
|
ICompilation typeSystem; |
|
|
|
public AssemblyTreeNode(LoadedAssembly assembly) : this(assembly, null) |
|
{ |
|
} |
|
|
|
internal AssemblyTreeNode(LoadedAssembly assembly, PackageEntry packageEntry) |
|
{ |
|
this.LoadedAssembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); |
|
this.LazyLoading = true; |
|
this.PackageEntry = packageEntry; |
|
Init(); |
|
} |
|
|
|
public AssemblyList AssemblyList { |
|
get { return LoadedAssembly.AssemblyList; } |
|
} |
|
|
|
public LoadedAssembly LoadedAssembly { get; } |
|
|
|
/// <summary> |
|
/// If this assembly was loaded from a bundle; this property returns the bundle entry that the |
|
/// assembly was loaded from. |
|
/// </summary> |
|
public PackageEntry PackageEntry { get; } |
|
|
|
public override bool IsAutoLoaded { |
|
get { |
|
return LoadedAssembly.IsAutoLoaded; |
|
} |
|
} |
|
|
|
public override object Text => LoadedAssembly.Text; |
|
|
|
public override object Icon { |
|
get { |
|
if (LoadedAssembly.IsLoaded) |
|
{ |
|
if (LoadedAssembly.HasLoadError) |
|
return Images.AssemblyWarning; |
|
var loadResult = LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult(); |
|
if (loadResult.Package != null) |
|
{ |
|
return loadResult.Package.Kind switch { |
|
LoadedPackage.PackageKind.Zip => Images.NuGet, |
|
_ => Images.Library, |
|
}; |
|
} |
|
return Images.Assembly; |
|
} |
|
else |
|
{ |
|
return Images.FindAssembly; |
|
} |
|
} |
|
} |
|
|
|
TextBlock tooltip; |
|
|
|
public override object ToolTip { |
|
get { |
|
if (LoadedAssembly.HasLoadError) |
|
return "Assembly could not be loaded. Click here for details."; |
|
|
|
if (tooltip == null && LoadedAssembly.IsLoaded) |
|
{ |
|
tooltip = new TextBlock(); |
|
var module = LoadedAssembly.GetPEFileOrNull(); |
|
var metadata = module?.Metadata; |
|
if (metadata?.IsAssembly == true && metadata.TryGetFullAssemblyName(out var assemblyName)) |
|
{ |
|
tooltip.Inlines.Add(new Bold(new Run("Name: "))); |
|
tooltip.Inlines.Add(new Run(assemblyName)); |
|
tooltip.Inlines.Add(new LineBreak()); |
|
} |
|
tooltip.Inlines.Add(new Bold(new Run("Location: "))); |
|
tooltip.Inlines.Add(new Run(LoadedAssembly.FileName)); |
|
if (module != null) |
|
{ |
|
tooltip.Inlines.Add(new LineBreak()); |
|
tooltip.Inlines.Add(new Bold(new Run("Architecture: "))); |
|
tooltip.Inlines.Add(new Run(Language.GetPlatformDisplayName(module))); |
|
string runtimeName = Language.GetRuntimeDisplayName(module); |
|
if (runtimeName != null) |
|
{ |
|
tooltip.Inlines.Add(new LineBreak()); |
|
tooltip.Inlines.Add(new Bold(new Run("Runtime: "))); |
|
tooltip.Inlines.Add(new Run(runtimeName)); |
|
} |
|
var debugInfo = LoadedAssembly.GetDebugInfoOrNull(); |
|
tooltip.Inlines.Add(new LineBreak()); |
|
tooltip.Inlines.Add(new Bold(new Run("Debug info: "))); |
|
tooltip.Inlines.Add(new Run(debugInfo?.Description ?? "none")); |
|
} |
|
} |
|
|
|
return tooltip; |
|
} |
|
} |
|
|
|
public override bool ShowExpander { |
|
get { return !LoadedAssembly.HasLoadError; } |
|
} |
|
|
|
async void Init() |
|
{ |
|
try |
|
{ |
|
await this.LoadedAssembly.GetLoadResultAsync(); |
|
RaisePropertyChanged(nameof(Text)); // shortname might have changed |
|
} |
|
catch |
|
{ |
|
RaisePropertyChanged(nameof(ShowExpander)); // cannot expand assemblies with load error |
|
} |
|
// change from "Loading" icon to final icon |
|
RaisePropertyChanged(nameof(Icon)); |
|
RaisePropertyChanged(nameof(ExpandedIcon)); |
|
RaisePropertyChanged(nameof(ToolTip)); |
|
} |
|
|
|
protected override void LoadChildren() |
|
{ |
|
LoadedAssembly.LoadResult loadResult; |
|
try |
|
{ |
|
loadResult = LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult(); |
|
} |
|
catch |
|
{ |
|
// if we crashed on loading, then we don't have any children |
|
return; |
|
} |
|
try |
|
{ |
|
if (loadResult.PEFile != null) |
|
{ |
|
LoadChildrenForPEFile(loadResult.PEFile); |
|
} |
|
else if (loadResult.Package != null) |
|
{ |
|
var package = loadResult.Package; |
|
this.Children.AddRange(PackageFolderTreeNode.LoadChildrenForFolder(package.RootFolder)); |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
App.UnhandledException(ex); |
|
} |
|
} |
|
|
|
void LoadChildrenForPEFile(PEFile module) |
|
{ |
|
typeSystem = LoadedAssembly.GetTypeSystemOrNull(); |
|
var assembly = (MetadataModule)typeSystem.MainModule; |
|
this.Children.Add(new Metadata.MetadataTreeNode(module, this)); |
|
Decompiler.DebugInfo.IDebugInfoProvider debugInfo = LoadedAssembly.GetDebugInfoOrNull(); |
|
if (debugInfo is PortableDebugInfoProvider ppdb |
|
&& ppdb.GetMetadataReader() is System.Reflection.Metadata.MetadataReader reader) |
|
{ |
|
this.Children.Add(new Metadata.DebugMetadataTreeNode(module, ppdb.IsEmbedded, reader, this)); |
|
} |
|
this.Children.Add(new ReferenceFolderTreeNode(module, this)); |
|
if (module.Resources.Any()) |
|
this.Children.Add(new ResourceListTreeNode(module)); |
|
foreach (NamespaceTreeNode ns in namespaces.Values) |
|
{ |
|
ns.Children.Clear(); |
|
} |
|
foreach (var type in assembly.TopLevelTypeDefinitions.OrderBy(t => t.ReflectionName, NaturalStringComparer.Instance)) |
|
{ |
|
var escapedNamespace = Language.EscapeName(type.Namespace); |
|
if (!namespaces.TryGetValue(type.Namespace, out NamespaceTreeNode ns)) |
|
{ |
|
ns = new NamespaceTreeNode(escapedNamespace); |
|
namespaces.Add(type.Namespace, ns); |
|
} |
|
TypeTreeNode node = new TypeTreeNode(type, this); |
|
typeDict[(TypeDefinitionHandle)type.MetadataToken] = node; |
|
ns.Children.Add(node); |
|
} |
|
foreach (NamespaceTreeNode ns in namespaces.Values.OrderBy(n => n.Name, NaturalStringComparer.Instance)) |
|
{ |
|
if (ns.Children.Count > 0) |
|
this.Children.Add(ns); |
|
ns.SetPublicAPI(ns.Children.OfType<ILSpyTreeNode>().Any(n => n.IsPublicAPI)); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Finds the node for a top-level type. |
|
/// </summary> |
|
public TypeTreeNode FindTypeNode(ITypeDefinition type) |
|
{ |
|
if (type == null) |
|
return null; |
|
EnsureLazyChildren(); |
|
TypeTreeNode node; |
|
if (typeDict.TryGetValue((TypeDefinitionHandle)type.MetadataToken, out node)) |
|
return node; |
|
else |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Finds the node for a namespace. |
|
/// </summary> |
|
public NamespaceTreeNode FindNamespaceNode(string namespaceName) |
|
{ |
|
if (string.IsNullOrEmpty(namespaceName)) |
|
return null; |
|
EnsureLazyChildren(); |
|
NamespaceTreeNode node; |
|
if (namespaces.TryGetValue(namespaceName, out node)) |
|
return node; |
|
else |
|
return null; |
|
} |
|
|
|
public override bool CanDrag(SharpTreeNode[] nodes) |
|
{ |
|
// prohibit dragging assemblies nested in nuget packages |
|
return nodes.All(n => n is AssemblyTreeNode { PackageEntry: null }); |
|
} |
|
|
|
public override void StartDrag(DependencyObject dragSource, SharpTreeNode[] nodes) |
|
{ |
|
DragDrop.DoDragDrop(dragSource, Copy(nodes), DragDropEffects.All); |
|
} |
|
|
|
public override bool CanDelete() |
|
{ |
|
// prohibit deleting assemblies nested in nuget packages |
|
return PackageEntry == null; |
|
} |
|
|
|
public override void Delete() |
|
{ |
|
DeleteCore(); |
|
} |
|
|
|
public override void DeleteCore() |
|
{ |
|
LoadedAssembly.AssemblyList.Unload(LoadedAssembly); |
|
} |
|
|
|
internal const string DataFormat = "ILSpyAssemblies"; |
|
|
|
public override IDataObject Copy(SharpTreeNode[] nodes) |
|
{ |
|
DataObject dataObject = new DataObject(); |
|
dataObject.SetData(DataFormat, nodes.OfType<AssemblyTreeNode>().Select(n => n.LoadedAssembly.FileName).ToArray()); |
|
return dataObject; |
|
} |
|
|
|
public override FilterResult Filter(FilterSettings settings) |
|
{ |
|
if (settings.SearchTermMatches(LoadedAssembly.ShortName)) |
|
return FilterResult.Match; |
|
else |
|
return FilterResult.Recurse; |
|
} |
|
|
|
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
|
{ |
|
void HandleException(Exception ex, string message) |
|
{ |
|
language.WriteCommentLine(output, message); |
|
|
|
output.WriteLine(); |
|
output.MarkFoldStart("Exception details", true); |
|
output.Write(ex.ToString()); |
|
output.MarkFoldEnd(); |
|
} |
|
|
|
try |
|
{ |
|
var loadResult = LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult(); |
|
if (loadResult.PEFile != null) |
|
{ |
|
language.DecompileAssembly(LoadedAssembly, output, options); |
|
} |
|
else if (loadResult.Package != null) |
|
{ |
|
output.WriteLine("// " + LoadedAssembly.FileName); |
|
DecompilePackage(loadResult.Package, output); |
|
} |
|
else |
|
{ |
|
LoadedAssembly.GetPEFileOrNullAsync().GetAwaiter().GetResult(); |
|
} |
|
} |
|
catch (BadImageFormatException badImage) |
|
{ |
|
HandleException(badImage, "This file does not contain a managed assembly."); |
|
} |
|
catch (FileNotFoundException fileNotFound) when (options.SaveAsProjectDirectory == null) |
|
{ |
|
HandleException(fileNotFound, "The file was not found."); |
|
} |
|
catch (DirectoryNotFoundException dirNotFound) when (options.SaveAsProjectDirectory == null) |
|
{ |
|
HandleException(dirNotFound, "The directory was not found."); |
|
} |
|
catch (PEFileNotSupportedException notSupported) |
|
{ |
|
HandleException(notSupported, notSupported.Message); |
|
} |
|
} |
|
|
|
private void DecompilePackage(LoadedPackage package, ITextOutput output) |
|
{ |
|
switch (package.Kind) |
|
{ |
|
case LoadedPackage.PackageKind.Zip: |
|
output.WriteLine("// File format: .zip file"); |
|
break; |
|
case LoadedPackage.PackageKind.Bundle: |
|
var header = package.BundleHeader; |
|
output.WriteLine($"// File format: .NET bundle {header.MajorVersion}.{header.MinorVersion}"); |
|
break; |
|
} |
|
output.WriteLine(); |
|
output.WriteLine("Entries:"); |
|
foreach (var entry in package.Entries) |
|
{ |
|
output.WriteLine(" " + entry.Name); |
|
} |
|
} |
|
|
|
public override bool Save(TabPageModel tabPage) |
|
{ |
|
if (!LoadedAssembly.IsLoadedAsValidAssembly) |
|
return false; |
|
Language language = this.Language; |
|
if (string.IsNullOrEmpty(language.ProjectFileExtension)) |
|
return false; |
|
SaveFileDialog dlg = new SaveFileDialog(); |
|
dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName) + language.ProjectFileExtension; |
|
dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*"; |
|
if (dlg.ShowDialog() == true) |
|
{ |
|
DecompilationOptions options = new DecompilationOptions(); |
|
options.FullDecompilation = true; |
|
if (dlg.FilterIndex == 1) |
|
{ |
|
options.SaveAsProjectDirectory = Path.GetDirectoryName(dlg.FileName); |
|
foreach (string entry in Directory.GetFileSystemEntries(options.SaveAsProjectDirectory)) |
|
{ |
|
if (!string.Equals(entry, dlg.FileName, StringComparison.OrdinalIgnoreCase)) |
|
{ |
|
var result = MessageBox.Show( |
|
Resources.AssemblySaveCodeDirectoryNotEmpty, |
|
Resources.AssemblySaveCodeDirectoryNotEmptyTitle, |
|
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); |
|
if (result == MessageBoxResult.No) |
|
return true; // don't save, but mark the Save operation as handled |
|
break; |
|
} |
|
} |
|
} |
|
tabPage.ShowTextView(textView => textView.SaveToDisk(language, new[] { this }, options, dlg.FileName)); |
|
} |
|
return true; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
// ToString is used by FindNodeByPath/GetPathForNode |
|
// Fixes #821 - Reload All Assemblies Should Point to the Correct Assembly |
|
return LoadedAssembly.FileName; |
|
} |
|
} |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources._Remove), Icon = "images/Delete")] |
|
sealed class RemoveAssembly : IContextMenuEntry |
|
{ |
|
public bool IsVisible(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes.All(n => n is AssemblyTreeNode); |
|
} |
|
|
|
public bool IsEnabled(TextViewContext context) |
|
{ |
|
return true; |
|
} |
|
|
|
public void Execute(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return; |
|
foreach (var node in context.SelectedTreeNodes) |
|
{ |
|
node.Delete(); |
|
} |
|
} |
|
} |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources._Reload), Icon = "images/Refresh")] |
|
sealed class ReloadAssembly : IContextMenuEntry |
|
{ |
|
public bool IsVisible(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes.All(n => n is AssemblyTreeNode); |
|
} |
|
|
|
public bool IsEnabled(TextViewContext context) |
|
{ |
|
return true; |
|
} |
|
|
|
public void Execute(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return; |
|
var paths = new List<string[]>(); |
|
using (context.TreeView.LockUpdates()) |
|
{ |
|
foreach (var node in context.SelectedTreeNodes) |
|
{ |
|
paths.Add(MainWindow.GetPathForNode(node)); |
|
var la = ((AssemblyTreeNode)node).LoadedAssembly; |
|
la.AssemblyList.ReloadAssembly(la.FileName); |
|
} |
|
} |
|
MainWindow.Instance.SelectNodes(paths.Select(p => MainWindow.Instance.FindNodeByPath(p, true)).ToArray()); |
|
MainWindow.Instance.RefreshDecompiledView(); |
|
} |
|
} |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources._LoadDependencies), Category = nameof(Resources.Dependencies))] |
|
sealed class LoadDependencies : IContextMenuEntry |
|
{ |
|
public bool IsVisible(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes.All(n => n is AssemblyTreeNode asm && asm.LoadedAssembly.IsLoadedAsValidAssembly); |
|
} |
|
|
|
public bool IsEnabled(TextViewContext context) |
|
{ |
|
return true; |
|
} |
|
|
|
public async void Execute(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return; |
|
var tasks = new List<Task>(); |
|
foreach (var node in context.SelectedTreeNodes) |
|
{ |
|
var la = ((AssemblyTreeNode)node).LoadedAssembly; |
|
var resolver = la.GetAssemblyResolver(); |
|
var module = la.GetPEFileOrNull(); |
|
if (module != null) |
|
{ |
|
var metadata = module.Metadata; |
|
foreach (var assyRef in metadata.AssemblyReferences) |
|
{ |
|
tasks.Add(resolver.ResolveAsync(new AssemblyReference(module, assyRef))); |
|
} |
|
} |
|
} |
|
await Task.WhenAll(tasks); |
|
MainWindow.Instance.RefreshDecompiledView(); |
|
} |
|
} |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources._AddMainList), Category = nameof(Resources.Dependencies))] |
|
sealed class AddToMainList : IContextMenuEntry |
|
{ |
|
public bool IsVisible(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes.Where(n => n is AssemblyTreeNode).Any(n => ((AssemblyTreeNode)n).IsAutoLoaded); |
|
} |
|
|
|
public bool IsEnabled(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes.Any(n => n is AssemblyTreeNode); |
|
} |
|
|
|
public void Execute(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return; |
|
foreach (var node in context.SelectedTreeNodes) |
|
{ |
|
var loadedAssm = ((AssemblyTreeNode)node).LoadedAssembly; |
|
if (!loadedAssm.HasLoadError) |
|
{ |
|
loadedAssm.IsAutoLoaded = false; |
|
node.RaisePropertyChanged(nameof(ILSpyTreeNode.IsAutoLoaded)); |
|
} |
|
} |
|
MainWindow.Instance.CurrentAssemblyList.RefreshSave(); |
|
} |
|
} |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources._OpenContainingFolder), Category = nameof(Resources.Shell))] |
|
sealed class OpenContainingFolder : IContextMenuEntry |
|
{ |
|
public bool IsVisible(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes |
|
.All(n => { |
|
var a = GetAssemblyTreeNode(n); |
|
return a != null && File.Exists(a.LoadedAssembly.FileName); |
|
}); |
|
} |
|
|
|
internal static AssemblyTreeNode GetAssemblyTreeNode(SharpTreeNode node) |
|
{ |
|
while (node != null) |
|
{ |
|
if (node is AssemblyTreeNode a) |
|
return a; |
|
node = node.Parent; |
|
} |
|
return null; |
|
} |
|
|
|
public bool IsEnabled(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes |
|
.All(n => { |
|
var a = GetAssemblyTreeNode(n); |
|
return a != null && File.Exists(a.LoadedAssembly.FileName); |
|
}); |
|
} |
|
|
|
public void Execute(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return; |
|
foreach (var n in context.SelectedTreeNodes) |
|
{ |
|
var node = GetAssemblyTreeNode(n); |
|
var path = node.LoadedAssembly.FileName; |
|
if (File.Exists(path)) |
|
{ |
|
MainWindow.ExecuteCommand("explorer.exe", $"/select,\"{path}\""); |
|
} |
|
} |
|
} |
|
} |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources._OpenCommandLineHere), Category = nameof(Resources.Shell))] |
|
sealed class OpenCmdHere : IContextMenuEntry |
|
{ |
|
public bool IsVisible(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes |
|
.All(n => { |
|
var a = OpenContainingFolder.GetAssemblyTreeNode(n); |
|
return a != null && File.Exists(a.LoadedAssembly.FileName); |
|
}); |
|
} |
|
|
|
public bool IsEnabled(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return false; |
|
return context.SelectedTreeNodes |
|
.All(n => { |
|
var a = OpenContainingFolder.GetAssemblyTreeNode(n); |
|
return a != null && File.Exists(a.LoadedAssembly.FileName); |
|
}); |
|
} |
|
|
|
public void Execute(TextViewContext context) |
|
{ |
|
if (context.SelectedTreeNodes == null) |
|
return; |
|
foreach (var n in context.SelectedTreeNodes) |
|
{ |
|
var node = OpenContainingFolder.GetAssemblyTreeNode(n); |
|
var path = Path.GetDirectoryName(node.LoadedAssembly.FileName); |
|
if (Directory.Exists(path)) |
|
{ |
|
MainWindow.ExecuteCommand("cmd.exe", $"/k \"cd {path}\""); |
|
} |
|
} |
|
} |
|
} |
|
|
|
}
|
|
|