Browse Source

Merge pull request #1654 from icsharpcode/fancy-tooltips

Rich Text Tooltips
pull/1790/head
Siegfried Pammer 7 years ago committed by GitHub
parent
commit
62ce7a7e46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  2. 6
      ICSharpCode.Decompiler/CSharp/OutputVisitor/ITokenWriter.cs
  3. 72
      ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterTokenWriter.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  5. 240
      ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs
  6. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  7. 9
      ICSharpCode.Decompiler/NRExtensions.cs
  8. 40
      ILSpy/ExtensionMethods.cs
  9. 3
      ILSpy/ILSpy.csproj
  10. 66
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs
  11. 14
      ILSpy/Languages/CSharpLanguage.cs
  12. 10
      ILSpy/Languages/Language.cs
  13. 4
      ILSpy/MainWindow.xaml.cs
  14. 241
      ILSpy/TextView/DecompilerTextView.cs
  15. 2
      ILSpy/TextView/DecompilerTextView.xaml
  16. 551
      ILSpy/TextView/DocumentationUIBuilder.cs
  17. 137
      ILSpy/TextView/XmlDocRenderer.cs

4
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -56,6 +56,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
TypeSystemAstBuilder astBuilder = CreateAstBuilder(); TypeSystemAstBuilder astBuilder = CreateAstBuilder();
AstNode node = astBuilder.ConvertSymbol(symbol); AstNode node = astBuilder.ConvertSymbol(symbol);
writer.StartNode(node);
EntityDeclaration entityDecl = node as EntityDeclaration; EntityDeclaration entityDecl = node as EntityDeclaration;
if (entityDecl != null) if (entityDecl != null)
PrintModifiers(entityDecl.Modifiers, writer); PrintModifiers(entityDecl.Modifiers, writer);
@ -167,6 +168,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} else { } else {
writer.WriteToken(Roles.Semicolon, ";"); writer.WriteToken(Roles.Semicolon, ";");
} }
writer.EndNode(node);
} }
} }
@ -189,7 +191,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
TypeSystemAstBuilder CreateAstBuilder() TypeSystemAstBuilder CreateAstBuilder()
{ {
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(); TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder();
astBuilder.AddTypeReferenceAnnotations = true; astBuilder.AddResolveResultAnnotations = true;
astBuilder.ShowTypeParametersForUnboundTypes = true; astBuilder.ShowTypeParametersForUnboundTypes = true;
astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers; astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers;
astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility; astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility;

6
ICSharpCode.Decompiler/CSharp/OutputVisitor/ITokenWriter.cs

@ -68,6 +68,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new InsertMissingTokensDecorator(target, target))); return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new InsertMissingTokensDecorator(target, target)));
} }
public static TokenWriter InsertRequiredSpaces(TokenWriter writer)
{
return new InsertRequiredSpacesDecorator(writer);
}
public static TokenWriter WrapInWriterThatSetsLocationsInAST(TokenWriter writer) public static TokenWriter WrapInWriterThatSetsLocationsInAST(TokenWriter writer)
{ {
if (!(writer is ILocatable)) if (!(writer is ILocatable))
@ -79,6 +84,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public interface ILocatable public interface ILocatable
{ {
TextLocation Location { get; } TextLocation Location { get; }
int Length { get; }
} }
public abstract class DecoratingTokenWriter : TokenWriter public abstract class DecoratingTokenWriter : TokenWriter

72
ICSharpCode.Decompiler/CSharp/OutputVisitor/TextWriterTokenWriter.cs

@ -30,22 +30,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public class TextWriterTokenWriter : TokenWriter, ILocatable public class TextWriterTokenWriter : TokenWriter, ILocatable
{ {
readonly TextWriter textWriter; readonly TextWriter textWriter;
int indentation;
bool needsIndent = true; bool needsIndent = true;
bool isAtStartOfLine = true; bool isAtStartOfLine = true;
int line, column; int line, column;
public int Indentation { public int Indentation { get; set; }
get { return this.indentation; }
set { this.indentation = value; }
}
public TextLocation Location { public TextLocation Location {
get { return new TextLocation(line, column + (needsIndent ? indentation * IndentationString.Length : 0)); } get { return new TextLocation(line, column + (needsIndent ? Indentation * IndentationString.Length : 0)); }
} }
public string IndentationString { get; set; } public string IndentationString { get; set; }
public int Length { get; private set; }
public TextWriterTokenWriter(TextWriter textWriter) public TextWriterTokenWriter(TextWriter textWriter)
{ {
if (textWriter == null) if (textWriter == null)
@ -62,10 +60,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) { if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) {
textWriter.Write('@'); textWriter.Write('@');
column++; column++;
Length++;
} }
string name = EscapeIdentifier(identifier.Name); string name = EscapeIdentifier(identifier.Name);
textWriter.Write(name); textWriter.Write(name);
column += name.Length; column += name.Length;
Length += name.Length;
isAtStartOfLine = false; isAtStartOfLine = false;
} }
@ -73,6 +73,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
WriteIndentation(); WriteIndentation();
column += keyword.Length; column += keyword.Length;
Length += keyword.Length;
textWriter.Write(keyword); textWriter.Write(keyword);
isAtStartOfLine = false; isAtStartOfLine = false;
} }
@ -81,6 +82,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
WriteIndentation(); WriteIndentation();
column += token.Length; column += token.Length;
Length += token.Length;
textWriter.Write(token); textWriter.Write(token);
isAtStartOfLine = false; isAtStartOfLine = false;
} }
@ -89,6 +91,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
WriteIndentation(); WriteIndentation();
column++; column++;
Length++;
textWriter.Write(' '); textWriter.Write(' ');
} }
@ -96,10 +99,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
if (needsIndent) { if (needsIndent) {
needsIndent = false; needsIndent = false;
for (int i = 0; i < indentation; i++) { for (int i = 0; i < Indentation; i++) {
textWriter.Write(this.IndentationString); textWriter.Write(this.IndentationString);
} }
column += indentation * IndentationString.Length; column += Indentation * IndentationString.Length;
Length += Indentation * IndentationString.Length;
} }
} }
@ -108,18 +112,19 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
textWriter.WriteLine(); textWriter.WriteLine();
column = 1; column = 1;
line++; line++;
Length += textWriter.NewLine.Length;
needsIndent = true; needsIndent = true;
isAtStartOfLine = true; isAtStartOfLine = true;
} }
public override void Indent() public override void Indent()
{ {
indentation++; Indentation++;
} }
public override void Unindent() public override void Unindent()
{ {
indentation--; Indentation--;
} }
public override void WriteComment(CommentType commentType, string content) public override void WriteComment(CommentType commentType, string content)
@ -129,6 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case CommentType.SingleLine: case CommentType.SingleLine:
textWriter.Write("//"); textWriter.Write("//");
textWriter.WriteLine(content); textWriter.WriteLine(content);
Length += 2 + content.Length + textWriter.NewLine.Length;
column = 1; column = 1;
line++; line++;
needsIndent = true; needsIndent = true;
@ -138,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
textWriter.Write("/*"); textWriter.Write("/*");
textWriter.Write(content); textWriter.Write(content);
textWriter.Write("*/"); textWriter.Write("*/");
Length += 4 + content.Length;
column += 2; column += 2;
UpdateEndLocation(content, ref line, ref column); UpdateEndLocation(content, ref line, ref column);
column += 2; column += 2;
@ -146,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case CommentType.Documentation: case CommentType.Documentation:
textWriter.Write("///"); textWriter.Write("///");
textWriter.WriteLine(content); textWriter.WriteLine(content);
Length += 3 + content.Length + textWriter.NewLine.Length;
column = 1; column = 1;
line++; line++;
needsIndent = true; needsIndent = true;
@ -155,6 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
textWriter.Write("/**"); textWriter.Write("/**");
textWriter.Write(content); textWriter.Write(content);
textWriter.Write("*/"); textWriter.Write("*/");
Length += 5 + content.Length;
column += 3; column += 3;
UpdateEndLocation(content, ref line, ref column); UpdateEndLocation(content, ref line, ref column);
column += 2; column += 2;
@ -163,6 +172,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
default: default:
textWriter.Write(content); textWriter.Write(content);
column += content.Length; column += content.Length;
Length += content.Length;
break; break;
} }
} }
@ -197,10 +207,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
string directive = type.ToString().ToLowerInvariant(); string directive = type.ToString().ToLowerInvariant();
textWriter.Write(directive); textWriter.Write(directive);
column += 1 + directive.Length; column += 1 + directive.Length;
Length += 1 + directive.Length;
if (!string.IsNullOrEmpty(argument)) { if (!string.IsNullOrEmpty(argument)) {
textWriter.Write(' '); textWriter.Write(' ');
textWriter.Write(argument); textWriter.Write(argument);
column += 1 + argument.Length; column += 1 + argument.Length;
Length += 1 + argument.Length;
} }
NewLine(); NewLine();
} }
@ -218,6 +230,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
if (literalValue != null) { if (literalValue != null) {
textWriter.Write(literalValue); textWriter.Write(literalValue);
column += literalValue.Length; column += literalValue.Length;
Length += literalValue.Length;
return; return;
} }
@ -225,6 +238,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// usually NullReferenceExpression should be used for this, but we'll handle it anyways // usually NullReferenceExpression should be used for this, but we'll handle it anyways
textWriter.Write("null"); textWriter.Write("null");
column += 4; column += 4;
Length += 4;
return; return;
} }
@ -232,9 +246,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
if ((bool)value) { if ((bool)value) {
textWriter.Write("true"); textWriter.Write("true");
column += 4; column += 4;
Length += 4;
} else { } else {
textWriter.Write("false"); textWriter.Write("false");
column += 5; column += 5;
Length += 5;
} }
return; return;
} }
@ -242,18 +258,21 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
if (value is string) { if (value is string) {
string tmp = ConvertString(value.ToString()); string tmp = ConvertString(value.ToString());
column += tmp.Length + 2; column += tmp.Length + 2;
Length += tmp.Length + 2;
textWriter.Write('"'); textWriter.Write('"');
textWriter.Write(tmp); textWriter.Write(tmp);
textWriter.Write('"'); textWriter.Write('"');
} else if (value is char) { } else if (value is char) {
string tmp = ConvertCharLiteral((char)value); string tmp = ConvertCharLiteral((char)value);
column += tmp.Length + 2; column += tmp.Length + 2;
Length += tmp.Length + 2;
textWriter.Write('\''); textWriter.Write('\'');
textWriter.Write(tmp); textWriter.Write(tmp);
textWriter.Write('\''); textWriter.Write('\'');
} else if (value is decimal) { } else if (value is decimal) {
string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"; string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m";
column += str.Length; column += str.Length;
Length += str.Length;
textWriter.Write(str); textWriter.Write(str);
} else if (value is float) { } else if (value is float) {
float f = (float)value; float f = (float)value;
@ -262,16 +281,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// but we still support writing these to make life easier for code generators. // but we still support writing these to make life easier for code generators.
textWriter.Write("float"); textWriter.Write("float");
column += 5; column += 5;
Length += 5;
WriteToken(Roles.Dot, "."); WriteToken(Roles.Dot, ".");
if (float.IsPositiveInfinity(f)) { if (float.IsPositiveInfinity(f)) {
textWriter.Write("PositiveInfinity"); textWriter.Write("PositiveInfinity");
column += "PositiveInfinity".Length; column += "PositiveInfinity".Length;
Length += "PositiveInfinity".Length;
} else if (float.IsNegativeInfinity(f)) { } else if (float.IsNegativeInfinity(f)) {
textWriter.Write("NegativeInfinity"); textWriter.Write("NegativeInfinity");
column += "NegativeInfinity".Length; column += "NegativeInfinity".Length;
Length += "NegativeInfinity".Length;
} else { } else {
textWriter.Write("NaN"); textWriter.Write("NaN");
column += 3; column += 3;
Length += 3;
} }
return; return;
} }
@ -281,9 +304,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// the special case here than to do it in all code generators) // the special case here than to do it in all code generators)
textWriter.Write("-"); textWriter.Write("-");
column++; column++;
Length++;
} }
var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"; var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f";
column += str.Length; column += str.Length;
Length += str.Length;
textWriter.Write(str); textWriter.Write(str);
} else if (value is double) { } else if (value is double) {
double f = (double)value; double f = (double)value;
@ -292,16 +317,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// but we still support writing these to make life easier for code generators. // but we still support writing these to make life easier for code generators.
textWriter.Write("double"); textWriter.Write("double");
column += 6; column += 6;
Length += 6;
WriteToken(Roles.Dot, "."); WriteToken(Roles.Dot, ".");
if (double.IsPositiveInfinity(f)) { if (double.IsPositiveInfinity(f)) {
textWriter.Write("PositiveInfinity"); textWriter.Write("PositiveInfinity");
column += "PositiveInfinity".Length; column += "PositiveInfinity".Length;
Length += "PositiveInfinity".Length;
} else if (double.IsNegativeInfinity(f)) { } else if (double.IsNegativeInfinity(f)) {
textWriter.Write("NegativeInfinity"); textWriter.Write("NegativeInfinity");
column += "NegativeInfinity".Length; column += "NegativeInfinity".Length;
Length += "NegativeInfinity".Length;
} else { } else {
textWriter.Write("NaN"); textWriter.Write("NaN");
column += 3; column += 3;
Length += 3;
} }
return; return;
} }
@ -310,20 +339,22 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// (again, not a primitive expression, but it's better to handle // (again, not a primitive expression, but it's better to handle
// the special case here than to do it in all code generators) // the special case here than to do it in all code generators)
textWriter.Write("-"); textWriter.Write("-");
Length++;
} }
string number = f.ToString("R", NumberFormatInfo.InvariantInfo); string number = f.ToString("R", NumberFormatInfo.InvariantInfo);
if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) {
number += ".0"; number += ".0";
} }
textWriter.Write(number); textWriter.Write(number);
Length += number.Length;
} else if (value is IFormattable) { } else if (value is IFormattable) {
StringBuilder b = new StringBuilder (); StringBuilder b = new StringBuilder();
// if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { // if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) {
// b.Append("0x"); // b.Append("0x");
// b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); // b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo));
// } else { // } else {
b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo)); b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo));
// } // }
if (value is uint || value is ulong) { if (value is uint || value is ulong) {
b.Append("u"); b.Append("u");
} }
@ -332,9 +363,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} }
textWriter.Write(b.ToString()); textWriter.Write(b.ToString());
column += b.Length; column += b.Length;
Length += b.Length;
} else { } else {
textWriter.Write(value.ToString()); textWriter.Write(value.ToString());
column += value.ToString().Length; int length = value.ToString().Length;
column += length;
Length += length;
} }
} }
@ -412,7 +446,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// </summary> /// </summary>
public static string ConvertString(string str) public static string ConvertString(string str)
{ {
StringBuilder sb = new StringBuilder (); StringBuilder sb = new StringBuilder();
foreach (char ch in str) { foreach (char ch in str) {
string s = ch == '"' ? "\\\"" : ConvertChar(ch); string s = ch == '"' ? "\\\"" : ConvertChar(ch);
if (s != null) sb.Append(s); if (s != null) sb.Append(s);
@ -482,9 +516,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
textWriter.Write(type); textWriter.Write(type);
column += type.Length; column += type.Length;
Length += type.Length;
if (type == "new") { if (type == "new") {
textWriter.Write("()"); textWriter.Write("()");
column += 2; column += 2;
Length += 2;
} }
} }

2
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -212,8 +212,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
private void AddTypeAnnotation(AstType astType, IType type) private void AddTypeAnnotation(AstType astType, IType type)
{ {
if (AddTypeReferenceAnnotations)
astType.AddAnnotation(type);
if (AddResolveResultAnnotations) if (AddResolveResultAnnotations)
astType.AddAnnotation(new TypeResolveResult(type)); astType.AddAnnotation(new TypeResolveResult(type));
} }

240
ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs

@ -0,0 +1,240 @@
// Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml.Linq;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.Documentation
{
/// <summary>
/// Represents an element in the XML documentation.
/// Any occurrences of "&lt;inheritdoc/>" are replaced with the inherited documentation.
/// </summary>
public class XmlDocumentationElement
{
static XmlDocumentationElement Create(string documentationComment, IEntity declaringEntity)
{
return new XmlDocumentationElement(XElement.Parse(documentationComment), declaringEntity, null);
}
readonly XElement element;
readonly IEntity declaringEntity;
readonly Func<string, IEntity> crefResolver;
volatile string textContent;
/// <summary>
/// Inheritance level; used to prevent cyclic doc inheritance.
/// </summary>
int nestingLevel;
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(XElement element, IEntity declaringEntity, Func<string, IEntity> crefResolver)
{
if (element == null)
throw new ArgumentNullException("element");
this.element = element;
this.declaringEntity = declaringEntity;
this.crefResolver = crefResolver;
}
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(string text, IEntity declaringEntity)
{
if (text == null)
throw new ArgumentNullException("text");
this.declaringEntity = declaringEntity;
this.textContent = text;
}
/// <summary>
/// Gets the entity on which this documentation was originally declared.
/// May return null.
/// </summary>
public IEntity DeclaringEntity {
get { return declaringEntity; }
}
IEntity referencedEntity;
volatile bool referencedEntityInitialized;
/// <summary>
/// Gets the entity referenced by the 'cref' attribute.
/// May return null.
/// </summary>
public IEntity ReferencedEntity {
get {
if (!referencedEntityInitialized) {
string cref = GetAttribute("cref");
if (!string.IsNullOrEmpty(cref) && crefResolver != null)
referencedEntity = crefResolver(cref);
referencedEntityInitialized = true;
}
return referencedEntity;
}
}
/// <summary>
/// Gets the element name.
/// </summary>
public string Name {
get {
return element != null ? element.Name.LocalName : string.Empty;
}
}
/// <summary>
/// Gets the attribute value.
/// </summary>
public string GetAttribute(string name)
{
return element?.Attribute(name)?.Value ?? string.Empty;
}
/// <summary>
/// Gets whether this is a pure text node.
/// </summary>
public bool IsTextNode {
get { return element == null; }
}
/// <summary>
/// Gets the text content.
/// </summary>
public string TextContent {
get {
if (textContent == null) {
StringBuilder b = new StringBuilder();
foreach (var child in this.Children)
b.Append(child.TextContent);
textContent = b.ToString();
}
return textContent;
}
}
IList<XmlDocumentationElement> children;
/// <summary>
/// Gets the child elements.
/// </summary>
public IList<XmlDocumentationElement> Children {
get {
if (element == null)
return EmptyList<XmlDocumentationElement>.Instance;
return LazyInitializer.EnsureInitialized(
ref this.children,
() => CreateElements(element.Nodes(), declaringEntity, crefResolver, nestingLevel));
}
}
static readonly string[] doNotInheritIfAlreadyPresent = {
"example", "exclude", "filterpriority", "preliminary", "summary",
"remarks", "returns", "threadsafety", "value"
};
static List<XmlDocumentationElement> CreateElements(IEnumerable<XObject> childObjects, IEntity declaringEntity, Func<string, IEntity> crefResolver, int nestingLevel)
{
List<XmlDocumentationElement> list = new List<XmlDocumentationElement>();
foreach (var child in childObjects) {
var childText = child as XText;
var childTag = child as XCData;
var childElement = child as XElement;
if (childText != null) {
list.Add(new XmlDocumentationElement(childText.Value, declaringEntity));
} else if (childTag != null) {
list.Add(new XmlDocumentationElement(childTag.Value, declaringEntity));
} else if (childElement != null) {
if (nestingLevel < 5 && childElement.Name == "inheritdoc") {
string cref = childElement.Attribute("cref").Value;
IEntity inheritedFrom = null;
string inheritedDocumentation = null;
if (cref != null) {
inheritedFrom = crefResolver(cref);
if (inheritedFrom != null)
inheritedDocumentation = inheritedFrom.GetDocumentation();
} else {
foreach (IMember baseMember in InheritanceHelper.GetBaseMembers((IMember)declaringEntity, includeImplementedInterfaces: true)) {
inheritedDocumentation = baseMember.GetDocumentation();
if (inheritedDocumentation != null) {
inheritedFrom = baseMember;
break;
}
}
}
if (inheritedDocumentation != null) {
var doc = XDocument.Parse(inheritedDocumentation);
// XPath filter not yet implemented
if (childElement.Parent == null && childElement.Attribute("select").Value == null) {
// Inheriting documentation at the root level
List<string> doNotInherit = new List<string>();
doNotInherit.Add("overloads");
doNotInherit.AddRange(childObjects.OfType<XElement>().Select(e => e.Name.LocalName).Intersect(
doNotInheritIfAlreadyPresent));
var inheritedChildren = doc.Nodes().Where(
inheritedObject => {
XElement inheritedElement = inheritedObject as XElement;
return !(inheritedElement != null && doNotInherit.Contains(inheritedElement.Name.LocalName));
});
list.AddRange(CreateElements(inheritedChildren, inheritedFrom, crefResolver, nestingLevel + 1));
}
}
} else {
list.Add(new XmlDocumentationElement(childElement, declaringEntity, crefResolver) { nestingLevel = nestingLevel });
}
}
}
if (list.Count > 0 && list[0].IsTextNode) {
if (string.IsNullOrWhiteSpace(list[0].textContent))
list.RemoveAt(0);
else
list[0].textContent = list[0].textContent.TrimStart();
}
if (list.Count > 0 && list[list.Count - 1].IsTextNode) {
if (string.IsNullOrWhiteSpace(list[list.Count - 1].textContent))
list.RemoveAt(list.Count - 1);
else
list[list.Count - 1].textContent = list[list.Count - 1].textContent.TrimEnd();
}
return list;
}
/// <inheritdoc/>
public override string ToString()
{
if (element != null)
return "<" + element.Name + ">";
else
return this.TextContent;
}
}
}

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -63,6 +63,7 @@
<Compile Include="CSharp\CSharpLanguageVersion.cs" /> <Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" /> <Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" /> <Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="Documentation\XmlDocumentationElement.cs" />
<Compile Include="Solution\ProjectId.cs" /> <Compile Include="Solution\ProjectId.cs" />
<Compile Include="Solution\ProjectItem.cs" /> <Compile Include="Solution\ProjectItem.cs" />
<Compile Include="Solution\SolutionCreator.cs" /> <Compile Include="Solution\SolutionCreator.cs" />

9
ICSharpCode.Decompiler/NRExtensions.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler namespace ICSharpCode.Decompiler
@ -86,5 +87,13 @@ namespace ICSharpCode.Decompiler
return base.VisitTypeDefinition(type); return base.VisitTypeDefinition(type);
} }
} }
internal static string GetDocumentation(this IEntity entity)
{
var docProvider = XmlDocLoader.LoadDocumentation(entity.ParentModule.PEFile);
if (docProvider == null)
return null;
return docProvider.GetDocumentation(entity);
}
} }
} }

