Browse Source

Merge pull request #1654 from icsharpcode/fancy-tooltips

Rich Text Tooltips
pull/1790/head
Siegfried Pammer 6 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. 122
      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. 2
      ILSpy/ISmartTextOutput.cs
  11. 66
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs
  12. 14
      ILSpy/Languages/CSharpLanguage.cs
  13. 10
      ILSpy/Languages/Language.cs
  14. 4
      ILSpy/MainWindow.xaml.cs
  15. 247
      ILSpy/TextView/DecompilerTextView.cs
  16. 2
      ILSpy/TextView/DecompilerTextView.xaml
  17. 551
      ILSpy/TextView/DocumentationUIBuilder.cs
  18. 137
      ILSpy/TextView/XmlDocRenderer.cs

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

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

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

@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
var target = new TextWriterTokenWriter(writer) { IndentationString = indentation };
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)
{
@ -79,6 +84,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -79,6 +84,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public interface ILocatable
{
TextLocation Location { get; }
int Length { get; }
}
public abstract class DecoratingTokenWriter : TokenWriter

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

@ -30,22 +30,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -30,22 +30,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public class TextWriterTokenWriter : TokenWriter, ILocatable
{
readonly TextWriter textWriter;
int indentation;
bool needsIndent = true;
bool isAtStartOfLine = true;
int line, column;
public int Indentation {
get { return this.indentation; }
set { this.indentation = value; }
}
public int Indentation { get; set; }
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 int Length { get; private set; }
public TextWriterTokenWriter(TextWriter textWriter)
{
if (textWriter == null)
@ -55,73 +53,80 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -55,73 +53,80 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
this.line = 1;
this.column = 1;
}
public override void WriteIdentifier(Identifier identifier)
{
WriteIndentation();
if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) {
textWriter.Write('@');
column++;
Length++;
}
string name = EscapeIdentifier(identifier.Name);
textWriter.Write(name);
column += name.Length;
Length += name.Length;
isAtStartOfLine = false;
}
public override void WriteKeyword(Role role, string keyword)
{
WriteIndentation();
column += keyword.Length;
Length += keyword.Length;
textWriter.Write(keyword);
isAtStartOfLine = false;
}
public override void WriteToken(Role role, string token)
{
WriteIndentation();
column += token.Length;
Length += token.Length;
textWriter.Write(token);
isAtStartOfLine = false;
}
public override void Space()
{
WriteIndentation();
column++;
Length++;
textWriter.Write(' ');
}
protected void WriteIndentation()
{
if (needsIndent) {
needsIndent = false;
for (int i = 0; i < indentation; i++) {
for (int i = 0; i < Indentation; i++) {
textWriter.Write(this.IndentationString);
}
column += indentation * IndentationString.Length;
column += Indentation * IndentationString.Length;
Length += Indentation * IndentationString.Length;
}
}
public override void NewLine()
{
textWriter.WriteLine();
column = 1;
line++;
Length += textWriter.NewLine.Length;
needsIndent = true;
isAtStartOfLine = true;
}
public override void Indent()
{
indentation++;
Indentation++;
}
public override void Unindent()
{
indentation--;
Indentation--;
}
public override void WriteComment(CommentType commentType, string content)
{
WriteIndentation();
@ -129,6 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -129,6 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case CommentType.SingleLine:
textWriter.Write("//");
textWriter.WriteLine(content);
Length += 2 + content.Length + textWriter.NewLine.Length;
column = 1;
line++;
needsIndent = true;
@ -138,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -138,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
textWriter.Write("/*");
textWriter.Write(content);
textWriter.Write("*/");
Length += 4 + content.Length;
column += 2;
UpdateEndLocation(content, ref line, ref column);
column += 2;
@ -146,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -146,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case CommentType.Documentation:
textWriter.Write("///");
textWriter.WriteLine(content);
Length += 3 + content.Length + textWriter.NewLine.Length;
column = 1;
line++;
needsIndent = true;
@ -155,6 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -155,6 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
textWriter.Write("/**");
textWriter.Write(content);
textWriter.Write("*/");
Length += 5 + content.Length;
column += 3;
UpdateEndLocation(content, ref line, ref column);
column += 2;
@ -163,10 +172,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -163,10 +172,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
default:
textWriter.Write(content);
column += content.Length;
Length += content.Length;
break;
}
}
static void UpdateEndLocation(string content, ref int line, ref int column)
{
if (string.IsNullOrEmpty(content))
@ -186,7 +196,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -186,7 +196,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
column++;
}
}
public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument)
{
// pre-processor directive must start on its own line
@ -197,14 +207,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -197,14 +207,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
string directive = type.ToString().ToLowerInvariant();
textWriter.Write(directive);
column += 1 + directive.Length;
Length += 1 + directive.Length;
if (!string.IsNullOrEmpty(argument)) {
textWriter.Write(' ');
textWriter.Write(argument);
column += 1 + argument.Length;
Length += 1 + argument.Length;
}
NewLine();
}
public static string PrintPrimitiveValue(object value)
{
TextWriter writer = new StringWriter();
@ -212,48 +224,55 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -212,48 +224,55 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
tokenWriter.WritePrimitiveValue(value);
return writer.ToString();
}
public override void WritePrimitiveValue(object value, string literalValue = null)
{
if (literalValue != null) {
textWriter.Write(literalValue);
column += literalValue.Length;
Length += literalValue.Length;
return;
}
if (value == null) {
// usually NullReferenceExpression should be used for this, but we'll handle it anyways
textWriter.Write("null");
column += 4;
Length += 4;
return;
}
if (value is bool) {
if ((bool)value) {
textWriter.Write("true");
column += 4;
Length += 4;
} else {
textWriter.Write("false");
column += 5;
Length += 5;
}
return;
}
if (value is string) {
string tmp = ConvertString(value.ToString());
column += tmp.Length + 2;
Length += tmp.Length + 2;
textWriter.Write('"');
textWriter.Write(tmp);
textWriter.Write('"');
} else if (value is char) {
string tmp = ConvertCharLiteral((char)value);
column += tmp.Length + 2;
Length += tmp.Length + 2;
textWriter.Write('\'');
textWriter.Write(tmp);
textWriter.Write('\'');
} else if (value is decimal) {
string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m";
column += str.Length;
Length += str.Length;
textWriter.Write(str);
} else if (value is float) {
float f = (float)value;
@ -262,16 +281,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -262,16 +281,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// but we still support writing these to make life easier for code generators.
textWriter.Write("float");
column += 5;
Length += 5;
WriteToken(Roles.Dot, ".");
if (float.IsPositiveInfinity(f)) {
textWriter.Write("PositiveInfinity");
column += "PositiveInfinity".Length;
Length += "PositiveInfinity".Length;
} else if (float.IsNegativeInfinity(f)) {
textWriter.Write("NegativeInfinity");
column += "NegativeInfinity".Length;
Length += "NegativeInfinity".Length;
} else {
textWriter.Write("NaN");
column += 3;
Length += 3;
}
return;
}
@ -281,9 +304,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -281,9 +304,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// the special case here than to do it in all code generators)
textWriter.Write("-");
column++;
Length++;
}
var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f";
column += str.Length;
Length += str.Length;
textWriter.Write(str);
} else if (value is double) {
double f = (double)value;
@ -292,16 +317,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -292,16 +317,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// but we still support writing these to make life easier for code generators.
textWriter.Write("double");
column += 6;
Length += 6;
WriteToken(Roles.Dot, ".");
if (double.IsPositiveInfinity(f)) {
textWriter.Write("PositiveInfinity");
column += "PositiveInfinity".Length;
Length += "PositiveInfinity".Length;
} else if (double.IsNegativeInfinity(f)) {
textWriter.Write("NegativeInfinity");
column += "NegativeInfinity".Length;
Length += "NegativeInfinity".Length;
} else {
textWriter.Write("NaN");
column += 3;
Length += 3;
}
return;
}
@ -310,20 +339,22 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -310,20 +339,22 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// (again, not a primitive expression, but it's better to handle
// the special case here than to do it in all code generators)
textWriter.Write("-");
Length++;
}
string number = f.ToString("R", NumberFormatInfo.InvariantInfo);
if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) {
number += ".0";
}
textWriter.Write(number);
Length += number.Length;
} else if (value is IFormattable) {
StringBuilder b = new StringBuilder ();
// if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) {
// b.Append("0x");
// b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo));
// } else {
b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo));
// }
StringBuilder b = new StringBuilder();
// if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) {
// b.Append("0x");
// b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo));
// } else {
b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo));
// }
if (value is uint || value is ulong) {
b.Append("u");
}
@ -332,12 +363,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -332,12 +363,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
textWriter.Write(b.ToString());
column += b.Length;
Length += b.Length;
} else {
textWriter.Write(value.ToString());
column += value.ToString().Length;
int length = value.ToString().Length;
column += length;
Length += length;
}
}
/// <summary>
/// Gets the escape sequence for the specified character within a char literal.
/// Does not include the single quotes surrounding the char literal.
@ -349,7 +383,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -349,7 +383,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
return ConvertChar(ch) ?? ch.ToString();
}
/// <summary>
/// Gets the escape sequence for the specified character.
/// </summary>
@ -412,7 +446,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -412,7 +446,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// </summary>
public static string ConvertString(string str)
{
StringBuilder sb = new StringBuilder ();
StringBuilder sb = new StringBuilder();
foreach (char ch in str) {
string s = ch == '"' ? "\\\"" : ConvertChar(ch);
if (s != null) sb.Append(s);
@ -477,17 +511,19 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -477,17 +511,19 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return true;
}
}
public override void WritePrimitiveType(string type)
{
textWriter.Write(type);
column += type.Length;
Length += type.Length;
if (type == "new") {
textWriter.Write("()");
column += 2;
Length += 2;
}
}
public override void StartNode(AstNode node)
{
// Write out the indentation, so that overrides of this method
@ -495,7 +531,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -495,7 +531,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// in the output.
WriteIndentation();
}
public override void EndNode(AstNode node)
{
}

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

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

