diff --git a/ILSpy.AddIn/Commands/AssemblyReferenceForILSpy.cs b/ILSpy.AddIn/Commands/AssemblyReferenceForILSpy.cs index cde4f679a..e5aaa592a 100644 --- a/ILSpy.AddIn/Commands/AssemblyReferenceForILSpy.cs +++ b/ILSpy.AddIn/Commands/AssemblyReferenceForILSpy.cs @@ -34,10 +34,10 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// /// List of current project's references. /// Parameters object or null, if not applicable. - public ILSpyParameters GetILSpyParameters(Dictionary projectReferences) + public ILSpyParameters GetILSpyParameters(Dictionary projectReferences) { - if (projectReferences.TryGetValue(reference.Name, out var path)) - return new ILSpyParameters(new[] { path }); + if (projectReferences.TryGetValue(reference.Name, out var refentry)) + return new ILSpyParameters(new[] { refentry.AssemblyFile }); return null; } diff --git a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs index d8f14cc25..cd0b95f24 100644 --- a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs +++ b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs @@ -1,10 +1,12 @@ 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 @@ -70,33 +72,80 @@ namespace ICSharpCode.ILSpy.AddIn.Commands var model = await roslynDocument.GetSemanticModelAsync().ConfigureAwait(false); var node = ast.FindNode(new TextSpan(caretPosition.Position, 0), false, true); if (node == null) { - owner.ShowMessage("Can't show ILSpy for this code element!"); + owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Can't show ILSpy for this code element!"); return; } var symbol = GetSymbolResolvableByILSpy(model, node); if (symbol == null) { - owner.ShowMessage("Can't show ILSpy for this code element!"); + 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) { - string projectOutputPath = GetProjectOutputPath(project, roslynProject); - refsmap.Add(roslynDocument.Project.AssemblyName, projectOutputPath); + 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; } - var refs = refsmap.Select(fn => fn.Value).Where(f => File.Exists(f)); - OpenAssembliesInILSpy(new ILSpyParameters(refs, "/navigateTo:" + + 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; diff --git a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs index d3e1254f1..f7a431e08 100644 --- a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs +++ b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs @@ -23,6 +23,20 @@ namespace ICSharpCode.ILSpy.AddIn.Commands public string[] Arguments { get; private set; } } + public class DetectedReference + { + public DetectedReference(string name, string assemblyFile, bool isProjectReference) + { + this.Name = name; + this.AssemblyFile = assemblyFile; + this.IsProjectReference = isProjectReference; + } + + public string Name { get; private set; } + public string AssemblyFile { get; private set; } + public bool IsProjectReference { get; private set; } + } + abstract class ILSpyCommand { protected ILSpyAddInPackage owner; @@ -68,26 +82,18 @@ namespace ICSharpCode.ILSpy.AddIn.Commands System.Diagnostics.Process.Start(GetILSpyPath(), commandLineArguments); } - protected string GetProjectOutputPath(EnvDTE.Project project, Microsoft.CodeAnalysis.Project roslynProject) - { - string outputFileName = Path.GetFileName(roslynProject.OutputFilePath); - //get the directory path based on the project file. - string projectPath = Path.GetDirectoryName(project.FullName); - //get the output path based on the active configuration - string projectOutputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString(); - //combine the project path and output path to get the bin path - return Path.Combine(projectPath, projectOutputPath, outputFileName); - } - - protected Dictionary GetReferences(Microsoft.CodeAnalysis.Project parentProject) + protected Dictionary GetReferences(Microsoft.CodeAnalysis.Project parentProject) { - var dict = new Dictionary(); + var dict = new Dictionary(); foreach (var reference in parentProject.MetadataReferences) { using (var assemblyDef = AssemblyDefinition.ReadAssembly(reference.Display)) { + string assemblyName = assemblyDef.Name.Name; if (IsReferenceAssembly(assemblyDef)) { - dict.Add(assemblyDef.Name.Name, GacInterop.FindAssemblyInNetGac(assemblyDef.Name)); + dict.Add(assemblyName, + new DetectedReference(assemblyName, GacInterop.FindAssemblyInNetGac(assemblyDef.Name), false)); } else { - dict.Add(assemblyDef.Name.Name, reference.Display); + dict.Add(assemblyName, + new DetectedReference(assemblyName, reference.Display, false)); } } } @@ -95,7 +101,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands var roslynProject = owner.Workspace.CurrentSolution.GetProject(projectReference.ProjectId); var project = owner.DTE.Solution.Projects.OfType().FirstOrDefault(p => p.FileName == roslynProject.FilePath); if (roslynProject != null && project != null) - dict.Add(roslynProject.AssemblyName, GetProjectOutputPath(project, roslynProject)); + dict.Add(roslynProject.AssemblyName, + new DetectedReference(roslynProject.AssemblyName, Utils.GetProjectOutputAssembly(project, roslynProject), true)); } return dict; } diff --git a/ILSpy.AddIn/Commands/OpenReferenceCommand.cs b/ILSpy.AddIn/Commands/OpenReferenceCommand.cs index f3c2fcc00..7b5b346fb 100644 --- a/ILSpy.AddIn/Commands/OpenReferenceCommand.cs +++ b/ILSpy.AddIn/Commands/OpenReferenceCommand.cs @@ -73,7 +73,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands if (!string.IsNullOrEmpty(fileName)) { var roslynProject = owner.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == fileName); var references = GetReferences(roslynProject); - if (references.TryGetValue(projectItem.Name, out string path)) { + if (references.TryGetValue(projectItem.Name, out DetectedReference path)) { OpenAssembliesInILSpy(projectRefItem.GetILSpyParameters(references)); return; } diff --git a/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs b/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs index 1927e9852..8f48b2bd4 100644 --- a/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs +++ b/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs @@ -48,12 +48,12 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// /// List of current project's references. /// Parameters object or null, if not applicable. - public ILSpyParameters GetILSpyParameters(Dictionary projectReferences) + public ILSpyParameters GetILSpyParameters(Dictionary projectReferences) { string fileName = projectItem.ContainingProject?.FileName; if (!string.IsNullOrEmpty(fileName)) { - if (projectReferences.TryGetValue(projectItem.Name, out string path)) { - return new ILSpyParameters(new[] { path }); + if (projectReferences.TryGetValue(projectItem.Name, out DetectedReference path)) { + return new ILSpyParameters(new[] { path.AssemblyFile }); } } diff --git a/ILSpy.AddIn/ILSpyAddInPackage.cs b/ILSpy.AddIn/ILSpyAddInPackage.cs index 7e4ec8388..cd2b937e4 100644 --- a/ILSpy.AddIn/ILSpyAddInPackage.cs +++ b/ILSpy.AddIn/ILSpyAddInPackage.cs @@ -90,6 +90,16 @@ namespace ICSharpCode.ILSpy.AddIn #endregion public void ShowMessage(string format, params object[] items) + { + ShowMessage(OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_INFO, format, items); + } + + public void ShowMessage(OLEMSGICON icon, string format, params object[] items) + { + ShowMessage(OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, icon, format, items); + } + + public int ShowMessage(OLEMSGBUTTON buttons, OLEMSGDEFBUTTON defaultButton, OLEMSGICON icon, string format, params object[] items) { IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); Guid clsid = Guid.Empty; @@ -98,17 +108,19 @@ namespace ICSharpCode.ILSpy.AddIn uiShell.ShowMessageBox( 0, ref clsid, - "ILSpy.AddIn", + "ILSpy AddIn", string.Format(CultureInfo.CurrentCulture, format, items), string.Empty, 0, - OLEMSGBUTTON.OLEMSGBUTTON_OK, - OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, - OLEMSGICON.OLEMSGICON_INFO, + buttons, + defaultButton, + icon, 0, // false out result ) ); + + return result; } public IEnumerable GetSelectedItemsData() diff --git a/ILSpy.AddIn/Utils.cs b/ILSpy.AddIn/Utils.cs index 54bdd8d69..ae5ee4bca 100644 --- a/ILSpy.AddIn/Utils.cs +++ b/ILSpy.AddIn/Utils.cs @@ -14,8 +14,22 @@ using Microsoft.VisualStudio.TextManager.Interop; namespace ICSharpCode.ILSpy.AddIn { + public enum MessageButtonResult : int + { + IDOK = 1, + IDCANCEL = 2, + IDABORT = 3, + IDRETRY = 4, + IDIGNORE = 5, + IDYES = 6, + IDNO = 7, + IDTRYAGAIN = 10, + IDCONTINUE = 11, + } + static class Utils { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern unsafe char** CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); @@ -230,7 +244,10 @@ namespace ICSharpCode.ILSpy.AddIn string projectOutputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString(); // Combine the project path and output path to get the bin path - return Path.Combine(projectPath, projectOutputPath, outputFileName); + if ((projectPath != null) && (projectOutputPath != null) && (outputFileName != null)) + return Path.Combine(projectPath, projectOutputPath, outputFileName); + + return null; } } }