40
ILSpy/ExtensionMethods.cs

@ -18,6 +18,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
@ -161,5 +163,43 @@ namespace ICSharpCode.ILSpy
} }
return result; return result;
} }
#region DPI independence
public static Rect TransformToDevice(this Rect rect, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
return Rect.Transform(rect, matrix);
}
public static Rect TransformFromDevice(this Rect rect, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return Rect.Transform(rect, matrix);
}
public static Size TransformToDevice(this Size size, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
}
public static Size TransformFromDevice(this Size size, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
}
public static Point TransformToDevice(this Point point, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
return matrix.Transform(point);
}
public static Point TransformFromDevice(this Point point, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return matrix.Transform(point);
}
#endregion
} }
} }

3
ILSpy/ILSpy.csproj

@ -39,6 +39,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
<Reference Include="System.IO.Compression" /> <Reference Include="System.IO.Compression" />
@ -225,7 +226,7 @@
<Compile Include="TextView\EditorCommands.cs" /> <Compile Include="TextView\EditorCommands.cs" />
<Compile Include="TextView\FoldingCommands.cs" /> <Compile Include="TextView\FoldingCommands.cs" />
<Compile Include="Commands\SaveCodeContextMenuEntry.cs" /> <Compile Include="Commands\SaveCodeContextMenuEntry.cs" />
<Compile Include="TextView\XmlDocRenderer.cs" /> <Compile Include="TextView\DocumentationUIBuilder.cs" />
<Compile Include="Analyzers\AnalyzeCommand.cs" /> <Compile Include="Analyzers\AnalyzeCommand.cs" />
<Compile Include="Analyzers\Builtin\FieldAccessAnalyzer.cs" /> <Compile Include="Analyzers\Builtin\FieldAccessAnalyzer.cs" />
<Compile Include="Analyzers\TreeNodes\AnalyzedFieldTreeNode.cs" /> <Compile Include="Analyzers\TreeNodes\AnalyzedFieldTreeNode.cs" />