240
ICSharpCode.Decompiler/Documentation/XmlDocumentationElement.cs

@ -0,0 +1,240 @@ @@ -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 @@ @@ -63,6 +63,7 @@
<Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="Documentation\XmlDocumentationElement.cs" />
<Compile Include="Solution\ProjectId.cs" />
<Compile Include="Solution\ProjectItem.cs" />
<Compile Include="Solution\SolutionCreator.cs" />

9
ICSharpCode.Decompiler/NRExtensions.cs

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler
@ -86,5 +87,13 @@ namespace ICSharpCode.Decompiler @@ -86,5 +87,13 @@ namespace ICSharpCode.Decompiler
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 @@ @@ -18,6 +18,8 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.ILSpy.Options;
namespace ICSharpCode.ILSpy
@ -161,5 +163,43 @@ namespace ICSharpCode.ILSpy @@ -161,5 +163,43 @@ namespace ICSharpCode.ILSpy
}
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 @@ @@ -39,6 +39,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.IO.Compression" />
@ -225,7 +226,7 @@ @@ -225,7 +226,7 @@
<Compile Include="TextView\EditorCommands.cs" />
<Compile Include="TextView\FoldingCommands.cs" />
<Compile Include="Commands\SaveCodeContextMenuEntry.cs" />
<Compile Include="TextView\XmlDocRenderer.cs" />
<Compile Include="TextView\DocumentationUIBuilder.cs" />
<Compile Include="Analyzers\AnalyzeCommand.cs" />
<Compile Include="Analyzers\Builtin\FieldAccessAnalyzer.cs" />
<Compile Include="Analyzers\TreeNodes\AnalyzedFieldTreeNode.cs" />

