Browse Source

Display tooltips when hovering over member references.

pull/129/head
Daniel Grunwald 14 years ago
parent
commit
1255f87696
  1. 23
      ICSharpCode.Decompiler/Ast/AstBuilder.cs
  2. 31
      ILSpy/CSharpLanguage.cs
  3. 7
      ILSpy/ILSpy.csproj
  4. 12
      ILSpy/Language.cs
  5. 2
      ILSpy/MainWindow.xaml.cs
  6. 106
      ILSpy/TextView/DecompilerTextView.cs
  7. 7
      ILSpy/XmlDoc/XmlDocKeyProvider.cs
  8. 117
      ILSpy/XmlDoc/XmlDocLoader.cs
  9. 122
      ILSpy/XmlDoc/XmlDocRenderer.cs

23
ICSharpCode.Decompiler/Ast/AstBuilder.cs

@ -41,6 +41,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -41,6 +41,7 @@ namespace ICSharpCode.Decompiler.Ast
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
this.DecompileMethodBodies = true;
}
public static bool MemberIsHidden(MemberReference member, DecompilerSettings settings)
@ -629,7 +630,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -629,7 +630,7 @@ namespace ICSharpCode.Decompiler.Ast
}
} else
astMethod.PrivateImplementationType = ConvertType(methodDef.Overrides.First().DeclaringType);
astMethod.Body = AstMethodBodyBuilder.CreateMethodBody(methodDef, context, astMethod.Parameters);
astMethod.Body = CreateMethodBody(methodDef, astMethod.Parameters);
}
ConvertAttributes(astMethod, methodDef);
if (methodDef.HasCustomAttributes && astMethod.Parameters.Count > 0) {
@ -707,7 +708,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -707,7 +708,7 @@ namespace ICSharpCode.Decompiler.Ast
}
astMethod.Name = CleanName(methodDef.DeclaringType.Name);
astMethod.Parameters.AddRange(MakeParameters(methodDef));
astMethod.Body = AstMethodBodyBuilder.CreateMethodBody(methodDef, context, astMethod.Parameters);
astMethod.Body = CreateMethodBody(methodDef, astMethod.Parameters);
ConvertAttributes(astMethod, methodDef);
return astMethod;
}
@ -755,7 +756,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -755,7 +756,7 @@ namespace ICSharpCode.Decompiler.Ast
astProp.ReturnType = ConvertType(propDef.PropertyType, propDef);
if (propDef.GetMethod != null) {
astProp.Getter = new Accessor();
astProp.Getter.Body = AstMethodBodyBuilder.CreateMethodBody(propDef.GetMethod, context);
astProp.Getter.Body = CreateMethodBody(propDef.GetMethod);
astProp.AddAnnotation(propDef.GetMethod);
ConvertAttributes(astProp.Getter, propDef.GetMethod);
@ -764,7 +765,7 @@ namespace ICSharpCode.Decompiler.Ast @@ -764,7 +765,7 @@ namespace ICSharpCode.Decompiler.Ast
}
if (propDef.SetMethod != null) {
astProp.Setter = new Accessor();
astProp.Setter.Body = AstMethodBodyBuilder.CreateMethodBody(propDef.SetMethod, context);
astProp.Setter.Body = CreateMethodBody(propDef.SetMethod);
astProp.Setter.AddAnnotation(propDef.SetMethod);
ConvertAttributes(astProp.Setter, propDef.SetMethod);
ConvertCustomAttributes(astProp.Setter, propDef.SetMethod.Parameters.Last(), AttributeTarget.Param);
@ -819,13 +820,13 @@ namespace ICSharpCode.Decompiler.Ast @@ -819,13 +820,13 @@ namespace ICSharpCode.Decompiler.Ast
astEvent.PrivateImplementationType = ConvertType(eventDef.AddMethod.Overrides.First().DeclaringType);
if (eventDef.AddMethod != null) {
astEvent.AddAccessor = new Accessor {
Body = AstMethodBodyBuilder.CreateMethodBody(eventDef.AddMethod, context)
Body = CreateMethodBody(eventDef.AddMethod)
}.WithAnnotation(eventDef.AddMethod);
ConvertAttributes(astEvent.AddAccessor, eventDef.AddMethod);
}
if (eventDef.RemoveMethod != null) {
astEvent.RemoveAccessor = new Accessor {
Body = AstMethodBodyBuilder.CreateMethodBody(eventDef.RemoveMethod, context)
Body = CreateMethodBody(eventDef.RemoveMethod)
}.WithAnnotation(eventDef.RemoveMethod);
ConvertAttributes(astEvent.RemoveAccessor, eventDef.RemoveMethod);
}
@ -833,6 +834,16 @@ namespace ICSharpCode.Decompiler.Ast @@ -833,6 +834,16 @@ namespace ICSharpCode.Decompiler.Ast
}
}
public bool DecompileMethodBodies { get; set; }
BlockStatement CreateMethodBody(MethodDefinition method, IEnumerable<ParameterDeclaration> parameters = null)
{
if (DecompileMethodBodies)
return AstMethodBodyBuilder.CreateMethodBody(method, context, parameters);
else
return null;
}
FieldDeclaration CreateField(FieldDefinition fieldDef)
{
FieldDeclaration astField = new FieldDeclaration();

31
ILSpy/CSharpLanguage.cs

@ -23,6 +23,8 @@ using System.ComponentModel.Composition; @@ -23,6 +23,8 @@ using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xaml;
using System.Xml;
@ -462,5 +464,34 @@ namespace ICSharpCode.ILSpy @@ -462,5 +464,34 @@ namespace ICSharpCode.ILSpy
{
return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings);
}
public override string GetTooltip(MemberReference member)
{
MethodDefinition md = member as MethodDefinition;
PropertyDefinition pd = member as PropertyDefinition;
EventDefinition ed = member as EventDefinition;
FieldDefinition fd = member as FieldDefinition;
if (md != null || pd != null || ed != null || fd != null) {
AstBuilder b = new AstBuilder(new DecompilerContext(member.Module) { Settings = new DecompilerSettings { UsingDeclarations = false } });
b.DecompileMethodBodies = false;
if (md != null)
b.AddMethod(md);
else if (pd != null)
b.AddProperty(pd);
else if (ed != null)
b.AddEvent(ed);
else
b.AddField(fd);
b.RunTransformations();
foreach (var attribute in b.CompilationUnit.Descendants.OfType<AttributeSection>())
attribute.Remove();
StringWriter w = new StringWriter();
b.GenerateCode(new PlainTextOutput(w));
return Regex.Replace(w.ToString(), @"\s+", " ").TrimEnd();
}
return base.GetTooltip(member);
}
}
}