66
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -29,8 +29,6 @@ namespace ICSharpCode.ILSpy
{ {
class CSharpHighlightingTokenWriter : DecoratingTokenWriter class CSharpHighlightingTokenWriter : DecoratingTokenWriter
{ {
ISmartTextOutput textOutput;
HighlightingColor visibilityKeywordsColor; HighlightingColor visibilityKeywordsColor;
HighlightingColor namespaceKeywordsColor; HighlightingColor namespaceKeywordsColor;
HighlightingColor structureKeywordsColor; HighlightingColor structureKeywordsColor;
@ -65,12 +63,15 @@ namespace ICSharpCode.ILSpy
HighlightingColor trueKeywordColor; HighlightingColor trueKeywordColor;
HighlightingColor typeKeywordsColor; HighlightingColor typeKeywordsColor;
public CSharpHighlightingTokenWriter(TokenWriter decoratedWriter, ISmartTextOutput textOutput) : base(decoratedWriter) public RichTextModel HighlightingModel { get; } = new RichTextModel();
public CSharpHighlightingTokenWriter(TokenWriter decoratedWriter, ISmartTextOutput textOutput = null, ILocatable locatable = null)
: base(decoratedWriter)
{ {
this.textOutput = textOutput;
var highlighting = HighlightingManager.Instance.GetDefinition("C#"); var highlighting = HighlightingManager.Instance.GetDefinition("C#");
//this.defaultTextColor = ???; this.locatable = locatable;
this.textOutput = textOutput;
this.visibilityKeywordsColor = highlighting.GetNamedColor("Visibility"); this.visibilityKeywordsColor = highlighting.GetNamedColor("Visibility");
this.namespaceKeywordsColor = highlighting.GetNamedColor("NamespaceKeywords"); this.namespaceKeywordsColor = highlighting.GetNamedColor("NamespaceKeywords");
@ -264,11 +265,11 @@ namespace ICSharpCode.ILSpy
if (nodeStack.PeekOrDefault() is AttributeSection) if (nodeStack.PeekOrDefault() is AttributeSection)
color = attributeKeywordsColor; color = attributeKeywordsColor;
if (color != null) { if (color != null) {
textOutput.BeginSpan(color); BeginSpan(color);
} }
base.WriteKeyword(role, keyword); base.WriteKeyword(role, keyword);
if (color != null) { if (color != null) {
textOutput.EndSpan(); EndSpan();
} }
} }
@ -307,11 +308,11 @@ namespace ICSharpCode.ILSpy
break; break;
} }
if (color != null) { if (color != null) {
textOutput.BeginSpan(color); BeginSpan(color);
} }
base.WritePrimitiveType(type); base.WritePrimitiveType(type);
if (color != null) { if (color != null) {
textOutput.EndSpan(); EndSpan();
} }
} }
@ -383,11 +384,11 @@ namespace ICSharpCode.ILSpy
break; break;
} }
if (color != null) { if (color != null) {
textOutput.BeginSpan(color); BeginSpan(color);
} }
base.WriteIdentifier(identifier); base.WriteIdentifier(identifier);
if (color != null) { if (color != null) {
textOutput.EndSpan(); EndSpan();
} }
} }
@ -401,11 +402,11 @@ namespace ICSharpCode.ILSpy
color = trueKeywordColor; color = trueKeywordColor;
} }
if (color != null) { if (color != null) {
textOutput.BeginSpan(color); BeginSpan(color);
} }
base.WritePrimitiveValue(value, literalValue); base.WritePrimitiveValue(value, literalValue);
if (color != null) { if (color != null) {
textOutput.EndSpan(); EndSpan();
} }
} }
@ -425,6 +426,9 @@ namespace ICSharpCode.ILSpy
ISymbol GetCurrentMemberReference() ISymbol GetCurrentMemberReference()
{ {
if (nodeStack == null || nodeStack.Count == 0)
return null;
AstNode node = nodeStack.Peek(); AstNode node = nodeStack.Peek();
var symbol = node.GetSymbol(); var symbol = node.GetSymbol();
if (symbol == null && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression) { if (symbol == null && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression) {
@ -441,7 +445,7 @@ namespace ICSharpCode.ILSpy
return symbol; return symbol;
} }
Stack<AstNode> nodeStack = new Stack<AstNode>(); readonly Stack<AstNode> nodeStack = new Stack<AstNode>();
public override void StartNode(AstNode node) public override void StartNode(AstNode node)
{ {
@ -454,5 +458,39 @@ namespace ICSharpCode.ILSpy
base.EndNode(node); base.EndNode(node);
nodeStack.Pop(); nodeStack.Pop();
} }
readonly Stack<HighlightingColor> colorStack = new Stack<HighlightingColor>();
HighlightingColor currentColor = new HighlightingColor();
int currentColorBegin = -1;
readonly ILocatable locatable;
readonly ISmartTextOutput textOutput;
private void BeginSpan(HighlightingColor highlightingColor)
{
if (textOutput != null) {
textOutput.BeginSpan(highlightingColor);
return;
}
if (currentColorBegin > -1)
HighlightingModel.SetHighlighting(currentColorBegin, locatable.Length - currentColorBegin, currentColor);
colorStack.Push(currentColor);
currentColor = currentColor.Clone();
currentColorBegin = locatable.Length;
currentColor.MergeWith(highlightingColor);
currentColor.Freeze();
}
private void EndSpan()
{
if (textOutput != null) {
textOutput.EndSpan();
return;
}
HighlightingModel.SetHighlighting(currentColorBegin, locatable.Length - currentColorBegin, currentColor);
currentColor = colorStack.Pop();
currentColorBegin = locatable.Length;
}
} }
} }