2
ILSpy/ISmartTextOutput.cs

@ -39,7 +39,7 @@ namespace ICSharpCode.ILSpy @@ -39,7 +39,7 @@ namespace ICSharpCode.ILSpy
void BeginSpan(HighlightingColor highlightingColor);
void EndSpan();
}
public static class SmartTextOutputExtensions
{
/// <summary>

66
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -29,8 +29,6 @@ namespace ICSharpCode.ILSpy @@ -29,8 +29,6 @@ namespace ICSharpCode.ILSpy
{
class CSharpHighlightingTokenWriter : DecoratingTokenWriter
{
ISmartTextOutput textOutput;
HighlightingColor visibilityKeywordsColor;
HighlightingColor namespaceKeywordsColor;
HighlightingColor structureKeywordsColor;
@ -65,12 +63,15 @@ namespace ICSharpCode.ILSpy @@ -65,12 +63,15 @@ namespace ICSharpCode.ILSpy
HighlightingColor trueKeywordColor;
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#");
//this.defaultTextColor = ???;
this.locatable = locatable;
this.textOutput = textOutput;
this.visibilityKeywordsColor = highlighting.GetNamedColor("Visibility");
this.namespaceKeywordsColor = highlighting.GetNamedColor("NamespaceKeywords");
@ -264,11 +265,11 @@ namespace ICSharpCode.ILSpy @@ -264,11 +265,11 @@ namespace ICSharpCode.ILSpy
if (nodeStack.PeekOrDefault() is AttributeSection)
color = attributeKeywordsColor;
if (color != null) {
textOutput.BeginSpan(color);
BeginSpan(color);
}
base.WriteKeyword(role, keyword);
if (color != null) {
textOutput.EndSpan();
EndSpan();
}
}
@ -307,11 +308,11 @@ namespace ICSharpCode.ILSpy @@ -307,11 +308,11 @@ namespace ICSharpCode.ILSpy
break;
}
if (color != null) {
textOutput.BeginSpan(color);
BeginSpan(color);
}
base.WritePrimitiveType(type);
if (color != null) {
textOutput.EndSpan();
EndSpan();
}
}
@ -383,11 +384,11 @@ namespace ICSharpCode.ILSpy @@ -383,11 +384,11 @@ namespace ICSharpCode.ILSpy
break;
}
if (color != null) {
textOutput.BeginSpan(color);
BeginSpan(color);
}
base.WriteIdentifier(identifier);
if (color != null) {
textOutput.EndSpan();
EndSpan();
}
}
@ -401,11 +402,11 @@ namespace ICSharpCode.ILSpy @@ -401,11 +402,11 @@ namespace ICSharpCode.ILSpy
color = trueKeywordColor;
}
if (color != null) {
textOutput.BeginSpan(color);
BeginSpan(color);
}
base.WritePrimitiveValue(value, literalValue);
if (color != null) {
textOutput.EndSpan();
EndSpan();
}
}
@ -425,6 +426,9 @@ namespace ICSharpCode.ILSpy @@ -425,6 +426,9 @@ namespace ICSharpCode.ILSpy
ISymbol GetCurrentMemberReference()
{
if (nodeStack == null || nodeStack.Count == 0)
return null;
AstNode node = nodeStack.Peek();
var symbol = node.GetSymbol();
if (symbol == null && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression) {
@ -441,7 +445,7 @@ namespace ICSharpCode.ILSpy @@ -441,7 +445,7 @@ namespace ICSharpCode.ILSpy
return symbol;
}
Stack<AstNode> nodeStack = new Stack<AstNode>();
readonly Stack<AstNode> nodeStack = new Stack<AstNode>();
public override void StartNode(AstNode node)
{
@ -454,5 +458,39 @@ namespace ICSharpCode.ILSpy @@ -454,5 +458,39 @@ namespace ICSharpCode.ILSpy
base.EndNode(node);
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; @@ -26,7 +26,9 @@ using System.Reflection.Metadata;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
@ -485,7 +487,7 @@ namespace ICSharpCode.ILSpy @@ -485,7 +487,7 @@ namespace ICSharpCode.ILSpy
// HACK : UnknownType is not supported by CSharpAmbience.
} else if (type.Kind == TypeKind.Unknown) {
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 {
return ambience.ConvertType(type);
}
@ -625,10 +627,14 @@ namespace ICSharpCode.ILSpy @@ -625,10 +627,14 @@ namespace ICSharpCode.ILSpy
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);
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)

