diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index e5ed81781..341984981 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -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 } } 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 } 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 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 } 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,19 +820,29 @@ 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); } return astEvent; } } + + public bool DecompileMethodBodies { get; set; } + + BlockStatement CreateMethodBody(MethodDefinition method, IEnumerable parameters = null) + { + if (DecompileMethodBodies) + return AstMethodBodyBuilder.CreateMethodBody(method, context, parameters); + else + return null; + } FieldDeclaration CreateField(FieldDefinition fieldDef) { diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 778cbf3c4..da2051d7b 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -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 { 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()) + attribute.Remove(); + + StringWriter w = new StringWriter(); + b.GenerateCode(new PlainTextOutput(w)); + return Regex.Replace(w.ToString(), @"\s+", " ").TrimEnd(); + } + + return base.GetTooltip(member); + } } } diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 09515d5b3..d2b59b2ef 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -132,10 +132,12 @@ - + + + README.txt @@ -266,5 +268,8 @@ ICSharpCode.TreeView + + + \ No newline at end of file diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index fcde9c686..8e89fc6d0 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -105,7 +105,19 @@ namespace ICSharpCode.ILSpy else return type.Name; } - + + /// + /// Converts a member signature to a string. + /// This is used for displaying the tooltip on a member reference. + /// + 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) diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index a4fdd0791..dc08b44ed 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -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; diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 6315e8891..6f0b328da 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -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 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}): {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 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 ret.ToString(); + return renderer.CreateTextBlock(); } - } catch (XmlException) { - return null; // invalid XML docu } - } - - string FindDocumentation(string fileName) - { - string path = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); - List names = new List(); - 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 names) - { - while (info != null) { - names.Add(info.Name); - info = info.Parent; - if (info == info.Parent) - return; - } - } #endregion #region RunWithCancellation @@ -269,7 +225,7 @@ namespace ICSharpCode.ILSpy.TextView /// void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null, DecompilerTextViewState state = null) { - Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength); + Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength); Stopwatch w = Stopwatch.StartNew(); textEditor.ScrollToHome(); diff --git a/ILSpy/XmlDocKeyProvider.cs b/ILSpy/XmlDoc/XmlDocKeyProvider.cs similarity index 96% rename from ILSpy/XmlDocKeyProvider.cs rename to ILSpy/XmlDoc/XmlDocKeyProvider.cs index b8a449b1d..0949c662a 100644 --- a/ILSpy/XmlDocKeyProvider.cs +++ b/ILSpy/XmlDoc/XmlDocKeyProvider.cs @@ -8,12 +8,12 @@ using System.Linq; using System.Text; using Mono.Cecil; -namespace ICSharpCode.ILSpy +namespace ICSharpCode.ILSpy.XmlDoc { /// /// Provides XML documentation tags. /// - sealed class XmlDocKeyProvider + public sealed class XmlDocKeyProvider { #region GetKey public static string GetKey(MemberReference member) @@ -33,7 +33,10 @@ namespace ICSharpCode.ILSpy b.Append("M:"); AppendTypeName(b, member.DeclaringType); b.Append('.'); - b.Append(member.Name); + if (member.Name == ".ctor") + b.Append("#ctor"); + else + b.Append(member.Name); IList parameters; if (member is PropertyDefinition) { parameters = ((PropertyDefinition)member).Parameters; diff --git a/ILSpy/XmlDoc/XmlDocLoader.cs b/ILSpy/XmlDoc/XmlDocLoader.cs new file mode 100644 index 000000000..0936327f3 --- /dev/null +++ b/ILSpy/XmlDoc/XmlDocLoader.cs @@ -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 +{ + /// + /// Helps finding and loading .xml documentation. + /// + public static class XmlDocLoader + { + static readonly Lazy mscorlibDocumentation = new Lazy(LoadMscorlibDocumentation); + static readonly ConditionalWeakTable cache = new ConditionalWeakTable(); + + 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; + } + } +} diff --git a/ILSpy/XmlDoc/XmlDocRenderer.cs b/ILSpy/XmlDoc/XmlDocRenderer.cs new file mode 100644 index 000000000..329c49107 --- /dev/null +++ b/ILSpy/XmlDoc/XmlDocRenderer.cs @@ -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 +{ + /// + /// Renders XML documentation into a WPF . + /// + 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("" + xmlDocumentation + "")); + 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() }; + } + } +}