7
ILSpy/ILSpy.csproj

@ -132,10 +132,12 @@ @@ -132,10 +132,12 @@
<Compile Include="TreeNodes\Analyzer\AnalyzeContextMenuEntry.cs" />
<Compile Include="TreeNodes\IMemberTreeNode.cs" />
<Compile Include="TreeNodes\XamlResourceNode.cs" />
<Compile Include="XmlDocKeyProvider.cs" />
<Compile Include="TreeNodes\Analyzer\AnalyzedPropertyAccessorsTreeNode.cs" />
<Compile Include="TreeNodes\Analyzer\AnalyzedPropertyOverridesTreeNode.cs" />
<Compile Include="TreeNodes\Analyzer\AnalyzedPropertyTreeNode.cs" />
<Compile Include="XmlDoc\XmlDocKeyProvider.cs" />
<Compile Include="XmlDoc\XmlDocLoader.cs" />
<Compile Include="XmlDoc\XmlDocRenderer.cs" />
<EmbeddedResource Include="..\README.txt">
<Link>README.txt</Link>
</EmbeddedResource>
@ -266,5 +268,8 @@ @@ -266,5 +268,8 @@
<Name>ICSharpCode.TreeView</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="XmlDoc" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
</Project>

12
ILSpy/Language.cs