14
ILSpy/Languages/CSharpLanguage.cs

@ -26,7 +26,9 @@ using System.Reflection.Metadata;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.OutputVisitor;
@ -485,7 +487,7 @@ namespace ICSharpCode.ILSpy
// HACK : UnknownType is not supported by CSharpAmbience. // HACK : UnknownType is not supported by CSharpAmbience.
} else if (type.Kind == TypeKind.Unknown) { } else if (type.Kind == TypeKind.Unknown) {
return (includeNamespace ? type.FullName : type.Name) return (includeNamespace ? type.FullName : type.Name)
+ (type.TypeParameterCount > 0 ? "<" + string.Join(",", type.TypeArguments.Select(t => t.Name)) + ">" : ""); + (type.TypeParameterCount > 0 ? "<" + string.Join(", ", type.TypeArguments.Select(t => t.Name)) + ">" : "");
} else { } else {
return ambience.ConvertType(type); return ambience.ConvertType(type);
} }
@ -625,10 +627,14 @@ namespace ICSharpCode.ILSpy
return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, new DecompilationOptions().DecompilerSettings); return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, new DecompilationOptions().DecompilerSettings);
} }
public override string GetTooltip(IEntity entity) public override RichText GetRichTextTooltip(IEntity entity)
{ {
var flags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList); var flags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList);
return new CSharpAmbience() { ConversionFlags = flags }.ConvertSymbol(entity); var output = new StringWriter();
var decoratedWriter = new TextWriterTokenWriter(output);
var writer = new CSharpHighlightingTokenWriter(TokenWriter.InsertRequiredSpaces(decoratedWriter), locatable: decoratedWriter);
new CSharpAmbience() { ConversionFlags = flags }.ConvertSymbol(entity, writer, new DecompilerSettings().CSharpFormattingOptions);
return new RichText(output.ToString(), writer.HighlightingModel);
} }
public override CodeMappingInfo GetCodeMappingInfo(PEFile module, EntityHandle member) public override CodeMappingInfo GetCodeMappingInfo(PEFile module, EntityHandle member)

