Browse Source

Overhauled "Open code in ILSpy" features to support more cases.

Rearranged some of the helper methods for opening things in ILSpy.
Added methods to open Project and CodeItem in ILSpy.
Had hoped the latter would help with opening from Call Stack, but can't see how procedurally access the call stack window.
Asked how to do so here: http://stackoverflow.com/questions/36726595/how-to-get-a-programmable-interface-to-the-visual-studio-call-stack-window

"Open code in ILSpy" now works for:
- classes, interfaces and structs (generic or not, nested or not)
- enums
- fields and properties
- constructors (generic or not)
- overloaded constructors, unless they are generic or use type parameters of their generic class
- methods (generic or not)
- overloaded methods, unless they are generic or use type parameters of their generic class

TODO: disambiguate overloaded generic constructors and methods
pull/697/head
yggy 9 years ago
parent
commit
29491ff951
  1. 196
      ILSpy.AddIn/ILSpyAddInPackage.cs

196
ILSpy.AddIn/ILSpyAddInPackage.cs

@ -119,11 +119,7 @@ namespace ICSharpCode.ILSpy.AddIn @@ -119,11 +119,7 @@ namespace ICSharpCode.ILSpy.AddIn
foreach (EnvDTE.UIHierarchyItem item in items) {
EnvDTE.Project project = (EnvDTE.Project)item.Object;
EnvDTE.Configuration config = project.ConfigurationManager.ActiveConfiguration;
string projectPath = Path.GetDirectoryName(project.FileName);
string outputPath = config.Properties.Item("OutputPath").Value.ToString();
string assemblyFileName = project.Properties.Item("OutputFileName").Value.ToString();
OpenAssemblyInILSpy(Path.Combine(projectPath, outputPath, assemblyFileName));
OpenProjectInILSpy(project);
}
}
@ -131,68 +127,147 @@ namespace ICSharpCode.ILSpy.AddIn @@ -131,68 +127,147 @@ namespace ICSharpCode.ILSpy.AddIn
{
var document = (EnvDTE.Document)(((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ActiveDocument);
var selection = (EnvDTE.TextPoint)((EnvDTE.TextSelection)document.Selection).ActivePoint;
var projectItem = document.ProjectItem;
string navigateTo = null;
// Find the full name of the method or class enclosing the current selection.
// Note that order of testing is important, need to get the narrowest, most
// internal element first.
//
// Add a prefix to match the ILSpy command line (see doc\Command Line.txt).
// The prefix characters are documented in Appendix A of the C# specification:
// E Event
// F Field
// M Method (including constructors, destructors, and operators)
// N Namespace
// P Property (including indexers)
// T Type (such as class, delegate, enum, interface, and struct)
navigateTo = GetCodeElementFullName("/navigateTo:M:", projectItem, selection, EnvDTE.vsCMElement.vsCMElementFunction);
if (navigateTo == null) {
navigateTo = GetCodeElementFullName("/navigateTo:E:", projectItem, selection, EnvDTE.vsCMElement.vsCMElementEvent);
// Search code elements in desired order, working from innermost to outermost.
// Should eventually find something, and if not we'll just open the assembly itself.
var codeElement = GetSelectedCodeElement(selection,
EnvDTE.vsCMElement.vsCMElementFunction,
EnvDTE.vsCMElement.vsCMElementEvent,
EnvDTE.vsCMElement.vsCMElementVariable, // There is no vsCMElementField, fields are just variables outside of function scope.
EnvDTE.vsCMElement.vsCMElementProperty,
EnvDTE.vsCMElement.vsCMElementDelegate,
EnvDTE.vsCMElement.vsCMElementEnum,
EnvDTE.vsCMElement.vsCMElementInterface,
EnvDTE.vsCMElement.vsCMElementStruct,
EnvDTE.vsCMElement.vsCMElementClass,
EnvDTE.vsCMElement.vsCMElementNamespace);
if (codeElement != null) {
OpenCodeItemInILSpy(codeElement);
}
if (navigateTo == null) {
navigateTo = GetCodeElementFullName("/navigateTo:P:", projectItem, selection, EnvDTE.vsCMElement.vsCMElementProperty);
else {
OpenProjectInILSpy(document.ProjectItem.ContainingProject);
}
if (navigateTo == null) {
navigateTo = GetCodeElementFullName("/navigateTo:T:", projectItem, selection,
EnvDTE.vsCMElement.vsCMElementDelegate,
EnvDTE.vsCMElement.vsCMElementEnum,
EnvDTE.vsCMElement.vsCMElementInterface,
EnvDTE.vsCMElement.vsCMElementStruct,
EnvDTE.vsCMElement.vsCMElementClass);
}
private EnvDTE.CodeElement GetSelectedCodeElement(EnvDTE.TextPoint selection, params EnvDTE.vsCMElement[] elementTypes)
{
foreach (var elementType in elementTypes) {
var codeElement = selection.CodeElement[elementType];
if (codeElement != null) {
return codeElement;
}
}
EnvDTE.Project project = projectItem.ContainingProject;
EnvDTE.Configuration config = project.ConfigurationManager.ActiveConfiguration;
string projectPath = Path.GetDirectoryName(project.FileName);
string outputPath = config.Properties.Item("OutputPath").Value.ToString();
string assemblyFileName = project.Properties.Item("OutputFileName").Value.ToString();
return null;
}
// Note that if navigateTo is still null this will just open ILSpy on the assembly.
OpenAssemblyInILSpy(Path.Combine(projectPath, outputPath, assemblyFileName), navigateTo);
private void OpenCodeItemInILSpy(EnvDTE.CodeElement codeElement)
{
string navigateTo = "/navigateTo:" + GetCodeElementIDString(codeElement);
OpenProjectInILSpy(codeElement.ProjectItem.ContainingProject, navigateTo);
}
private string GetCodeElementFullName(string prefix, EnvDTE.ProjectItem file, EnvDTE.TextPoint selection, params EnvDTE.vsCMElement[] elementTypes)
// Get ID string for code element, for /navigateTo command line option.
// See "ID string format" in Appendix A of the C# language specification for details.
// See ICSharpCode.ILSpy.XmlDoc.XmlDocKeyProvider.GetKey for a similar implementation, based on Mono.Cecil.MemberReference.
private string GetCodeElementIDString(EnvDTE.CodeElement codeElement)
{
foreach (var elementType in elementTypes)
{
try
{
var codeElement = file.FileCodeModel.CodeElementFromPoint(selection, elementType);
if (elementType == EnvDTE.vsCMElement.vsCMElementFunction)
{
// TODO: use codeElement.Parameters to disambiguate overloaded methods
switch (codeElement.Kind) {
case EnvDTE.vsCMElement.vsCMElementEvent:
return string.Concat("E:",
GetCodeElementContainerString((EnvDTE.CodeElement)codeElement.Collection.Parent),
".", codeElement.Name);
case EnvDTE.vsCMElement.vsCMElementVariable:
return string.Concat("F:",
GetCodeElementContainerString((EnvDTE.CodeElement)codeElement.Collection.Parent),
".", codeElement.Name);
case EnvDTE.vsCMElement.vsCMElementFunction: {
var codeFunction = (EnvDTE.CodeFunction)codeElement;
var idBuilder = new System.Text.StringBuilder();
idBuilder.Append("M:");
// Constructors need to be called "#ctor" for navigation purposes.
string classFullName = GetCodeElementContainerString((EnvDTE.CodeElement)codeFunction.Parent);
string functionName = (codeFunction.FunctionKind == EnvDTE.vsCMFunction.vsCMFunctionConstructor ? "#ctor" : codeFunction.Name);
idBuilder.Append(classFullName);
idBuilder.Append('.');
idBuilder.Append(functionName);
// Append parameter types, to disambiguate overloaded methods.
// TODO: handle generic type parameters correctly, both from class and method
if (codeFunction.Parameters.Count > 0) {
idBuilder.Append("(");
bool first = true;
foreach (EnvDTE.CodeParameter parameter in codeFunction.Parameters) {
if (!first) {
idBuilder.Append(",");
}
first = false;
idBuilder.Append(parameter.Type.AsFullName);
}
idBuilder.Append(")");
}
return idBuilder.ToString();
}
return prefix + codeElement.FullName;
}
catch (COMException)
{
//Don’t do anything – this is expected if there is no such code element at specified point.
}
case EnvDTE.vsCMElement.vsCMElementNamespace:
return string.Concat("N:",
codeElement.FullName);
case EnvDTE.vsCMElement.vsCMElementProperty:
return string.Concat("P:",
GetCodeElementContainerString((EnvDTE.CodeElement)codeElement.Collection.Parent),
".", codeElement.Name);
case EnvDTE.vsCMElement.vsCMElementDelegate:
case EnvDTE.vsCMElement.vsCMElementEnum:
case EnvDTE.vsCMElement.vsCMElementInterface:
case EnvDTE.vsCMElement.vsCMElementStruct:
case EnvDTE.vsCMElement.vsCMElementClass:
return string.Concat("T:",
GetCodeElementContainerString(codeElement));
default:
return string.Format("!:Code element {0} is of unsupported type {1}", codeElement.FullName, codeElement.Kind.ToString());
}
}
return null;
private string GetCodeElementContainerString(EnvDTE.CodeElement containerElement)
{
switch (containerElement.Kind) {
case EnvDTE.vsCMElement.vsCMElementNamespace:
return containerElement.FullName;
case EnvDTE.vsCMElement.vsCMElementInterface:
case EnvDTE.vsCMElement.vsCMElementStruct:
case EnvDTE.vsCMElement.vsCMElementClass: {
var idBuilder = new System.Text.StringBuilder();
idBuilder.Append(GetCodeElementContainerString((EnvDTE.CodeElement)containerElement.Collection.Parent));
idBuilder.Append('.');
idBuilder.Append(containerElement.Name);
// For "Generic<T1,T2>" we need "Generic`2".
bool isGeneric =
((containerElement.Kind == EnvDTE.vsCMElement.vsCMElementClass) && ((EnvDTE80.CodeClass2)containerElement).IsGeneric) ||
((containerElement.Kind == EnvDTE.vsCMElement.vsCMElementStruct) && ((EnvDTE80.CodeStruct2)containerElement).IsGeneric) ||
((containerElement.Kind == EnvDTE.vsCMElement.vsCMElementInterface) && ((EnvDTE80.CodeInterface2)containerElement).IsGeneric);
int iGenericParams = containerElement.FullName.LastIndexOf('<');
if (isGeneric && (iGenericParams >= 0)) {
int genericParamsCount = containerElement.FullName.Substring(iGenericParams).Split(',').Length;
idBuilder.Append('`');
idBuilder.Append(genericParamsCount);
}
return idBuilder.ToString();
}
default:
return string.Format("!:Code element {0} is of unsupported container type {1}", containerElement.FullName, containerElement.Kind.ToString());
}
}
private void OpenILSpyCallback(object sender, EventArgs e)
@ -206,6 +281,15 @@ namespace ICSharpCode.ILSpy.AddIn @@ -206,6 +281,15 @@ namespace ICSharpCode.ILSpy.AddIn
return Path.Combine(basePath, "ILSpy.exe");
}
private void OpenProjectInILSpy(EnvDTE.Project project, params string[] arguments)
{
EnvDTE.Configuration config = project.ConfigurationManager.ActiveConfiguration;
string projectPath = Path.GetDirectoryName(project.FileName);
string outputPath = config.Properties.Item("OutputPath").Value.ToString();
string assemblyFileName = project.Properties.Item("OutputFileName").Value.ToString();
OpenAssemblyInILSpy(Path.Combine(projectPath, outputPath, assemblyFileName), arguments);
}
private void OpenAssemblyInILSpy(string assemblyFileName, params string[] arguments)
{
if (!File.Exists(assemblyFileName)) {

Loading…
Cancel
Save