@ -106,6 +106,18 @@ namespace ICSharpCode.ILSpy @@ -106,6 +106,18 @@ namespace ICSharpCode.ILSpy
return type.Name;
}
/// <summary>
/// Converts a member signature to a string.
/// This is used for displaying the tooltip on a member reference.
/// </summary>
public virtual string GetTooltip(MemberReference member)
{
if (member is TypeReference)
return TypeToString((TypeReference)member, true);
else
return member.ToString();
}
public virtual string FormatPropertyName(PropertyDefinition property, bool? isIndexer = null)
{
if (property == null)

2
ILSpy/MainWindow.xaml.cs

@ -30,9 +30,11 @@ using System.Windows.Controls; @@ -30,9 +30,11 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.TreeNodes.Analyzer;
using ICSharpCode.ILSpy.XmlDoc;
using ICSharpCode.TreeView;
using Microsoft.Win32;
using Mono.Cecil;

106
ILSpy/TextView/DecompilerTextView.cs

@ -40,7 +40,9 @@ using ICSharpCode.AvalonEdit.Highlighting; @@ -40,7 +40,9 @@ using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.XmlDoc;
using ICSharpCode.NRefactory.Documentation;
using ICSharpCode.NRefactory.TypeSystem;
using Microsoft.Win32;
using Mono.Cecil;
@ -115,85 +117,39 @@ namespace ICSharpCode.ILSpy.TextView @@ -115,85 +117,39 @@ namespace ICSharpCode.ILSpy.TextView
Mono.Cecil.Cil.OpCode code = (Mono.Cecil.Cil.OpCode)segment.Reference;
string encodedName = code.Code.ToString();
string opCodeHex = code.Size > 1 ? string.Format("0x{0:x2}{1:x2}", code.Op1, code.Op2) : string.Format("0x{0:x2}", code.Op2);
string documentationFile = FindDocumentation("mscorlib.xml");
string text = "";
if (documentationFile != null){
XmlDocumentationProvider provider = new XmlDocumentationProvider(documentationFile);
string documentation = provider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + encodedName);
if (documentation != null)
text = StripXml(documentation);
XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
if (docProvider != null){
string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + encodedName);
if (documentation != null) {
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText(string.Format("{0} ({1}) - ", code.Name, opCodeHex));
renderer.AddXmlDocumentation(documentation);
return renderer.CreateTextBlock();
}
}
return string.Format("{0} ({1})", code.Name, opCodeHex);
} else if (segment.Reference is MemberReference) {
MemberReference mr = (MemberReference)segment.Reference;
// if possible, resolve the reference
if (mr is TypeReference) {
mr = ((TypeReference)mr).Resolve() ?? mr;
} else if (mr is MethodReference) {
mr = ((MethodReference)mr).Resolve() ?? mr;
}
XmlDocumentationProvider docProvider = XmlDocLoader.LoadDocumentation(mr.Module);
if (docProvider != null) {
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(mr));
string documentation = docProvider.GetDocumentation(XmlDocKeyProvider.GetKey(mr));
if (documentation != null) {
renderer.AppendText(Environment.NewLine);
renderer.AddXmlDocumentation(documentation);
}
return renderer.CreateTextBlock();
}
return string.Format("{0} ({1}): {2}", code.Name, opCodeHex, text);
}
return null;
}
string StripXml(string xmlText)
{
try {
using (XmlTextReader xml = new XmlTextReader(new StringReader(xmlText))) {
StringBuilder ret = new StringBuilder();
while (xml.Read()) {
if (xml.NodeType == XmlNodeType.Element) {
string elname = xml.Name.ToLowerInvariant();
switch (elname) {
case "summary":
break;
case "br":
case "para":
ret.AppendLine();
break;
default:
xml.Skip();
break;
}
} else if (xml.NodeType == XmlNodeType.Text) {
ret.Append(Regex.Replace(xml.Value, @"\s+", " "));
}
}
return ret.ToString();
}
} catch (XmlException) {
return null; // invalid XML docu
}
}
string FindDocumentation(string fileName)
{
string path = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
List<string> names = new List<string>();
EnumerateCultures(CultureInfo.CurrentCulture, names);
names.Add("en");
names.Add("en-US");
names.Add("en-GB");
foreach (string name in names) {
string location = Path.Combine(path, name, fileName);
if (File.Exists(location))
return location;
}
path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0");
string loc = Path.Combine(path, fileName);
if (File.Exists(loc)) {
return loc;
}
return null;
}
void EnumerateCultures(CultureInfo info, List<string> names)
{
while (info != null) {
names.Add(info.Name);
info = info.Parent;
if (info == info.Parent)
return;
}
}
#endregion
#region RunWithCancellation

7
ILSpy/XmlDocKeyProvider.cs → ILSpy/XmlDoc/XmlDocKeyProvider.cs

@ -8,12 +8,12 @@ using System.Linq; @@ -8,12 +8,12 @@ using System.Linq;
using System.Text;
using Mono.Cecil;
namespace ICSharpCode.ILSpy
namespace ICSharpCode.ILSpy.XmlDoc
{
/// <summary>
/// Provides XML documentation tags.
/// </summary>
sealed class XmlDocKeyProvider
public sealed class XmlDocKeyProvider
{
#region GetKey
public static string GetKey(MemberReference member)
@ -33,6 +33,9 @@ namespace ICSharpCode.ILSpy @@ -33,6 +33,9 @@ namespace ICSharpCode.ILSpy
b.Append("M:");
AppendTypeName(b, member.DeclaringType);
b.Append('.');
if (member.Name == ".ctor")
b.Append("#ctor");
else
b.Append(member.Name);
IList<ParameterDefinition> parameters;
if (member is PropertyDefinition) {

117
ILSpy/XmlDoc/XmlDocLoader.cs

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using ICSharpCode.NRefactory.Documentation;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.XmlDoc
{
/// <summary>
/// Helps finding and loading .xml documentation.
/// </summary>
public static class XmlDocLoader
{
static readonly Lazy<XmlDocumentationProvider> mscorlibDocumentation = new Lazy<XmlDocumentationProvider>(LoadMscorlibDocumentation);
static readonly ConditionalWeakTable<ModuleDefinition, XmlDocumentationProvider> cache = new ConditionalWeakTable<ModuleDefinition, XmlDocumentationProvider>();
static XmlDocumentationProvider LoadMscorlibDocumentation()
{
string xmlDocFile = FindXmlDocumentation("mscorlib.dll", TargetRuntime.Net_4_0)
?? FindXmlDocumentation("mscorlib.dll", TargetRuntime.Net_2_0);
if (xmlDocFile != null)
return new XmlDocumentationProvider(xmlDocFile);
else
return null;
}
public static XmlDocumentationProvider MscorlibDocumentation {
get { return mscorlibDocumentation.Value; }
}
public static XmlDocumentationProvider LoadDocumentation(ModuleDefinition module)
{
if (module == null)
throw new ArgumentNullException("module");
lock (cache) {
XmlDocumentationProvider xmlDoc;
if (!cache.TryGetValue(module, out xmlDoc)) {
string xmlDocFile = LookupLocalizedXmlDoc(module.FullyQualifiedName);
if (xmlDocFile == null) {
xmlDocFile = FindXmlDocumentation(Path.GetFileName(module.FullyQualifiedName), module.Runtime);
}
if (xmlDocFile != null) {
xmlDoc = new XmlDocumentationProvider(xmlDocFile);
cache.Add(module, xmlDoc);
} else {
xmlDoc = null;
}
}
return xmlDoc;
}
}
static readonly string referenceAssembliesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\\Framework");
static readonly string frameworkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"Microsoft.NET\Framework");
static string FindXmlDocumentation(string assemblyFileName, TargetRuntime runtime)
{
string fileName;
switch (runtime) {
case TargetRuntime.Net_1_0:
fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v1.0.3705", assemblyFileName));
break;
case TargetRuntime.Net_1_1:
fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v1.1.4322", assemblyFileName));
break;
case TargetRuntime.Net_2_0:
fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v2.0.50727", assemblyFileName))
?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, "v3.5"))
?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, "v3.0"))
?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, @".NETFramework\v3.5\Profile\Client"));
break;
case TargetRuntime.Net_4_0:
default:
fileName = LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, @".NETFramework\v4.0", assemblyFileName))
?? LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v4.0.30319", assemblyFileName));
break;
}
return fileName;
}
static string LookupLocalizedXmlDoc(string fileName)
{
string xmlFileName = Path.ChangeExtension(fileName, ".xml");
string currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
string localizedXmlDocFile = GetLocalizedName(xmlFileName, currentCulture);
Debug.WriteLine("Try find XMLDoc @" + localizedXmlDocFile);
if (File.Exists(localizedXmlDocFile)) {
return localizedXmlDocFile;
}
Debug.WriteLine("Try find XMLDoc @" + xmlFileName);
if (File.Exists(xmlFileName)) {
return xmlFileName;
}
if (currentCulture != "en") {
string englishXmlDocFile = GetLocalizedName(xmlFileName, "en");
Debug.WriteLine("Try find XMLDoc @" + englishXmlDocFile);
if (File.Exists(englishXmlDocFile)) {
return englishXmlDocFile;
}
}
return null;
}
static string GetLocalizedName(string fileName, string language)
{
string localizedXmlDocFile = Path.GetDirectoryName(fileName);
localizedXmlDocFile = Path.Combine(localizedXmlDocFile, language);
localizedXmlDocFile = Path.Combine(localizedXmlDocFile, Path.GetFileName(fileName));
return localizedXmlDocFile;
}
}
}

