mirror of https://github.com/icsharpcode/ILSpy.git
5 changed files with 119 additions and 1197 deletions
@ -1,448 +0,0 @@
@@ -1,448 +0,0 @@
|
||||
// Copyright (c) 2010-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 ICSharpCode.AvalonEdit.Document; |
||||
|
||||
namespace ICSharpCode.ILSpy.TextView |
||||
{ |
||||
/// <summary>
|
||||
/// Read-only implementation of <see cref="IDocument"/>.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public sealed class ReadOnlyDocument : IDocument |
||||
{ |
||||
readonly ITextSource textSource; |
||||
readonly string fileName; |
||||
int[] lines; |
||||
|
||||
static readonly char[] newline = { '\r', '\n' }; |
||||
|
||||
/// <summary>
|
||||
/// Creates a new ReadOnlyDocument from the given text source.
|
||||
/// </summary>
|
||||
public ReadOnlyDocument(ITextSource textSource) |
||||
{ |
||||
if (textSource == null) |
||||
throw new ArgumentNullException("textSource"); |
||||
// ensure that underlying buffer is immutable
|
||||
this.textSource = textSource.CreateSnapshot(); |
||||
List<int> lines = new List<int>(); |
||||
lines.Add(0); |
||||
int offset = 0; |
||||
int textLength = textSource.TextLength; |
||||
while ((offset = textSource.IndexOfAny(newline, offset, textLength - offset)) >= 0) { |
||||
offset++; |
||||
if (textSource.GetCharAt(offset - 1) == '\r' && offset < textLength && textSource.GetCharAt(offset) == '\n') { |
||||
offset++; |
||||
} |
||||
lines.Add(offset); |
||||
} |
||||
this.lines = lines.ToArray(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new ReadOnlyDocument from the given string.
|
||||
/// </summary>
|
||||
public ReadOnlyDocument(string text) |
||||
: this(new StringTextSource(text)) |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new ReadOnlyDocument from the given text source;
|
||||
/// and sets IDocument.FileName to the specified file name.
|
||||
/// </summary>
|
||||
public ReadOnlyDocument(ITextSource textSource, string fileName) |
||||
: this(textSource) |
||||
{ |
||||
this.fileName = fileName; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public IDocumentLine GetLineByNumber(int lineNumber) |
||||
{ |
||||
if (lineNumber < 1 || lineNumber > lines.Length) |
||||
throw new ArgumentOutOfRangeException("lineNumber", lineNumber, "Value must be between 1 and " + lines.Length); |
||||
return new ReadOnlyDocumentLine(this, lineNumber); |
||||
} |
||||
|
||||
sealed class ReadOnlyDocumentLine : IDocumentLine |
||||
{ |
||||
readonly ReadOnlyDocument doc; |
||||
readonly int lineNumber; |
||||
readonly int offset, endOffset; |
||||
|
||||
public ReadOnlyDocumentLine(ReadOnlyDocument doc, int lineNumber) |
||||
{ |
||||
this.doc = doc; |
||||
this.lineNumber = lineNumber; |
||||
this.offset = doc.GetStartOffset(lineNumber); |
||||
this.endOffset = doc.GetEndOffset(lineNumber); |
||||
} |
||||
|
||||
public override int GetHashCode() |
||||
{ |
||||
return doc.GetHashCode() ^ lineNumber; |
||||
} |
||||
|
||||
public override bool Equals(object obj) |
||||
{ |
||||
ReadOnlyDocumentLine other = obj as ReadOnlyDocumentLine; |
||||
return other != null && doc == other.doc && lineNumber == other.lineNumber; |
||||
} |
||||
|
||||
public int Offset { |
||||
get { return offset; } |
||||
} |
||||
|
||||
public int Length { |
||||
get { return endOffset - offset; } |
||||
} |
||||
|
||||
public int EndOffset { |
||||
get { return endOffset; } |
||||
} |
||||
|
||||
public int TotalLength { |
||||
get { |
||||
return doc.GetTotalEndOffset(lineNumber) - offset; |
||||
} |
||||
} |
||||
|
||||
public int DelimiterLength { |
||||
get { |
||||
return doc.GetTotalEndOffset(lineNumber) - endOffset; |
||||
} |
||||
} |
||||
|
||||
public int LineNumber { |
||||
get { return lineNumber; } |
||||
} |
||||
|
||||
public IDocumentLine PreviousLine { |
||||
get { |
||||
if (lineNumber == 1) |
||||
return null; |
||||
else |
||||
return new ReadOnlyDocumentLine(doc, lineNumber - 1); |
||||
} |
||||
} |
||||
|
||||
public IDocumentLine NextLine { |
||||
get { |
||||
if (lineNumber == doc.LineCount) |
||||
return null; |
||||
else |
||||
return new ReadOnlyDocumentLine(doc, lineNumber + 1); |
||||
} |
||||
} |
||||
|
||||
public bool IsDeleted { |
||||
get { return false; } |
||||
} |
||||
} |
||||
|
||||
int GetStartOffset(int lineNumber) |
||||
{ |
||||
return lines[lineNumber - 1]; |
||||
} |
||||
|
||||
int GetTotalEndOffset(int lineNumber) |
||||
{ |
||||
return lineNumber < lines.Length ? lines[lineNumber] : textSource.TextLength; |
||||
} |
||||
|
||||
int GetEndOffset(int lineNumber) |
||||
{ |
||||
if (lineNumber == lines.Length) |
||||
return textSource.TextLength; |
||||
int off = lines[lineNumber] - 1; |
||||
if (off > 0 && textSource.GetCharAt(off - 1) == '\r' && textSource.GetCharAt(off) == '\n') |
||||
off--; |
||||
return off; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public IDocumentLine GetLineByOffset(int offset) |
||||
{ |
||||
return GetLineByNumber(GetLineNumberForOffset(offset)); |
||||
} |
||||
|
||||
int GetLineNumberForOffset(int offset) |
||||
{ |
||||
int r = Array.BinarySearch(lines, offset); |
||||
return r < 0 ? ~r : r + 1; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetOffset(int line, int column) |
||||
{ |
||||
if (line < 1 || line > lines.Length) |
||||
throw new ArgumentOutOfRangeException("line", line, "Value must be between 1 and " + lines.Length); |
||||
int lineStart = GetStartOffset(line); |
||||
if (column <= 1) |
||||
return lineStart; |
||||
int lineEnd = GetEndOffset(line); |
||||
if (column - 1 >= lineEnd - lineStart) |
||||
return lineEnd; |
||||
return lineStart + column - 1; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetOffset(TextLocation location) |
||||
{ |
||||
return GetOffset(location.Line, location.Column); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public TextLocation GetLocation(int offset) |
||||
{ |
||||
if (offset < 0 || offset > textSource.TextLength) |
||||
throw new ArgumentOutOfRangeException("offset", offset, "Value must be between 0 and " + textSource.TextLength); |
||||
int line = GetLineNumberForOffset(offset); |
||||
return new TextLocation(line, offset - GetStartOffset(line) + 1); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public string Text { |
||||
get { return textSource.Text; } |
||||
set { |
||||
throw new NotSupportedException(); |
||||
} |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int LineCount { |
||||
get { return lines.Length; } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextSourceVersion Version { |
||||
get { return textSource.Version; } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int TextLength { |
||||
get { return textSource.TextLength; } |
||||
} |
||||
|
||||
event EventHandler<TextChangeEventArgs> IDocument.TextChanging { add { } remove { } } |
||||
|
||||
event EventHandler<TextChangeEventArgs> IDocument.TextChanged { add { } remove { } } |
||||
|
||||
event EventHandler IDocument.ChangeCompleted { add { } remove { } } |
||||
|
||||
void IDocument.Insert(int offset, string text) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.Insert(int offset, string text, AnchorMovementType defaultAnchorMovementType) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.Remove(int offset, int length) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.Replace(int offset, int length, string newText) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.Insert(int offset, ITextSource text) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.Insert(int offset, ITextSource text, AnchorMovementType defaultAnchorMovementType) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.Replace(int offset, int length, ITextSource newText) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
void IDocument.StartUndoableAction() |
||||
{ |
||||
} |
||||
|
||||
void IDocument.EndUndoableAction() |
||||
{ |
||||
} |
||||
|
||||
IDisposable IDocument.OpenUndoGroup() |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextAnchor CreateAnchor(int offset) |
||||
{ |
||||
return new ReadOnlyDocumentTextAnchor(GetLocation(offset), offset); |
||||
} |
||||
|
||||
sealed class ReadOnlyDocumentTextAnchor : ITextAnchor |
||||
{ |
||||
readonly TextLocation location; |
||||
readonly int offset; |
||||
|
||||
public ReadOnlyDocumentTextAnchor(TextLocation location, int offset) |
||||
{ |
||||
this.location = location; |
||||
this.offset = offset; |
||||
} |
||||
|
||||
public event EventHandler Deleted { add { } remove { } } |
||||
|
||||
public TextLocation Location { |
||||
get { return location; } |
||||
} |
||||
|
||||
public int Offset { |
||||
get { return offset; } |
||||
} |
||||
|
||||
public AnchorMovementType MovementType { get; set; } |
||||
|
||||
public bool SurviveDeletion { get; set; } |
||||
|
||||
public bool IsDeleted { |
||||
get { return false; } |
||||
} |
||||
|
||||
public int Line { |
||||
get { return location.Line; } |
||||
} |
||||
|
||||
public int Column { |
||||
get { return location.Column; } |
||||
} |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextSource CreateSnapshot() |
||||
{ |
||||
return textSource; // textBuffer is immutable
|
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextSource CreateSnapshot(int offset, int length) |
||||
{ |
||||
return textSource.CreateSnapshot(offset, length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public IDocument CreateDocumentSnapshot() |
||||
{ |
||||
return this; // ReadOnlyDocument is immutable
|
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public System.IO.TextReader CreateReader() |
||||
{ |
||||
return textSource.CreateReader(); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public System.IO.TextReader CreateReader(int offset, int length) |
||||
{ |
||||
return textSource.CreateReader(offset, length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteTextTo(System.IO.TextWriter writer) |
||||
{ |
||||
textSource.WriteTextTo(writer); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteTextTo(System.IO.TextWriter writer, int offset, int length) |
||||
{ |
||||
textSource.WriteTextTo(writer, offset, length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public char GetCharAt(int offset) |
||||
{ |
||||
return textSource.GetCharAt(offset); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetText(int offset, int length) |
||||
{ |
||||
return textSource.GetText(offset, length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetText(ISegment segment) |
||||
{ |
||||
return textSource.GetText(segment); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOf(char c, int startIndex, int count) |
||||
{ |
||||
return textSource.IndexOf(c, startIndex, count); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOfAny(char[] anyOf, int startIndex, int count) |
||||
{ |
||||
return textSource.IndexOfAny(anyOf, startIndex, count); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) |
||||
{ |
||||
return textSource.IndexOf(searchText, startIndex, count, comparisonType); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastIndexOf(char c, int startIndex, int count) |
||||
{ |
||||
return textSource.LastIndexOf(c, startIndex, count); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) |
||||
{ |
||||
return textSource.LastIndexOf(searchText, startIndex, count, comparisonType); |
||||
} |
||||
|
||||
object IServiceProvider.GetService(Type serviceType) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Will never be raised on <see cref="ReadOnlyDocument" />.</remarks>
|
||||
public event EventHandler FileNameChanged { add { } remove { } } |
||||
|
||||
/// <inheritdoc/>
|
||||
public string FileName { |
||||
get { return fileName; } |
||||
} |
||||
} |
||||
} |
@ -1,99 +0,0 @@
@@ -1,99 +0,0 @@
|
||||
// Copyright (c) 2014 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.Windows.Documents; |
||||
using ICSharpCode.Decompiler.Output; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.NRefactory.Xml; |
||||
|
||||
namespace ICSharpCode.ILSpy.TextView |
||||
{ |
||||
/// <summary>
|
||||
/// Provides helper methods to create nicely formatted FlowDocuments from NRefactory XmlDoc.
|
||||
/// </summary>
|
||||
public static class XmlDocFormatter |
||||
{ |
||||
public static FlowDocument CreateTooltip(IType type, bool useFullyQualifiedMemberNames = true) |
||||
{ |
||||
var ambience = AmbienceService.GetCurrentAmbience(); |
||||
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.ShowDeclaringType; |
||||
if (useFullyQualifiedMemberNames) |
||||
ambience.ConversionFlags |= ConversionFlags.UseFullyQualifiedEntityNames; |
||||
string header; |
||||
if (type is ITypeDefinition) |
||||
header = ambience.ConvertSymbol((ITypeDefinition)type); |
||||
else |
||||
header = ambience.ConvertType(type); |
||||
|
||||
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList; |
||||
DocumentationUIBuilder b = new DocumentationUIBuilder(ambience); |
||||
b.AddCodeBlock(header, keepLargeMargin: true); |
||||
|
||||
ITypeDefinition entity = type.GetDefinition();/* |
||||
if (entity != null) { |
||||
var documentation = XmlDocumentationElement.Get(entity); |
||||
if (documentation != null) { |
||||
foreach (var child in documentation.Children) { |
||||
b.AddDocumentationElement(child); |
||||
} |
||||
} |
||||
}*/ |
||||
return b.CreateFlowDocument(); |
||||
} |
||||
|
||||
public static FlowDocument CreateTooltip(IEntity entity, bool useFullyQualifiedMemberNames = true) |
||||
{ |
||||
var ambience = AmbienceService.GetCurrentAmbience(); |
||||
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.ShowDeclaringType; |
||||
if (useFullyQualifiedMemberNames) |
||||
ambience.ConversionFlags |= ConversionFlags.UseFullyQualifiedEntityNames; |
||||
string header = ambience.ConvertSymbol(entity); |
||||
var documentation = XmlDocumentationElement.Get(entity); |
||||
|
||||
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList; |
||||
DocumentationUIBuilder b = new DocumentationUIBuilder(ambience); |
||||
b.AddCodeBlock(header, keepLargeMargin: true); |
||||
if (documentation != null) { |
||||
foreach (var child in documentation.Children) { |
||||
b.AddDocumentationElement(child); |
||||
} |
||||
} |
||||
return b.CreateFlowDocument(); |
||||
} |
||||
|
||||
public static FlowDocument CreateTooltip(ISymbol symbol) |
||||
{ |
||||
var ambience = AmbienceService.GetCurrentAmbience(); |
||||
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.ShowDeclaringType; |
||||
string header = ambience.ConvertSymbol(symbol); |
||||
|
||||
if (symbol is IParameter) { |
||||
header = "parameter " + header; |
||||
} else if (symbol is IVariable) { |
||||
header = "local variable " + header; |
||||
} |
||||
|
||||
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList; |
||||
DocumentationUIBuilder b = new DocumentationUIBuilder(ambience); |
||||
b.AddCodeBlock(header, keepLargeMargin: true); |
||||
return b.CreateFlowDocument(); |
||||
} |
||||
} |
||||
} |
@ -1,551 +0,0 @@
@@ -1,551 +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.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
|
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue