using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; namespace ICSharpCode.ILSpy.AddIn.Commands { class OpenCodeItemCommand : ILSpyCommand { static OpenCodeItemCommand instance; public OpenCodeItemCommand(ILSpyAddInPackage owner) : base(owner, PkgCmdIDList.cmdidOpenCodeItemInILSpy) { } protected override void OnBeforeQueryStatus(object sender, EventArgs e) { if (sender is OleMenuCommand menuItem) { menuItem.Visible = false; // Enable this item only if this is a .cs file! if (Utils.GetCurrentViewHost(owner, f => f.EndsWith(".cs")) == null) return; // Enable this item only if this is a Roslyn document if (GetRoslynDocument() == null) return; var document = owner.DTE.ActiveDocument; menuItem.Visible = (document != null) && (document.ProjectItem != null) && (document.ProjectItem.ContainingProject != null) && (document.ProjectItem.ContainingProject.ConfigurationManager != null) && !string.IsNullOrEmpty(document.ProjectItem.ContainingProject.FileName); } } Document GetRoslynDocument() { var document = owner.DTE.ActiveDocument; var selection = (EnvDTE.TextPoint)((EnvDTE.TextSelection)document.Selection).ActivePoint; var id = owner.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FullName).FirstOrDefault(); if (id == null) return null; return owner.Workspace.CurrentSolution.GetDocument(id); } EnvDTE.TextPoint GetEditorSelection() { var document = owner.DTE.ActiveDocument; return ((EnvDTE.TextSelection)document.Selection).ActivePoint; } protected override async void OnExecute(object sender, EventArgs e) { var textView = Utils.GetCurrentViewHost(owner)?.TextView; if (textView == null) return; SnapshotPoint caretPosition = textView.Caret.Position.BufferPosition; var roslynDocument = GetRoslynDocument(); var ast = await roslynDocument.GetSyntaxRootAsync().ConfigureAwait(false); var model = await roslynDocument.GetSemanticModelAsync().ConfigureAwait(false); var node = ast.FindNode(new TextSpan(caretPosition.Position, 0), false, true); if (node == null) { owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Can't show ILSpy for this code element!"); return; } var symbol = GetSymbolResolvableByILSpy(model, node); if (symbol == null) { owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Can't show ILSpy for this code element!"); return; } var roslynProject = roslynDocument.Project; var refsmap = GetReferences(roslynProject); var symbolAssemblyName = symbol.ContainingAssembly?.Identity?.Name; // Add our own project as well (not among references) var project = owner.DTE.Solution.Projects.OfType() .FirstOrDefault(p => p.FileName == roslynProject.FilePath); if (project == null) { owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Can't show ILSpy for this code element!"); return; } string assemblyName = roslynDocument.Project.AssemblyName; string projectOutputPath = Utils.GetProjectOutputAssembly(project, roslynProject); refsmap.Add(assemblyName, new DetectedReference(assemblyName, projectOutputPath, true)); // Divide into valid and invalid (= not found) referenced assemblies CheckAssemblies(refsmap, out var validRefs, out var invalidRefs); var invalidSymbolReference = invalidRefs.FirstOrDefault(r => r.IsProjectReference && (r.Name == symbolAssemblyName)); if (invalidSymbolReference != null) { if (string.IsNullOrEmpty(invalidSymbolReference.AssemblyFile)) { // No assembly file given at all. This has been seen while project is still loading after opening... owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Symbol can't be opened. This might happen while project is loading.", Environment.NewLine, invalidSymbolReference.AssemblyFile); } if (invalidSymbolReference.IsProjectReference) { // Some project references don't have assemblies, maybe not compiled yet? if (owner.ShowMessage( OLEMSGBUTTON.OLEMSGBUTTON_YESNO, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_WARNING, "The project output for '{0}' could not be found for analysis. Would you like to rebuild the solution?", symbolAssemblyName) == (int)MessageButtonResult.IDYES) { owner.DTE.ExecuteCommand("Build.BuildSolution"); } } else { // External assembly is missing, we should abort owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Referenced assembly{0}{0}'{1}'{0}{0} could not be found.", Environment.NewLine, invalidSymbolReference.AssemblyFile); } return; } OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "/navigateTo:" + (symbol.OriginalDefinition ?? symbol).GetDocumentationCommentId())); } void CheckAssemblies(Dictionary inputReferenceList, out List validRefs, out List invalidRefs) { validRefs = new List(); invalidRefs = new List(); foreach (var reference in inputReferenceList.Select(r => r.Value)) { if ((reference.AssemblyFile == null) || !File.Exists(reference.AssemblyFile)) { invalidRefs.Add(reference); } else { validRefs.Add(reference); } } } ISymbol GetSymbolResolvableByILSpy(SemanticModel model, SyntaxNode node) { var current = node; while (current != null) { var symbol = model.GetSymbolInfo(current).Symbol; if (symbol == null) { symbol = model.GetDeclaredSymbol(current); } // ILSpy can only resolve some symbol types, so allow them, discard everything else if (symbol != null) { switch (symbol.Kind) { case SymbolKind.ArrayType: case SymbolKind.Event: case SymbolKind.Field: case SymbolKind.Method: case SymbolKind.NamedType: case SymbolKind.Namespace: case SymbolKind.PointerType: case SymbolKind.Property: break; default: symbol = null; break; } } if (symbol != null) return symbol; current = current is IStructuredTriviaSyntax ? ((IStructuredTriviaSyntax)current).ParentTrivia.Token.Parent : current.Parent; } return null; } internal static void Register(ILSpyAddInPackage owner) { instance = new OpenCodeItemCommand(owner); } } }