122
ILSpy/XmlDoc/XmlDocRenderer.cs

@ -0,0 +1,122 @@ @@ -0,0 +1,122 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Xml;
namespace ICSharpCode.ILSpy.XmlDoc
{
/// <summary>
/// Renders XML documentation into a WPF <see cref="TextBlock"/>.
/// </summary>
public class XmlDocRenderer
{
StringBuilder ret = new StringBuilder();
public void AppendText(string text)
{
ret.Append(text);
}
public void AddXmlDocumentation(string xmlDocumentation)
{
if (xmlDocumentation == null)
return;
Debug.WriteLine(xmlDocumentation);
try {
XmlTextReader r = new XmlTextReader(new StringReader("<docroot>" + xmlDocumentation + "</docroot>"));
r.XmlResolver = null;
AddXmlDocumentation(r);
} catch (XmlException) {
}
}
static readonly Regex whitespace = new Regex(@"\s+");
public void AddXmlDocumentation(XmlReader xml)
{
while (xml.Read()) {
if (xml.NodeType == XmlNodeType.Element) {
string elname = xml.Name.ToLowerInvariant();
switch (elname) {
case "filterpriority":
case "remarks":
xml.Skip();
break;
case "example":
ret.Append(Environment.NewLine);
ret.Append("Example:");
ret.Append(Environment.NewLine);
break;
case "exception":
ret.Append(Environment.NewLine);
ret.Append(GetCref(xml["cref"]));
ret.Append(": ");
break;
case "returns":
ret.Append(Environment.NewLine);
ret.Append("Returns: ");
break;
case "see":
ret.Append(GetCref(xml["cref"]));
ret.Append(xml["langword"]);
break;
case "seealso":
ret.Append(Environment.NewLine);
ret.Append("See also: ");
ret.Append(GetCref(xml["cref"]));
break;
case "paramref":
ret.Append(xml["name"]);
break;
case "param":
ret.Append(Environment.NewLine);
ret.Append(whitespace.Replace(xml["name"].Trim()," "));
ret.Append(": ");
break;
case "typeparam":
ret.Append(Environment.NewLine);
ret.Append(whitespace.Replace(xml["name"].Trim()," "));
ret.Append(": ");
break;
case "value":
ret.Append(Environment.NewLine);
ret.Append("Value: ");
ret.Append(Environment.NewLine);
break;
case "br":
case "para":
ret.Append(Environment.NewLine);
break;
}
} else if (xml.NodeType == XmlNodeType.Text) {
ret.Append(whitespace.Replace(xml.Value, " "));
}
}
}
static string GetCref(string cref)
{
if (cref == null || cref.Trim().Length==0) {
return "";
}
if (cref.Length < 2) {
return cref;
}
if (cref.Substring(1, 1) == ":") {
return cref.Substring(2, cref.Length - 2);
}
return cref;
}
public TextBlock CreateTextBlock()
{
return new TextBlock { Text = ret.ToString() };
}
}
}
Loading…
Cancel
Save