10
ILSpy/Languages/Language.cs

@ -21,6 +21,7 @@ using System.Collections.Generic; @@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
@ -349,6 +350,15 @@ namespace ICSharpCode.ILSpy @@ -349,6 +350,15 @@ namespace ICSharpCode.ILSpy
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)
{
if (field == null)

4
ILSpy/MainWindow.xaml.cs

@ -373,7 +373,7 @@ namespace ICSharpCode.ILSpy @@ -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;
IMemberReference memberRef = null;
@ -397,7 +397,7 @@ namespace ICSharpCode.ILSpy @@ -397,7 +397,7 @@ namespace ICSharpCode.ILSpy
return null;
}
private bool CanResolveTypeInPEFile(PEFile module, ITypeReference typeRef, out EntityHandle typeHandle)
static bool CanResolveTypeInPEFile(PEFile module, ITypeReference typeRef, out EntityHandle typeHandle)
{
switch (typeRef) {
case GetPotentiallyNestedClassTypeReference topLevelType:

247
ILSpy/TextView/DecompilerTextView.cs

@ -27,6 +27,7 @@ using System.Threading; @@ -27,6 +27,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
@ -44,8 +45,10 @@ using ICSharpCode.AvalonEdit.Rendering; @@ -44,8 +45,10 @@ using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AvalonEdit;
using ICSharpCode.ILSpy.Options;
@ -63,7 +66,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -63,7 +66,7 @@ namespace ICSharpCode.ILSpy.TextView
{
readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator;
List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
readonly List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
RichTextColorizer activeRichTextColorizer;
BracketHighlightRenderer bracketHighlightRenderer;
FoldingManager foldingManager;
@ -112,6 +115,8 @@ namespace ICSharpCode.ILSpy.TextView @@ -112,6 +115,8 @@ namespace ICSharpCode.ILSpy.TextView
textEditor.TextArea.PreviewMouseDown += TextAreaMouseDown;
textEditor.TextArea.PreviewMouseUp += TextAreaMouseUp;
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.FontSizeProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFontSize") });
textEditor.SetBinding(TextEditor.WordWrapProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("EnableWordWrap") });
@ -166,20 +171,18 @@ namespace ICSharpCode.ILSpy.TextView @@ -166,20 +171,18 @@ namespace ICSharpCode.ILSpy.TextView
}
}
}
#endregion
#region Tooltip support
ToolTip tooltip;
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
if (tooltip != null)
tooltip.IsOpen = false;
}
ToolTip toolTip;
Popup popupToolTip;
void TextViewMouseHover(object sender, MouseEventArgs e)
{
if (!TryCloseExistingPopup(false)) {
return;
}
TextViewPosition? position = GetPositionFromMousePosition();
if (position == null)
return;
@ -190,35 +193,166 @@ namespace ICSharpCode.ILSpy.TextView @@ -190,35 +193,166 @@ namespace ICSharpCode.ILSpy.TextView
if (seg == null)
return;
object content = GenerateTooltip(seg);
if (tooltip != null)
tooltip.IsOpen = false;
if (content != null)
tooltip = new ToolTip() { Content = content, IsOpen = true };
if (content != null) {
popupToolTip = content as Popup;
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)
{
if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) {
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);
if (documentation != null) {
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText($"{code.Name} (0x{code.Code:x}) - ");
renderer.AddXmlDocumentation(documentation);
return renderer.CreateTextBlock();
renderer.AddXmlDocumentation(documentation, null, null);
}
}
return $"{code.Name} (0x{code.Code:x})";
return new FlowDocumentTooltip(renderer.CreateDocument());
} 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) {
var typeSystem = new DecompilerTypeSystem(unresolvedEntity.Item1, unresolvedEntity.Item1.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached);
try {
IEntity resolved = typeSystem.MainModule.ResolveEntity(unresolvedEntity.Item2);
if (resolved == null)
return null;
return CreateTextBlockForEntity(resolved);
return new FlowDocumentTooltip(CreateTooltipForEntity(resolved));
} catch (BadImageFormatException) {
return null;
}
@ -226,10 +360,12 @@ namespace ICSharpCode.ILSpy.TextView @@ -226,10 +360,12 @@ namespace ICSharpCode.ILSpy.TextView
return null;
}
static TextBlock CreateTextBlockForEntity(IEntity resolved)
static FlowDocument CreateTooltipForEntity(IEntity resolved)
{
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(resolved));
Language currentLanguage = MainWindow.Instance.CurrentLanguage;
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting);
RichText richText = currentLanguage.GetRichTextTooltip(resolved);
renderer.AddSignatureBlock(richText.Text, richText.ToRichTextModel());
try {
if (resolved.ParentModule == null || resolved.ParentModule.PEFile == null)
return null;
@ -237,14 +373,65 @@ namespace ICSharpCode.ILSpy.TextView @@ -237,14 +373,65 @@ namespace ICSharpCode.ILSpy.TextView
if (docProvider != null) {
string documentation = docProvider.GetDocumentation(resolved.GetIdString());
if (documentation != null) {
renderer.AppendText(Environment.NewLine);
renderer.AddXmlDocumentation(documentation);
renderer.AddXmlDocumentation(documentation, resolved, ResolveReference);
}
}
} catch (XmlException) {
// 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
@ -336,7 +523,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -336,7 +523,7 @@ namespace ICSharpCode.ILSpy.TextView
return tcs.Task;
}
void cancelButton_Click(object sender, RoutedEventArgs e)
void CancelButton_Click(object sender, RoutedEventArgs e)
{
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();

2
ILSpy/TextView/DecompilerTextView.xaml

@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock FontSize="14pt" Text="{x:Static properties:Resources.Decompiling}"/>
<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>
</Border>
</Grid>

551
ILSpy/TextView/DocumentationUIBuilder.cs

@ -0,0 +1,551 @@ @@ -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 @@ @@ -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