You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
11 KiB
368 lines
11 KiB
// 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.Linq;
|
|
using System.Reflection.Metadata;
|
|
using System.Text;
|
|
using System.Windows;
|
|
|
|
using ICSharpCode.AvalonEdit.Document;
|
|
using ICSharpCode.AvalonEdit.Folding;
|
|
using ICSharpCode.AvalonEdit.Highlighting;
|
|
using ICSharpCode.AvalonEdit.Rendering;
|
|
using ICSharpCode.Decompiler.Metadata;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
|
|
using TextLocation = ICSharpCode.Decompiler.CSharp.Syntax.TextLocation;
|
|
|
|
namespace ICSharpCode.ILSpy.TextView
|
|
{
|
|
/// <summary>
|
|
/// A text segment that references some object. Used for hyperlinks in the editor.
|
|
/// </summary>
|
|
public sealed class ReferenceSegment : TextSegment
|
|
{
|
|
public object Reference;
|
|
public bool IsLocal;
|
|
public bool IsDefinition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores the positions of the definitions that were written to the text output.
|
|
/// </summary>
|
|
sealed class DefinitionLookup
|
|
{
|
|
internal Dictionary<object, int> definitions = new Dictionary<object, int>();
|
|
|
|
public int GetDefinitionPosition(object definition)
|
|
{
|
|
int val;
|
|
if (definitions.TryGetValue(definition, out val))
|
|
return val;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
public void AddDefinition(object definition, int offset)
|
|
{
|
|
definitions[definition] = offset;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text output implementation for AvalonEdit.
|
|
/// </summary>
|
|
public sealed class AvalonEditTextOutput : ISmartTextOutput
|
|
{
|
|
int lastLineStart = 0;
|
|
int lineNumber = 1;
|
|
readonly StringBuilder b = new StringBuilder();
|
|
|
|
/// <summary>Current indentation level</summary>
|
|
int indent;
|
|
/// <summary>Whether indentation should be inserted on the next write</summary>
|
|
bool needsIndent;
|
|
|
|
public string IndentationString { get; set; } = "\t";
|
|
|
|
internal bool IgnoreNewLineAndIndent { get; set; }
|
|
|
|
public string Title { get; set; } = Properties.Resources.NewTab;
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="Uri"/> that is displayed by this view.
|
|
/// Used to identify the AboutPage and other views built into ILSpy in the navigation history.
|
|
/// </summary>
|
|
public Uri Address { get; set; }
|
|
|
|
internal readonly List<VisualLineElementGenerator> elementGenerators = new List<VisualLineElementGenerator>();
|
|
|
|
/// <summary>List of all references that were written to the output</summary>
|
|
TextSegmentCollection<ReferenceSegment> references = new TextSegmentCollection<ReferenceSegment>();
|
|
|
|
/// <summary>Stack of the fold markers that are open but not closed yet</summary>
|
|
Stack<(NewFolding, int startLine)> openFoldings = new Stack<(NewFolding, int startLine)>();
|
|
|
|
/// <summary>List of all foldings that were written to the output</summary>
|
|
internal readonly List<NewFolding> Foldings = new List<NewFolding>();
|
|
|
|
internal readonly DefinitionLookup DefinitionLookup = new DefinitionLookup();
|
|
|
|
internal bool EnableHyperlinks { get; set; }
|
|
|
|
/// <summary>Embedded UIElements, see <see cref="UIElementGenerator"/>.</summary>
|
|
internal readonly List<KeyValuePair<int, Lazy<UIElement>>> UIElements = new List<KeyValuePair<int, Lazy<UIElement>>>();
|
|
|
|
public RichTextModel HighlightingModel { get; } = new RichTextModel();
|
|
|
|
public AvalonEditTextOutput()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the list of references (hyperlinks).
|
|
/// </summary>
|
|
internal TextSegmentCollection<ReferenceSegment> References {
|
|
get { return references; }
|
|
}
|
|
|
|
public void AddVisualLineElementGenerator(VisualLineElementGenerator elementGenerator)
|
|
{
|
|
elementGenerators.Add(elementGenerator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls the maximum length of the text.
|
|
/// When this length is exceeded, an <see cref="OutputLengthExceededException"/> will be thrown,
|
|
/// thus aborting the decompilation.
|
|
/// </summary>
|
|
public int LengthLimit = int.MaxValue;
|
|
|
|
public int TextLength {
|
|
get { return b.Length; }
|
|
}
|
|
|
|
public TextLocation Location {
|
|
get {
|
|
return new TextLocation(lineNumber, b.Length - lastLineStart + 1 + (needsIndent ? indent : 0));
|
|
}
|
|
}
|
|
|
|
#region Text Document
|
|
TextDocument textDocument;
|
|
|
|
/// <summary>
|
|
/// Prepares the TextDocument.
|
|
/// This method may be called by the background thread writing to the output.
|
|
/// Once the document is prepared, it can no longer be written to.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Calling this method on the background thread ensures the TextDocument's line tokenization
|
|
/// runs in the background and does not block the GUI.
|
|
/// </remarks>
|
|
public void PrepareDocument()
|
|
{
|
|
if (textDocument == null)
|
|
{
|
|
textDocument = new TextDocument(b.ToString());
|
|
textDocument.SetOwnerThread(null); // release ownership
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the TextDocument.
|
|
/// Once the document is retrieved, it can no longer be written to.
|
|
/// </summary>
|
|
public TextDocument GetDocument()
|
|
{
|
|
PrepareDocument();
|
|
textDocument.SetOwnerThread(System.Threading.Thread.CurrentThread); // acquire ownership
|
|
return textDocument;
|
|
}
|
|
#endregion
|
|
|
|
public void Indent()
|
|
{
|
|
if (IgnoreNewLineAndIndent)
|
|
return;
|
|
indent++;
|
|
}
|
|
|
|
public void Unindent()
|
|
{
|
|
if (IgnoreNewLineAndIndent)
|
|
return;
|
|
indent--;
|
|
}
|
|
|
|
void WriteIndent()
|
|
{
|
|
if (IgnoreNewLineAndIndent)
|
|
return;
|
|
Debug.Assert(textDocument == null);
|
|
if (needsIndent)
|
|
{
|
|
needsIndent = false;
|
|
for (int i = 0; i < indent; i++)
|
|
{
|
|
b.Append(IndentationString);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Write(char ch)
|
|
{
|
|
WriteIndent();
|
|
b.Append(ch);
|
|
}
|
|
|
|
public void Write(string text)
|
|
{
|
|
WriteIndent();
|
|
b.Append(text);
|
|
}
|
|
|
|
public void WriteLine()
|
|
{
|
|
Debug.Assert(textDocument == null);
|
|
if (IgnoreNewLineAndIndent)
|
|
{
|
|
b.Append(' ');
|
|
}
|
|
else
|
|
{
|
|
b.AppendLine();
|
|
needsIndent = true;
|
|
lastLineStart = b.Length;
|
|
lineNumber++;
|
|
}
|
|
if (this.TextLength > LengthLimit)
|
|
{
|
|
throw new OutputLengthExceededException();
|
|
}
|
|
}
|
|
|
|
public void WriteReference(Decompiler.Disassembler.OpCodeInfo opCode, bool omitSuffix = false)
|
|
{
|
|
WriteIndent();
|
|
int start = this.TextLength;
|
|
if (omitSuffix)
|
|
{
|
|
int lastDot = opCode.Name.LastIndexOf('.');
|
|
if (lastDot > 0)
|
|
{
|
|
b.Append(opCode.Name.Remove(lastDot + 1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
b.Append(opCode.Name);
|
|
}
|
|
int end = this.TextLength - 1;
|
|
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = opCode });
|
|
}
|
|
|
|
public void WriteReference(MetadataFile metadata, Handle handle, string text, string protocol = "decompile", bool isDefinition = false)
|
|
{
|
|
WriteIndent();
|
|
int start = this.TextLength;
|
|
b.Append(text);
|
|
int end = this.TextLength;
|
|
if (isDefinition)
|
|
{
|
|
this.DefinitionLookup.AddDefinition((metadata, handle), this.TextLength);
|
|
}
|
|
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = new EntityReference(metadata, handle, protocol), IsDefinition = isDefinition });
|
|
}
|
|
|
|
public void WriteReference(IType type, string text, bool isDefinition = false)
|
|
{
|
|
WriteIndent();
|
|
int start = this.TextLength;
|
|
b.Append(text);
|
|
int end = this.TextLength;
|
|
if (isDefinition)
|
|
{
|
|
this.DefinitionLookup.AddDefinition(type, this.TextLength);
|
|
}
|
|
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = type, IsDefinition = isDefinition });
|
|
}
|
|
|
|
public void WriteReference(IMember member, string text, bool isDefinition = false)
|
|
{
|
|
WriteIndent();
|
|
int start = this.TextLength;
|
|
b.Append(text);
|
|
int end = this.TextLength;
|
|
if (isDefinition)
|
|
{
|
|
this.DefinitionLookup.AddDefinition(member, this.TextLength);
|
|
}
|
|
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = member, IsDefinition = isDefinition });
|
|
}
|
|
|
|
public void WriteLocalReference(string text, object reference, bool isDefinition = false)
|
|
{
|
|
WriteIndent();
|
|
int start = this.TextLength;
|
|
b.Append(text);
|
|
int end = this.TextLength;
|
|
if (isDefinition)
|
|
{
|
|
this.DefinitionLookup.AddDefinition(reference, this.TextLength);
|
|
}
|
|
references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = reference, IsLocal = true, IsDefinition = isDefinition });
|
|
}
|
|
|
|
public void MarkFoldStart(string collapsedText = "...", bool defaultCollapsed = false, bool isDefinition = false)
|
|
{
|
|
WriteIndent();
|
|
openFoldings.Push((
|
|
new NewFolding {
|
|
StartOffset = this.TextLength,
|
|
Name = collapsedText,
|
|
DefaultClosed = defaultCollapsed,
|
|
IsDefinition = isDefinition,
|
|
}, lineNumber));
|
|
}
|
|
|
|
public void MarkFoldEnd()
|
|
{
|
|
var (f, startLine) = openFoldings.Pop();
|
|
f.EndOffset = this.TextLength;
|
|
if (startLine != lineNumber)
|
|
this.Foldings.Add(f);
|
|
}
|
|
|
|
public void AddUIElement(Func<UIElement> element)
|
|
{
|
|
if (element != null)
|
|
{
|
|
if (this.UIElements.Count > 0 && this.UIElements.Last().Key == this.TextLength)
|
|
throw new InvalidOperationException("Only one UIElement is allowed for each position in the document");
|
|
this.UIElements.Add(new KeyValuePair<int, Lazy<UIElement>>(this.TextLength, new Lazy<UIElement>(element)));
|
|
}
|
|
}
|
|
|
|
readonly Stack<HighlightingColor> colorStack = new Stack<HighlightingColor>();
|
|
HighlightingColor currentColor = new HighlightingColor();
|
|
int currentColorBegin = -1;
|
|
|
|
public void BeginSpan(HighlightingColor highlightingColor)
|
|
{
|
|
WriteIndent();
|
|
if (currentColorBegin > -1)
|
|
HighlightingModel.SetHighlighting(currentColorBegin, b.Length - currentColorBegin, currentColor);
|
|
colorStack.Push(currentColor);
|
|
currentColor = currentColor.Clone();
|
|
currentColorBegin = b.Length;
|
|
currentColor.MergeWith(highlightingColor);
|
|
currentColor.Freeze();
|
|
}
|
|
|
|
public void EndSpan()
|
|
{
|
|
HighlightingModel.SetHighlighting(currentColorBegin, b.Length - currentColorBegin, currentColor);
|
|
currentColor = colorStack.Pop();
|
|
currentColorBegin = b.Length;
|
|
}
|
|
}
|
|
}
|