10
ILSpy/Languages/Language.cs

@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Text; using System.Text;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.Solution;
@ -349,6 +350,15 @@ namespace ICSharpCode.ILSpy
return GetDisplayName(entity, true, true, true); return GetDisplayName(entity, true, true, true);
} }
/// <summary>
/// Converts a member signature to a string.
/// This is used for displaying the tooltip on a member reference.
/// </summary>
public virtual RichText GetRichTextTooltip(IEntity entity)
{
return GetTooltip(entity);
}
public virtual string FieldToString(IField field, bool includeDeclaringTypeName, bool includeNamespace, bool includeNamespaceOfDeclaringTypeName) public virtual string FieldToString(IField field, bool includeDeclaringTypeName, bool includeNamespace, bool includeNamespaceOfDeclaringTypeName)
{ {
if (field == null) if (field == null)

4
ILSpy/MainWindow.xaml.cs

@ -373,7 +373,7 @@ namespace ICSharpCode.ILSpy
} }
} }
private IEntity FindEntityInRelevantAssemblies(string navigateTo, IEnumerable<LoadedAssembly> relevantAssemblies) internal static IEntity FindEntityInRelevantAssemblies(string navigateTo, IEnumerable<LoadedAssembly> relevantAssemblies)
{ {
ITypeReference typeRef = null; ITypeReference typeRef = null;
IMemberReference memberRef = null; IMemberReference memberRef = null;
@ -397,7 +397,7 @@ namespace ICSharpCode.ILSpy
return null; return null;
} }
private bool CanResolveTypeInPEFile(PEFile module, ITypeReference typeRef, out EntityHandle typeHandle) static bool CanResolveTypeInPEFile(PEFile module, ITypeReference typeRef, out EntityHandle typeHandle)
{ {
switch (typeRef) { switch (typeRef) {
case GetPotentiallyNestedClassTypeReference topLevelType: case GetPotentiallyNestedClassTypeReference topLevelType:

241
ILSpy/TextView/DecompilerTextView.cs

@ -27,6 +27,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
@ -44,8 +45,10 @@ using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search; using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.AvalonEdit;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
@ -63,7 +66,7 @@ namespace ICSharpCode.ILSpy.TextView
{ {
readonly ReferenceElementGenerator referenceElementGenerator; readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator; readonly UIElementGenerator uiElementGenerator;
List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>(); readonly List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
RichTextColorizer activeRichTextColorizer; RichTextColorizer activeRichTextColorizer;
BracketHighlightRenderer bracketHighlightRenderer; BracketHighlightRenderer bracketHighlightRenderer;
FoldingManager foldingManager; FoldingManager foldingManager;
@ -112,6 +115,8 @@ namespace ICSharpCode.ILSpy.TextView
textEditor.TextArea.PreviewMouseDown += TextAreaMouseDown; textEditor.TextArea.PreviewMouseDown += TextAreaMouseDown;
textEditor.TextArea.PreviewMouseUp += TextAreaMouseUp; textEditor.TextArea.PreviewMouseUp += TextAreaMouseUp;
textEditor.TextArea.Caret.PositionChanged += HighlightBrackets; textEditor.TextArea.Caret.PositionChanged += HighlightBrackets;
textEditor.MouseMove += TextEditorMouseMove;
textEditor.MouseLeave += TextEditorMouseLeave;
textEditor.SetBinding(Control.FontFamilyProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFont") }); textEditor.SetBinding(Control.FontFamilyProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFont") });
textEditor.SetBinding(Control.FontSizeProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFontSize") }); textEditor.SetBinding(Control.FontSizeProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFontSize") });
textEditor.SetBinding(TextEditor.WordWrapProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("EnableWordWrap") }); textEditor.SetBinding(TextEditor.WordWrapProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("EnableWordWrap") });
@ -170,16 +175,14 @@ namespace ICSharpCode.ILSpy.TextView
#endregion #endregion
#region Tooltip support #region Tooltip support
ToolTip tooltip; ToolTip toolTip;
Popup popupToolTip;
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
if (tooltip != null)
tooltip.IsOpen = false;
}
void TextViewMouseHover(object sender, MouseEventArgs e) void TextViewMouseHover(object sender, MouseEventArgs e)
{ {
if (!TryCloseExistingPopup(false)) {
return;
}
TextViewPosition? position = GetPositionFromMousePosition(); TextViewPosition? position = GetPositionFromMousePosition();
if (position == null) if (position == null)
return; return;
@ -190,35 +193,166 @@ namespace ICSharpCode.ILSpy.TextView
if (seg == null) if (seg == null)
return; return;
object content = GenerateTooltip(seg); object content = GenerateTooltip(seg);
if (tooltip != null)
tooltip.IsOpen = false; if (content != null) {
if (content != null) popupToolTip = content as Popup;
tooltip = new ToolTip() { Content = content, IsOpen = true };
if (popupToolTip != null) {
var popupPosition = GetPopupPosition(e);
popupToolTip.Closed += ToolTipClosed;
popupToolTip.HorizontalOffset = popupPosition.X;
popupToolTip.VerticalOffset = popupPosition.Y;
popupToolTip.StaysOpen = true; // We will close it ourselves
e.Handled = true;
popupToolTip.IsOpen = true;
distanceToPopupLimit = double.PositiveInfinity; // reset limit; we'll re-calculate it on the next mouse movement
} else {
if (toolTip == null) {
toolTip = new ToolTip();
toolTip.Closed += ToolTipClosed;
}
toolTip.PlacementTarget = this; // required for property inheritance
if (content is string s) {
toolTip.Content = new TextBlock {
Text = s,
TextWrapping = TextWrapping.Wrap
};
} else
toolTip.Content = content;
e.Handled = true;
toolTip.IsOpen = true;
}
}
}
bool TryCloseExistingPopup(bool mouseClick)
{
if (popupToolTip != null) {
if (popupToolTip.IsOpen && !mouseClick && popupToolTip is FlowDocumentTooltip t && !t.CloseWhenMouseMovesAway) {
return false; // Popup does not want to be closed yet
}
popupToolTip.IsOpen = false;
popupToolTip = null;
}
return true;
}
/// <summary> Returns Popup position based on mouse position, in device independent units </summary>
Point GetPopupPosition(MouseEventArgs mouseArgs)
{
Point mousePos = mouseArgs.GetPosition(this);
Point positionInPixels;
// align Popup with line bottom
TextViewPosition? logicalPos = textEditor.GetPositionFromPoint(mousePos);
if (logicalPos.HasValue) {
var textView = textEditor.TextArea.TextView;
positionInPixels =
textView.PointToScreen(
textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom) - textView.ScrollOffset);
positionInPixels.X -= 4;
} else {
positionInPixels = PointToScreen(mousePos + new Vector(-4, 6));
}
// use device independent units, because Popup Left/Top are in independent units
return positionInPixels.TransformFromDevice(this);
}
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
// Non-popup tooltips get closed as soon as the mouse starts moving again
if (toolTip != null) {
toolTip.IsOpen = false;
e.Handled = true;
}
}
double distanceToPopupLimit;
const double MaxMovementAwayFromPopup = 5;
void TextEditorMouseMove(object sender, MouseEventArgs e)
{
if (popupToolTip != null) {
double distanceToPopup = GetDistanceToPopup(e);
if (distanceToPopup > distanceToPopupLimit) {
// Close popup if mouse moved away, exceeding the limit
TryCloseExistingPopup(false);
} else {
// reduce distanceToPopupLimit
distanceToPopupLimit = Math.Min(distanceToPopupLimit, distanceToPopup + MaxMovementAwayFromPopup);
}
}
}
double GetDistanceToPopup(MouseEventArgs e)
{
Point p = popupToolTip.Child.PointFromScreen(PointToScreen(e.GetPosition(this)));
Size size = popupToolTip.Child.RenderSize;
double x = 0;
if (p.X < 0)
x = -p.X;
else if (p.X > size.Width)
x = p.X - size.Width;
double y = 0;
if (p.Y < 0)
y = -p.Y;
else if (p.Y > size.Height)
y = p.Y - size.Height;
return Math.Sqrt(x * x + y * y);
}
void TextEditorMouseLeave(object sender, MouseEventArgs e)
{
if (popupToolTip != null && !popupToolTip.IsMouseOver) {
// do not close popup if mouse moved from editor to popup
TryCloseExistingPopup(false);
}
}
void OnUnloaded(object sender, EventArgs e)
{
// Close popup when another document gets selected
// TextEditorMouseLeave is not sufficient for this because the mouse might be over the popup when the document switch happens (e.g. Ctrl+Tab)
TryCloseExistingPopup(true);
}
void ToolTipClosed(object sender, EventArgs e)
{
if (toolTip == sender) {
toolTip = null;
}
if (popupToolTip == sender) {
// Because popupToolTip instances are created by the tooltip provider,
// they might be reused; so we should detach the event handler
popupToolTip.Closed -= ToolTipClosed;
popupToolTip = null;
}
} }
object GenerateTooltip(ReferenceSegment segment) object GenerateTooltip(ReferenceSegment segment)
{ {
if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) { if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) {
XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation; XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
if (docProvider != null){ DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), MainWindow.Instance.CurrentLanguage.SyntaxHighlighting);
renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})");
if (docProvider != null) {
string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + code.EncodedName); string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + code.EncodedName);
if (documentation != null) { if (documentation != null) {
XmlDocRenderer renderer = new XmlDocRenderer(); renderer.AddXmlDocumentation(documentation, null, null);
renderer.AppendText($"{code.Name} (0x{code.Code:x}) - ");
renderer.AddXmlDocumentation(documentation);
return renderer.CreateTextBlock();
} }
} }
return $"{code.Name} (0x{code.Code:x})"; return new FlowDocumentTooltip(renderer.CreateDocument());
} else if (segment.Reference is IEntity entity) { } else if (segment.Reference is IEntity entity) {
return CreateTextBlockForEntity(entity); return new FlowDocumentTooltip(CreateTooltipForEntity(entity));
} else if (segment.Reference is ValueTuple<PEFile, System.Reflection.Metadata.EntityHandle> unresolvedEntity) { } else if (segment.Reference is ValueTuple<PEFile, System.Reflection.Metadata.EntityHandle> unresolvedEntity) {
var typeSystem = new DecompilerTypeSystem(unresolvedEntity.Item1, unresolvedEntity.Item1.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached); var typeSystem = new DecompilerTypeSystem(unresolvedEntity.Item1, unresolvedEntity.Item1.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached);
try { try {
IEntity resolved = typeSystem.MainModule.ResolveEntity(unresolvedEntity.Item2); IEntity resolved = typeSystem.MainModule.ResolveEntity(unresolvedEntity.Item2);
if (resolved == null) if (resolved == null)
return null; return null;
return CreateTextBlockForEntity(resolved); return new FlowDocumentTooltip(CreateTooltipForEntity(resolved));
} catch (BadImageFormatException) { } catch (BadImageFormatException) {
return null; return null;
} }
@ -226,10 +360,12 @@ namespace ICSharpCode.ILSpy.TextView
return null; return null;
} }
static TextBlock CreateTextBlockForEntity(IEntity resolved) static FlowDocument CreateTooltipForEntity(IEntity resolved)
{ {
XmlDocRenderer renderer = new XmlDocRenderer(); Language currentLanguage = MainWindow.Instance.CurrentLanguage;
renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(resolved)); DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting);
RichText richText = currentLanguage.GetRichTextTooltip(resolved);
renderer.AddSignatureBlock(richText.Text, richText.ToRichTextModel());
try { try {
if (resolved.ParentModule == null || resolved.ParentModule.PEFile == null) if (resolved.ParentModule == null || resolved.ParentModule.PEFile == null)
return null; return null;
@ -237,14 +373,65 @@ namespace ICSharpCode.ILSpy.TextView
if (docProvider != null) { if (docProvider != null) {
string documentation = docProvider.GetDocumentation(resolved.GetIdString()); string documentation = docProvider.GetDocumentation(resolved.GetIdString());
if (documentation != null) { if (documentation != null) {
renderer.AppendText(Environment.NewLine); renderer.AddXmlDocumentation(documentation, resolved, ResolveReference);
renderer.AddXmlDocumentation(documentation);
} }
} }
} catch (XmlException) { } catch (XmlException) {
// ignore // ignore
} }
return renderer.CreateTextBlock(); return renderer.CreateDocument();
IEntity ResolveReference(string idString)
{
return MainWindow.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.CurrentAssemblyList.GetAssemblies());
}
}
sealed class FlowDocumentTooltip : Popup
{
readonly FlowDocumentScrollViewer viewer;
public FlowDocumentTooltip(FlowDocument document)
{
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
double fontSize = DisplaySettingsPanel.CurrentDisplaySettings.SelectedFontSize;
viewer = new FlowDocumentScrollViewer() {
Width = document.MinPageWidth + fontSize * 5,
MaxWidth = MainWindow.Instance.ActualWidth
};
viewer.Document = document;
Border border = new Border {
Background = SystemColors.ControlBrush,
BorderBrush = SystemColors.ControlDarkBrush,
BorderThickness = new Thickness(1),
MaxHeight = 400,
Child = viewer
};
this.Child = border;
viewer.Foreground = SystemColors.InfoTextBrush;
document.FontSize = fontSize;
}
public bool CloseWhenMouseMovesAway {
get { return !this.IsKeyboardFocusWithin; }
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
this.IsOpen = false;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// When the mouse is over the popup, it is possible for ILSpy to be minimized,
// or moved into the background, and yet the popup stays open.
// We don't have a good method here to check whether the mouse moved back into the text area
// or somewhere else, so we'll just close the popup.
if (CloseWhenMouseMovesAway)
this.IsOpen = false;
}
} }
#endregion #endregion
@ -336,7 +523,7 @@ namespace ICSharpCode.ILSpy.TextView
return tcs.Task; return tcs.Task;
} }
void cancelButton_Click(object sender, RoutedEventArgs e) void CancelButton_Click(object sender, RoutedEventArgs e)
{ {
if (currentCancellationTokenSource != null) { if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel(); currentCancellationTokenSource.Cancel();

2
ILSpy/TextView/DecompilerTextView.xaml

@ -21,7 +21,7 @@
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock FontSize="14pt" Text="{x:Static properties:Resources.Decompiling}"/> <TextBlock FontSize="14pt" Text="{x:Static properties:Resources.Decompiling}"/>
<ProgressBar Name="progressBar" Height="16" Margin="0, 4" /> <ProgressBar Name="progressBar" Height="16" Margin="0, 4" />
<Button Click="cancelButton_Click" HorizontalAlignment="Center" Content="{x:Static properties:Resources.Cancel}"/> <Button Click="CancelButton_Click" HorizontalAlignment="Center" Content="{x:Static properties:Resources.Cancel}"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>

551
ILSpy/TextView/DocumentationUIBuilder.cs

@ -0,0 +1,551 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.Options;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Renders XML documentation into a WPF <see cref="FlowDocument"/>.
/// </summary>
public class DocumentationUIBuilder
{
readonly IAmbience ambience;
readonly IHighlightingDefinition highlightingDefinition;
readonly FlowDocument document;
BlockCollection blockCollection;
InlineCollection inlineCollection;
public DocumentationUIBuilder(IAmbience ambience, IHighlightingDefinition highlightingDefinition)
{
this.ambience = ambience;
this.highlightingDefinition = highlightingDefinition;
this.document = new FlowDocument();
this.blockCollection = document.Blocks;
this.ShowSummary = true;
this.ShowAllParameters = true;
this.ShowReturns = true;
this.ShowThreadSafety = true;
this.ShowExceptions = true;
this.ShowTypeParameters = true;
this.ShowExample = true;
this.ShowPreliminary = true;
this.ShowSeeAlso = true;
this.ShowValue = true;
this.ShowPermissions = true;
this.ShowRemarks = true;
}
public FlowDocument CreateDocument()
{
FlushAddedText(true);
return document;
}
public bool ShowExceptions { get; set; }
public bool ShowPermissions { get; set; }
public bool ShowExample { get; set; }
public bool ShowPreliminary { get; set; }
public bool ShowRemarks { get; set; }
public bool ShowSummary { get; set; }
public bool ShowReturns { get; set; }
public bool ShowSeeAlso { get; set; }
public bool ShowThreadSafety { get; set; }
public bool ShowTypeParameters { get; set; }
public bool ShowValue { get; set; }
public bool ShowAllParameters { get; set; }
public void AddCodeBlock(string textContent, bool keepLargeMargin = false)
{
var document = new TextDocument(textContent);
var highlighter = new DocumentHighlighter(document, highlightingDefinition);
var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, highlighter).ToRichTextModel();
var block = new Paragraph();
block.Inlines.AddRange(richText.CreateRuns(document));
block.FontFamily = GetCodeFont();
if (!keepLargeMargin)
block.Margin = new Thickness(0, 6, 0, 6);
AddBlock(block);
}
public void AddSignatureBlock(string signature, RichTextModel highlighting = null)
{
var document = new TextDocument(signature);
var richText = highlighting ?? DocumentPrinter.ConvertTextDocumentToRichText(document, new DocumentHighlighter(document, highlightingDefinition)).ToRichTextModel();
var block = new Paragraph();
// HACK: measure width of signature using a TextBlock
// Paragraph sadly does not support TextWrapping.NoWrap
var text = new TextBlock {
FontFamily = GetCodeFont(),
FontSize = DisplaySettingsPanel.CurrentDisplaySettings.SelectedFontSize,
TextAlignment = TextAlignment.Left
};
text.Inlines.AddRange(richText.CreateRuns(document));
text.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.document.MinPageWidth = Math.Min(text.DesiredSize.Width, MainWindow.Instance.ActualWidth);
block.Inlines.AddRange(richText.CreateRuns(document));
block.FontFamily = GetCodeFont();
block.TextAlignment = TextAlignment.Left;
AddBlock(block);
}
public void AddXmlDocumentation(string xmlDocumentation, IEntity declaringEntity, Func<string, IEntity> resolver)
{
if (xmlDocumentation == null)
return;
Debug.WriteLine(xmlDocumentation);
var xml = XElement.Parse("<doc>" + xmlDocumentation + "</doc>");
AddDocumentationElement(new XmlDocumentationElement(xml, declaringEntity, resolver));
}
/// <summary>
/// Gets/Sets the name of the parameter that should be shown.
/// </summary>
public string ParameterName { get; set; }
public void AddDocumentationElement(XmlDocumentationElement element)
{
if (element == null)
throw new ArgumentNullException("element");
if (element.IsTextNode) {
AddText(element.TextContent);
return;
}
switch (element.Name) {
case "b":
AddSpan(new Bold(), element.Children);
break;
case "i":
AddSpan(new Italic(), element.Children);
break;
case "c":
AddSpan(new Span { FontFamily = GetCodeFont() }, element.Children);
break;
case "code":
AddCodeBlock(element.TextContent);
break;
case "example":
if (ShowExample)
AddSection("Example: ", element.Children);
break;
case "exception":
if (ShowExceptions)
AddException(element.ReferencedEntity, element.Children);
break;
case "list":
AddList(element.GetAttribute("type"), element.Children);
break;
//case "note":
// throw new NotImplementedException();
case "para":
AddParagraph(new Paragraph { Margin = new Thickness(0, 5, 0, 5) }, element.Children);
break;
case "param":
if (ShowAllParameters || (ParameterName != null && ParameterName == element.GetAttribute("name")))
AddParam(element.GetAttribute("name"), element.Children);
break;
case "paramref":
AddParamRef(element.GetAttribute("name"));
break;
case "permission":
if (ShowPermissions)
AddPermission(element.ReferencedEntity, element.Children);
break;
case "preliminary":
if (ShowPreliminary)
AddPreliminary(element.Children);
break;
case "remarks":
if (ShowRemarks)
AddSection("Remarks: ", element.Children);
break;
case "returns":
if (ShowReturns)
AddSection("Returns: ", element.Children);
break;
case "see":
AddSee(element);
break;
case "seealso":
if (inlineCollection != null)
AddSee(element);
else if (ShowSeeAlso)
AddSection(new Run("See also: "), () => AddSee(element));
break;
case "summary":
if (ShowSummary)
AddSection("Summary: ", element.Children);
break;
case "threadsafety":
if (ShowThreadSafety)
AddThreadSafety(ParseBool(element.GetAttribute("static")), ParseBool(element.GetAttribute("instance")), element.Children);
break;
case "typeparam":
if (ShowTypeParameters)
AddSection("Type parameter " + element.GetAttribute("name") + ": ", element.Children);
break;
case "typeparamref":
AddText(element.GetAttribute("name"));
break;
case "value":
if (ShowValue)
AddSection("Value: ", element.Children);
break;
case "exclude":
case "filterpriority":
case "overloads":
// ignore children
break;
case "br":
AddLineBreak();
break;
default:
foreach (var child in element.Children)
AddDocumentationElement(child);
break;
}
}
void AddList(string type, IEnumerable<XmlDocumentationElement> items)
{
List list = new List();
AddBlock(list);
list.Margin = new Thickness(0, 5, 0, 5);
if (type == "number")
list.MarkerStyle = TextMarkerStyle.Decimal;
else if (type == "bullet")
list.MarkerStyle = TextMarkerStyle.Disc;
var oldBlockCollection = blockCollection;
try {
foreach (var itemElement in items) {
if (itemElement.Name == "listheader" || itemElement.Name == "item") {
ListItem item = new ListItem();
blockCollection = item.Blocks;
inlineCollection = null;
foreach (var prop in itemElement.Children) {
AddDocumentationElement(prop);
}
FlushAddedText(false);
list.ListItems.Add(item);
}
}
} finally {
blockCollection = oldBlockCollection;
}
}
bool? ParseBool(string input)
{
if (bool.TryParse(input, out bool result))
return result;
else
return null;
}
void AddThreadSafety(bool? staticThreadSafe, bool? instanceThreadSafe, IEnumerable<XmlDocumentationElement> children)
{
AddSection(
new Run("Thread-safety: "),
delegate {
if (staticThreadSafe == true)
AddText("Any public static members of this type are thread safe. ");
else if (staticThreadSafe == false)
AddText("The static members of this type are not thread safe. ");
if (instanceThreadSafe == true)
AddText("Any public instance members of this type are thread safe. ");
else if (instanceThreadSafe == false)
AddText("Any instance members are not guaranteed to be thread safe. ");
foreach (var child in children)
AddDocumentationElement(child);
});
}
void AddException(IEntity referencedEntity, IList<XmlDocumentationElement> children)
{
Span span = new Span();
if (referencedEntity != null)
span.Inlines.Add(ConvertReference(referencedEntity));
else
span.Inlines.Add("Exception");
span.Inlines.Add(": ");
AddSection(span, children);
}
void AddPermission(IEntity referencedEntity, IList<XmlDocumentationElement> children)
{
Span span = new Span();
span.Inlines.Add("Permission");
if (referencedEntity != null) {
span.Inlines.Add(" ");
span.Inlines.Add(ConvertReference(referencedEntity));
}
span.Inlines.Add(": ");
AddSection(span, children);
}
Inline ConvertReference(IEntity referencedEntity)
{
var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity)));
h.Click += (sender, e) => {
MainWindow.Instance.JumpToReference(referencedEntity);
};
return h;
}
void AddParam(string name, IEnumerable<XmlDocumentationElement> children)
{
Span span = new Span();
span.Inlines.Add(new Run(name ?? string.Empty) { FontStyle = FontStyles.Italic });
span.Inlines.Add(": ");
AddSection(span, children);
}
void AddParamRef(string name)
{
if (name != null) {
AddInline(new Run(name) { FontStyle = FontStyles.Italic });
}
}
void AddPreliminary(IEnumerable<XmlDocumentationElement> children)
{
if (children.Any()) {
foreach (var child in children)
AddDocumentationElement(child);
} else {
AddText("[This is preliminary documentation and subject to change.]");
}
}
void AddSee(XmlDocumentationElement element)
{
IEntity referencedEntity = element.ReferencedEntity;
if (referencedEntity != null) {
if (element.Children.Any()) {
Hyperlink link = new Hyperlink();
link.Click += (sender, e) => {
MainWindow.Instance.JumpToReference(referencedEntity);
};
AddSpan(link, element.Children);
} else {
AddInline(ConvertReference(referencedEntity));
}
} else if (element.GetAttribute("langword") != null) {
AddInline(new Run(element.GetAttribute("langword")) { FontFamily = GetCodeFont() });
} else if (element.GetAttribute("href") != null) {
Uri uri;
if (Uri.TryCreate(element.GetAttribute("href"), UriKind.Absolute, out uri)) {
if (element.Children.Any()) {
AddSpan(new Hyperlink { NavigateUri = uri }, element.Children);
} else {
AddInline(new Hyperlink(new Run(element.GetAttribute("href"))) { NavigateUri = uri });
}
}
} else {
// Invalid reference: print the cref value
AddText(element.GetAttribute("cref"));
}
}
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;
}
FontFamily GetCodeFont()
{
return DisplaySettingsPanel.CurrentDisplaySettings.SelectedFont;
}
public void AddInline(Inline inline)
{
FlushAddedText(false);
if (inlineCollection == null) {
var para = new Paragraph();
para.Margin = new Thickness(0, 0, 0, 5);
inlineCollection = para.Inlines;
AddBlock(para);
}
inlineCollection.Add(inline);
ignoreWhitespace = false;
}
void AddSection(string title, IEnumerable<XmlDocumentationElement> children)
{
AddSection(new Run(title), children);
}
void AddSection(Inline title, IEnumerable<XmlDocumentationElement> children)
{
AddSection(
title, delegate {
foreach (var child in children)
AddDocumentationElement(child);
});
}
void AddSection(Inline title, Action addChildren)
{
var section = new Section();
AddBlock(section);
var oldBlockCollection = blockCollection;
try {
blockCollection = section.Blocks;
inlineCollection = null;
if (title != null)
AddInline(new Bold(title));
addChildren();
FlushAddedText(false);
} finally {
blockCollection = oldBlockCollection;
inlineCollection = null;
}
}
void AddParagraph(Paragraph para, IEnumerable<XmlDocumentationElement> children)
{
AddBlock(para);
try {
inlineCollection = para.Inlines;
foreach (var child in children)
AddDocumentationElement(child);
FlushAddedText(false);
} finally {
inlineCollection = null;
}
}
void AddSpan(Span span, IEnumerable<XmlDocumentationElement> children)
{
AddInline(span);
var oldInlineCollection = inlineCollection;
try {
inlineCollection = span.Inlines;
foreach (var child in children)
AddDocumentationElement(child);
FlushAddedText(false);
} finally {
inlineCollection = oldInlineCollection;
}
}
public void AddBlock(Block block)
{
FlushAddedText(true);
blockCollection.Add(block);
}
StringBuilder addedText = new StringBuilder();
bool ignoreWhitespace;
public void AddLineBreak()
{
TrimEndOfAddedText();
addedText.AppendLine();
ignoreWhitespace = true;
}
public void AddText(string textContent)
{
if (string.IsNullOrEmpty(textContent))
return;
for (int i = 0; i < textContent.Length; i++) {
char c = textContent[i];
if (c == '\n' && IsEmptyLineBefore(textContent, i)) {
AddLineBreak(); // empty line -> line break
} else if (char.IsWhiteSpace(c)) {
// any whitespace sequence gets converted to a single space (like HTML)
if (!ignoreWhitespace) {
addedText.Append(' ');
ignoreWhitespace = true;
}
} else {
addedText.Append(c);
ignoreWhitespace = false;
}
}
}
bool IsEmptyLineBefore(string text, int i)
{
// Skip previous whitespace
do {
i--;
} while (i >= 0 && (text[i] == ' ' || text[i] == '\r'));
// Check if previous non-whitespace char is \n
return i >= 0 && text[i] == '\n';
}
void TrimEndOfAddedText()
{
while (addedText.Length > 0 && addedText[addedText.Length - 1] == ' ') {
addedText.Length--;
}
}
void FlushAddedText(bool trim)
{
if (trim) // trim end of current text element
TrimEndOfAddedText();
if (addedText.Length == 0)
return;
string text = addedText.ToString();
addedText.Length = 0;
AddInline(new Run(text));
ignoreWhitespace = trim; // trim start of next text element
}
}
}

137
ILSpy/TextView/XmlDocRenderer.cs

@ -1,137 +0,0 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
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.TextView
{
/// <summary>
/// Renders XML documentation into a WPF <see cref="TextBlock"/>.
/// </summary>
public class XmlDocRenderer
{
readonly 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