98 changed files with 2352 additions and 949 deletions
@ -0,0 +1,62 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ICSharpCode.NRefactory.CSharp; |
||||||
|
using ICSharpCode.NRefactory.CSharp.Refactoring; |
||||||
|
using ICSharpCode.SharpDevelop.Refactoring; |
||||||
|
using CSharpBinding.Parser; |
||||||
|
|
||||||
|
namespace CSharpBinding.Refactoring |
||||||
|
{ |
||||||
|
[ContextAction("Convert interface to abstract class", Description = "Converts an interface to a class with abstract members.")] |
||||||
|
public class ConvertInterfaceToAbstractClassContextAction : ContextAction |
||||||
|
{ |
||||||
|
public override async Task<bool> IsAvailableAsync(EditorRefactoringContext context, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
SyntaxTree st = await context.GetSyntaxTreeAsync().ConfigureAwait(false); |
||||||
|
Identifier identifier = (Identifier) st.GetNodeAt(context.CaretLocation, node => node.Role == Roles.Identifier); |
||||||
|
if (identifier == null) |
||||||
|
return false; |
||||||
|
TypeDeclaration typeDeclaration = identifier.Parent as TypeDeclaration; |
||||||
|
return (typeDeclaration != null) && (typeDeclaration.ClassType == ClassType.Interface); |
||||||
|
} |
||||||
|
|
||||||
|
public override void Execute(EditorRefactoringContext context) |
||||||
|
{ |
||||||
|
CSharpFullParseInformation parseInformation = context.GetParseInformation() as CSharpFullParseInformation; |
||||||
|
if (parseInformation != null) { |
||||||
|
SyntaxTree st = parseInformation.SyntaxTree; |
||||||
|
Identifier identifier = (Identifier) st.GetNodeAt(context.CaretLocation, node => node.Role == Roles.Identifier); |
||||||
|
if (identifier == null) |
||||||
|
return; |
||||||
|
TypeDeclaration interfaceTypeDeclaration = identifier.Parent as TypeDeclaration; |
||||||
|
if (interfaceTypeDeclaration != null) { |
||||||
|
// Generate abstract class from interface and abstract members from interface members
|
||||||
|
TypeDeclaration abstractClassTypeNode = (TypeDeclaration) interfaceTypeDeclaration.Clone(); |
||||||
|
abstractClassTypeNode.ClassType = ClassType.Class; |
||||||
|
abstractClassTypeNode.Modifiers |= Modifiers.Abstract; |
||||||
|
foreach (var entity in abstractClassTypeNode.Children.OfType<EntityDeclaration>()) { |
||||||
|
entity.Modifiers |= Modifiers.Abstract | Modifiers.Public; |
||||||
|
} |
||||||
|
|
||||||
|
var refactoringContext = SDRefactoringContext.Create(context.Editor, CancellationToken.None); |
||||||
|
using (Script script = refactoringContext.StartScript()) { |
||||||
|
// Replace interface node with node of abstract class
|
||||||
|
script.Replace(interfaceTypeDeclaration, abstractClassTypeNode); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override string DisplayName |
||||||
|
{ |
||||||
|
get { |
||||||
|
return "Convert interface to abstract class"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Text; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Document |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A TextWriter implementation that directly inserts into a document.
|
||||||
|
/// </summary>
|
||||||
|
public class DocumentTextWriter : TextWriter |
||||||
|
{ |
||||||
|
readonly IDocument document; |
||||||
|
int insertionOffset; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new DocumentTextWriter that inserts into document, starting at insertionOffset.
|
||||||
|
/// </summary>
|
||||||
|
public DocumentTextWriter(IDocument document, int insertionOffset) |
||||||
|
{ |
||||||
|
this.insertionOffset = insertionOffset; |
||||||
|
if (document == null) |
||||||
|
throw new ArgumentNullException("document"); |
||||||
|
this.document = document; |
||||||
|
var line = document.GetLineByOffset(insertionOffset); |
||||||
|
if (line.DelimiterLength == 0) |
||||||
|
line = line.PreviousLine; |
||||||
|
if (line != null) |
||||||
|
this.NewLine = document.GetText(line.EndOffset, line.DelimiterLength); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the current insertion offset.
|
||||||
|
/// </summary>
|
||||||
|
public int InsertionOffset { |
||||||
|
get { return insertionOffset; } |
||||||
|
set { insertionOffset = value; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Write(char value) |
||||||
|
{ |
||||||
|
document.Insert(insertionOffset, value.ToString()); |
||||||
|
insertionOffset++; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Write(char[] buffer, int index, int count) |
||||||
|
{ |
||||||
|
document.Insert(insertionOffset, new string(buffer, index, count)); |
||||||
|
insertionOffset += count; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Write(string value) |
||||||
|
{ |
||||||
|
document.Insert(insertionOffset, value); |
||||||
|
insertionOffset += value.Length; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override Encoding Encoding { |
||||||
|
get { return Encoding.UTF8; } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Net; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Highlighting |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Holds options for converting text to HTML.
|
||||||
|
/// </summary>
|
||||||
|
public class HtmlOptions |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Creates a default HtmlOptions instance.
|
||||||
|
/// </summary>
|
||||||
|
public HtmlOptions() |
||||||
|
{ |
||||||
|
this.TabSize = 4; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new HtmlOptions instance that copies applicable options from the <see cref="TextEditorOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
public HtmlOptions(TextEditorOptions options) : this() |
||||||
|
{ |
||||||
|
if (options == null) |
||||||
|
throw new ArgumentNullException("options"); |
||||||
|
this.TabSize = options.IndentationSize; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of spaces a tab gets converted to.
|
||||||
|
/// </summary>
|
||||||
|
public int TabSize { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the HTML attribute for the style to the text writer.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void WriteStyleAttributeForColor(TextWriter writer, HighlightingColor color) |
||||||
|
{ |
||||||
|
if (writer == null) |
||||||
|
throw new ArgumentNullException("writer"); |
||||||
|
if (color == null) |
||||||
|
throw new ArgumentNullException("color"); |
||||||
|
writer.Write(" style=\""); |
||||||
|
WebUtility.HtmlEncode(color.ToCss(), writer); |
||||||
|
writer.Write('"'); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the color needs to be written out to HTML.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool ColorNeedsSpanForStyling(HighlightingColor color) |
||||||
|
{ |
||||||
|
if (color == null) |
||||||
|
throw new ArgumentNullException("color"); |
||||||
|
return !string.IsNullOrEmpty(color.ToCss()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,236 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using System.Net; |
||||||
|
using System.Text; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Highlighting |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// RichTextWriter implementation that produces HTML.
|
||||||
|
/// </summary>
|
||||||
|
class HtmlRichTextWriter : RichTextWriter |
||||||
|
{ |
||||||
|
readonly TextWriter htmlWriter; |
||||||
|
readonly HtmlOptions options; |
||||||
|
Stack<string> endTagStack = new Stack<string>(); |
||||||
|
bool spaceNeedsEscaping = true; |
||||||
|
bool hasSpace; |
||||||
|
bool needIndentation = true; |
||||||
|
int indentationLevel; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new HtmlRichTextWriter instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="htmlWriter">
|
||||||
|
/// The text writer where the raw HTML is written to.
|
||||||
|
/// The HtmlRichTextWriter does not take ownership of the htmlWriter;
|
||||||
|
/// disposing the HtmlRichTextWriter will not dispose the underlying htmlWriter!
|
||||||
|
/// </param>
|
||||||
|
/// <param name="options">Options that control the HTML output.</param>
|
||||||
|
public HtmlRichTextWriter(TextWriter htmlWriter, HtmlOptions options = null) |
||||||
|
{ |
||||||
|
if (htmlWriter == null) |
||||||
|
throw new ArgumentNullException("htmlWriter"); |
||||||
|
this.htmlWriter = htmlWriter; |
||||||
|
this.options = options ?? new HtmlOptions(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override Encoding Encoding { |
||||||
|
get { return htmlWriter.Encoding; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Flush() |
||||||
|
{ |
||||||
|
FlushSpace(true); // next char potentially might be whitespace
|
||||||
|
htmlWriter.Flush(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Dispose(bool disposing) |
||||||
|
{ |
||||||
|
if (disposing) { |
||||||
|
FlushSpace(true); |
||||||
|
} |
||||||
|
base.Dispose(disposing); |
||||||
|
} |
||||||
|
|
||||||
|
void FlushSpace(bool nextIsWhitespace) |
||||||
|
{ |
||||||
|
if (hasSpace) { |
||||||
|
if (spaceNeedsEscaping || nextIsWhitespace) |
||||||
|
htmlWriter.Write(" "); |
||||||
|
else |
||||||
|
htmlWriter.Write(' '); |
||||||
|
hasSpace = false; |
||||||
|
spaceNeedsEscaping = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void WriteIndentation() |
||||||
|
{ |
||||||
|
if (needIndentation) { |
||||||
|
for (int i = 0; i < indentationLevel; i++) { |
||||||
|
WriteChar('\t'); |
||||||
|
} |
||||||
|
needIndentation = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Write(char value) |
||||||
|
{ |
||||||
|
WriteIndentation(); |
||||||
|
WriteChar(value); |
||||||
|
} |
||||||
|
|
||||||
|
static readonly char[] specialChars = { ' ', '\t', '\r', '\n' }; |
||||||
|
|
||||||
|
void WriteChar(char c) |
||||||
|
{ |
||||||
|
bool isWhitespace = char.IsWhiteSpace(c); |
||||||
|
FlushSpace(isWhitespace); |
||||||
|
switch (c) { |
||||||
|
case ' ': |
||||||
|
if (spaceNeedsEscaping) |
||||||
|
htmlWriter.Write(" "); |
||||||
|
else |
||||||
|
hasSpace = true; |
||||||
|
break; |
||||||
|
case '\t': |
||||||
|
for (int i = 0; i < options.TabSize; i++) { |
||||||
|
htmlWriter.Write(" "); |
||||||
|
} |
||||||
|
break; |
||||||
|
case '\r': |
||||||
|
break; // ignore; we'll write the <br/> with the following \n
|
||||||
|
case '\n': |
||||||
|
htmlWriter.Write("<br/>"); |
||||||
|
needIndentation = true; |
||||||
|
break; |
||||||
|
default: |
||||||
|
WebUtility.HtmlEncode(c.ToString(), htmlWriter); |
||||||
|
break; |
||||||
|
} |
||||||
|
// If we just handled a space by setting hasSpace = true,
|
||||||
|
// we mustn't set spaceNeedsEscaping as doing so would affect our own space,
|
||||||
|
// not just the following spaces.
|
||||||
|
if (c != ' ') { |
||||||
|
// Following spaces must be escaped if c was a newline/tab;
|
||||||
|
// and they don't need escaping if c was a normal character.
|
||||||
|
spaceNeedsEscaping = isWhitespace; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Write(string value) |
||||||
|
{ |
||||||
|
int pos = 0; |
||||||
|
do { |
||||||
|
int endPos = value.IndexOfAny(specialChars, pos); |
||||||
|
if (endPos < 0) { |
||||||
|
WriteSimpleString(value.Substring(pos)); |
||||||
|
return; // reached end of string
|
||||||
|
} |
||||||
|
if (endPos > pos) |
||||||
|
WriteSimpleString(value.Substring(pos, endPos - pos)); |
||||||
|
WriteChar(value[pos]); |
||||||
|
pos = endPos + 1; |
||||||
|
} while (pos < value.Length); |
||||||
|
} |
||||||
|
|
||||||
|
void WriteIndentationAndSpace() |
||||||
|
{ |
||||||
|
WriteIndentation(); |
||||||
|
FlushSpace(false); |
||||||
|
} |
||||||
|
|
||||||
|
void WriteSimpleString(string value) |
||||||
|
{ |
||||||
|
if (value.Length == 0) |
||||||
|
return; |
||||||
|
WriteIndentationAndSpace(); |
||||||
|
WebUtility.HtmlEncode(value, htmlWriter); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Indent() |
||||||
|
{ |
||||||
|
indentationLevel++; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Unindent() |
||||||
|
{ |
||||||
|
if (indentationLevel == 0) |
||||||
|
throw new NotSupportedException(); |
||||||
|
indentationLevel--; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void BeginUnhandledSpan() |
||||||
|
{ |
||||||
|
endTagStack.Push(null); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void EndSpan() |
||||||
|
{ |
||||||
|
htmlWriter.Write(endTagStack.Pop()); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(Color foregroundColor) |
||||||
|
{ |
||||||
|
BeginSpan(new HighlightingColor { Foreground = new SimpleHighlightingBrush(foregroundColor) }); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(FontFamily fontFamily) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); // TODO
|
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(FontStyle fontStyle) |
||||||
|
{ |
||||||
|
BeginSpan(new HighlightingColor { FontStyle = fontStyle }); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(FontWeight fontWeight) |
||||||
|
{ |
||||||
|
BeginSpan(new HighlightingColor { FontWeight = fontWeight }); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(HighlightingColor highlightingColor) |
||||||
|
{ |
||||||
|
WriteIndentationAndSpace(); |
||||||
|
if (options.ColorNeedsSpanForStyling(highlightingColor)) { |
||||||
|
htmlWriter.Write("<span"); |
||||||
|
options.WriteStyleAttributeForColor(htmlWriter, highlightingColor); |
||||||
|
htmlWriter.Write('>'); |
||||||
|
endTagStack.Push("</span>"); |
||||||
|
} else { |
||||||
|
endTagStack.Push(null); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginHyperlinkSpan(Uri uri) |
||||||
|
{ |
||||||
|
WriteIndentationAndSpace(); |
||||||
|
htmlWriter.Write("<a href=\"" + WebUtility.HtmlEncode(uri.ToString()) + "\">"); |
||||||
|
endTagStack.Push("</a>"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,252 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Globalization; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Windows.Documents; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Highlighting |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents a immutable piece text with highlighting information.
|
||||||
|
/// </summary>
|
||||||
|
public class RichText |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The empty string without any formatting information.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly RichText Empty = new RichText(string.Empty); |
||||||
|
|
||||||
|
readonly string text; |
||||||
|
internal readonly int[] stateChangeOffsets; |
||||||
|
internal readonly HighlightingColor[] stateChanges; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a RichText instance with the given text and RichTextModel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">
|
||||||
|
/// The text to use in this RichText instance.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="model">
|
||||||
|
/// The model that contains the formatting to use for this RichText instance.
|
||||||
|
/// <c>model.DocumentLength</c> should correspond to <c>text.Length</c>.
|
||||||
|
/// This parameter may be null, in which case the RichText instance just holds plain text.
|
||||||
|
/// </param>
|
||||||
|
public RichText(string text, RichTextModel model = null) |
||||||
|
{ |
||||||
|
if (text == null) |
||||||
|
throw new ArgumentNullException("text"); |
||||||
|
this.text = text; |
||||||
|
if (model != null) { |
||||||
|
var sections = model.GetHighlightedSections(0, text.Length).ToArray(); |
||||||
|
stateChangeOffsets = new int[sections.Length]; |
||||||
|
stateChanges = new HighlightingColor[sections.Length]; |
||||||
|
for (int i = 0; i < sections.Length; i++) { |
||||||
|
stateChangeOffsets[i] = sections[i].Offset; |
||||||
|
stateChanges[i] = sections[i].Color; |
||||||
|
} |
||||||
|
} else { |
||||||
|
stateChangeOffsets = new int[] { 0 }; |
||||||
|
stateChanges = new HighlightingColor[] { HighlightingColor.Empty }; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal RichText(string text, int[] offsets, HighlightingColor[] states) |
||||||
|
{ |
||||||
|
this.text = text; |
||||||
|
Debug.Assert(offsets[0] == 0); |
||||||
|
Debug.Assert(offsets.Last() <= text.Length); |
||||||
|
this.stateChangeOffsets = offsets; |
||||||
|
this.stateChanges = states; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { |
||||||
|
get { return text; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text length.
|
||||||
|
/// </summary>
|
||||||
|
public int Length { |
||||||
|
get { return text.Length; } |
||||||
|
} |
||||||
|
|
||||||
|
int GetIndexForOffset(int offset) |
||||||
|
{ |
||||||
|
if (offset < 0 || offset > text.Length) |
||||||
|
throw new ArgumentOutOfRangeException("offset"); |
||||||
|
int index = Array.BinarySearch(stateChangeOffsets, offset); |
||||||
|
if (index < 0) { |
||||||
|
// If no color change exists directly at offset,
|
||||||
|
// return the index of the color segment that contains offset.
|
||||||
|
index = ~index - 1; |
||||||
|
} |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
int GetEnd(int index) |
||||||
|
{ |
||||||
|
// Gets the end of the color segment no. index.
|
||||||
|
if (index + 1 < stateChangeOffsets.Length) |
||||||
|
return stateChangeOffsets[index + 1]; |
||||||
|
else |
||||||
|
return text.Length; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HighlightingColor for the specified offset.
|
||||||
|
/// </summary>
|
||||||
|
public HighlightingColor GetHighlightingAt(int offset) |
||||||
|
{ |
||||||
|
return stateChanges[GetIndexForOffset(offset)]; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the highlighted sections in the specified range.
|
||||||
|
/// The highlighted sections will be sorted by offset, and there will not be any nested or overlapping sections.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<HighlightedSection> GetHighlightedSections(int offset, int length) |
||||||
|
{ |
||||||
|
int index = GetIndexForOffset(offset); |
||||||
|
int pos = offset; |
||||||
|
int endOffset = offset + length; |
||||||
|
while (pos < endOffset) { |
||||||
|
int endPos = Math.Min(endOffset, GetEnd(index)); |
||||||
|
yield return new HighlightedSection { |
||||||
|
Offset = pos, |
||||||
|
Length = endPos - pos, |
||||||
|
Color = stateChanges[index] |
||||||
|
}; |
||||||
|
pos = endPos; |
||||||
|
index++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RichTextModel with the formatting from this RichText.
|
||||||
|
/// </summary>
|
||||||
|
public RichTextModel ToRichTextModel() |
||||||
|
{ |
||||||
|
return new RichTextModel(stateChangeOffsets, stateChanges); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text.
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() |
||||||
|
{ |
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates WPF Run instances that can be used for TextBlock.Inlines.
|
||||||
|
/// </summary>
|
||||||
|
public Run[] CreateRuns() |
||||||
|
{ |
||||||
|
Run[] runs = new Run[stateChanges.Length]; |
||||||
|
for (int i = 0; i < runs.Length; i++) { |
||||||
|
int startOffset = stateChangeOffsets[i]; |
||||||
|
int endOffset = i + 1 < stateChangeOffsets.Length ? stateChangeOffsets[i + 1] : text.Length; |
||||||
|
Run r = new Run(text.Substring(startOffset, endOffset - startOffset)); |
||||||
|
HighlightingColor state = stateChanges[i]; |
||||||
|
if (state.Foreground != null) |
||||||
|
r.Foreground = state.Foreground.GetBrush(null); |
||||||
|
if (state.Background != null) |
||||||
|
r.Background = state.Background.GetBrush(null); |
||||||
|
if (state.FontWeight != null) |
||||||
|
r.FontWeight = state.FontWeight.Value; |
||||||
|
if (state.FontStyle != null) |
||||||
|
r.FontStyle = state.FontStyle.Value; |
||||||
|
runs[i] = r; |
||||||
|
} |
||||||
|
return runs; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Produces HTML code for the line, with <span style="..."> tags.
|
||||||
|
/// </summary>
|
||||||
|
public string ToHtml(HtmlOptions options = null) |
||||||
|
{ |
||||||
|
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture); |
||||||
|
using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) { |
||||||
|
htmlWriter.Write(this); |
||||||
|
} |
||||||
|
return stringWriter.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Produces HTML code for a section of the line, with <span style="..."> tags.
|
||||||
|
/// </summary>
|
||||||
|
public string ToHtml(int offset, int length, HtmlOptions options = null) |
||||||
|
{ |
||||||
|
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture); |
||||||
|
using (var htmlWriter = new HtmlRichTextWriter(stringWriter, options)) { |
||||||
|
htmlWriter.Write(this, offset, length); |
||||||
|
} |
||||||
|
return stringWriter.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a substring of this rich text.
|
||||||
|
/// </summary>
|
||||||
|
public RichText Substring(int offset, int length) |
||||||
|
{ |
||||||
|
if (offset == 0 && length == this.Length) |
||||||
|
return this; |
||||||
|
string newText = text.Substring(offset, length); |
||||||
|
RichTextModel model = ToRichTextModel(); |
||||||
|
OffsetChangeMap map = new OffsetChangeMap(2); |
||||||
|
map.Add(new OffsetChangeMapEntry(offset + length, text.Length - offset - length, 0)); |
||||||
|
map.Add(new OffsetChangeMapEntry(0, offset, 0)); |
||||||
|
model.UpdateOffsets(map); |
||||||
|
return new RichText(newText, model); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Concatenates the specified rich texts.
|
||||||
|
/// </summary>
|
||||||
|
public static RichText Concat(params RichText[] texts) |
||||||
|
{ |
||||||
|
if (texts == null || texts.Length == 0) |
||||||
|
return Empty; |
||||||
|
else if (texts.Length == 1) |
||||||
|
return texts[0]; |
||||||
|
string newText = string.Concat(texts.Select(txt => txt.text)); |
||||||
|
RichTextModel model = texts[0].ToRichTextModel(); |
||||||
|
int offset = texts[0].Length; |
||||||
|
for (int i = 1; i < texts.Length; i++) { |
||||||
|
model.Append(offset, texts[i].stateChangeOffsets, texts[i].stateChanges); |
||||||
|
offset += texts[i].Length; |
||||||
|
} |
||||||
|
return new RichText(newText, model); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Concatenates the specified rich texts.
|
||||||
|
/// </summary>
|
||||||
|
public static RichText operator +(RichText a, RichText b) |
||||||
|
{ |
||||||
|
return RichText.Concat(a, b); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implicit conversion from string to RichText.
|
||||||
|
/// </summary>
|
||||||
|
public static implicit operator RichText(string text) |
||||||
|
{ |
||||||
|
if (text != null) |
||||||
|
return new RichText(text); |
||||||
|
else |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using ICSharpCode.AvalonEdit.Rendering; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Highlighting |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A colorizer that applies the highlighting from a <see cref="RichTextModel"/> to the editor.
|
||||||
|
/// </summary>
|
||||||
|
public class RichTextColorizer : DocumentColorizingTransformer |
||||||
|
{ |
||||||
|
readonly RichTextModel richTextModel; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RichTextColorizer instance.
|
||||||
|
/// </summary>
|
||||||
|
public RichTextColorizer(RichTextModel richTextModel) |
||||||
|
{ |
||||||
|
if (richTextModel == null) |
||||||
|
throw new ArgumentNullException("richTextModel"); |
||||||
|
this.richTextModel = richTextModel; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void ColorizeLine(DocumentLine line) |
||||||
|
{ |
||||||
|
var sections = richTextModel.GetHighlightedSections(line.Offset, line.Length); |
||||||
|
foreach (HighlightedSection section in sections) { |
||||||
|
if (HighlightingColorizer.IsEmptyColor(section.Color)) |
||||||
|
continue; |
||||||
|
ChangeLinePart(section.Offset, section.Offset + section.Length, |
||||||
|
visualLineElement => HighlightingColorizer.ApplyColorToElement(visualLineElement, section.Color, CurrentContext)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,268 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Linq; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Highlighting |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Stores rich-text formatting.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RichTextModel : AbstractFreezable |
||||||
|
{ |
||||||
|
List<int> stateChangeOffsets = new List<int>(); |
||||||
|
List<HighlightingColor> stateChanges = new List<HighlightingColor>(); |
||||||
|
|
||||||
|
int GetIndexForOffset(int offset) |
||||||
|
{ |
||||||
|
if (offset < 0) |
||||||
|
throw new ArgumentOutOfRangeException("offset"); |
||||||
|
int index = stateChangeOffsets.BinarySearch(offset); |
||||||
|
if (index < 0) { |
||||||
|
// If no color change exists directly at offset,
|
||||||
|
// create a new one.
|
||||||
|
index = ~index; |
||||||
|
stateChanges.Insert(index, stateChanges[index - 1].Clone()); |
||||||
|
stateChangeOffsets.Insert(index, offset); |
||||||
|
} |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
int GetIndexForOffsetUseExistingSegment(int offset) |
||||||
|
{ |
||||||
|
if (offset < 0) |
||||||
|
throw new ArgumentOutOfRangeException("offset"); |
||||||
|
int index = stateChangeOffsets.BinarySearch(offset); |
||||||
|
if (index < 0) { |
||||||
|
// If no color change exists directly at offset,
|
||||||
|
// return the index of the color segment that contains offset.
|
||||||
|
index = ~index - 1; |
||||||
|
} |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
int GetEnd(int index) |
||||||
|
{ |
||||||
|
// Gets the end of the color segment no. index.
|
||||||
|
if (index + 1 < stateChangeOffsets.Count) |
||||||
|
return stateChangeOffsets[index + 1]; |
||||||
|
else |
||||||
|
return int.MaxValue; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RichTextModel.
|
||||||
|
/// </summary>
|
||||||
|
public RichTextModel() |
||||||
|
{ |
||||||
|
stateChangeOffsets.Add(0); |
||||||
|
stateChanges.Add(new HighlightingColor()); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a RichTextModel from a CONTIGUOUS list of HighlightedSections.
|
||||||
|
/// </summary>
|
||||||
|
internal RichTextModel(int[] stateChangeOffsets, HighlightingColor[] stateChanges) |
||||||
|
{ |
||||||
|
Debug.Assert(stateChangeOffsets[0] == 0); |
||||||
|
this.stateChangeOffsets.AddRange(stateChangeOffsets); |
||||||
|
this.stateChanges.AddRange(stateChanges); |
||||||
|
} |
||||||
|
|
||||||
|
#region UpdateOffsets
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the start and end offsets of all segments stored in this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e">TextChangeEventArgs instance describing the change to the document.</param>
|
||||||
|
public void UpdateOffsets(TextChangeEventArgs e) |
||||||
|
{ |
||||||
|
if (e == null) |
||||||
|
throw new ArgumentNullException("e"); |
||||||
|
UpdateOffsets(e.GetNewOffset); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the start and end offsets of all segments stored in this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="change">OffsetChangeMap instance describing the change to the document.</param>
|
||||||
|
public void UpdateOffsets(OffsetChangeMap change) |
||||||
|
{ |
||||||
|
if (change == null) |
||||||
|
throw new ArgumentNullException("change"); |
||||||
|
UpdateOffsets(change.GetNewOffset); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the start and end offsets of all segments stored in this collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
|
||||||
|
public void UpdateOffsets(OffsetChangeMapEntry change) |
||||||
|
{ |
||||||
|
UpdateOffsets(change.GetNewOffset); |
||||||
|
} |
||||||
|
|
||||||
|
void UpdateOffsets(Func<int, AnchorMovementType, int> updateOffset) |
||||||
|
{ |
||||||
|
int readPos = 1; |
||||||
|
int writePos = 1; |
||||||
|
while (readPos < stateChangeOffsets.Count) { |
||||||
|
Debug.Assert(writePos <= readPos); |
||||||
|
int newOffset = updateOffset(stateChangeOffsets[readPos], AnchorMovementType.Default); |
||||||
|
if (newOffset == stateChangeOffsets[writePos - 1]) { |
||||||
|
// offset moved to same position as previous offset
|
||||||
|
// -> previous segment has length 0 and gets overwritten with this segment
|
||||||
|
stateChanges[writePos - 1] = stateChanges[readPos]; |
||||||
|
} else { |
||||||
|
stateChangeOffsets[writePos] = newOffset; |
||||||
|
stateChanges[writePos] = stateChanges[readPos]; |
||||||
|
writePos++; |
||||||
|
} |
||||||
|
readPos++; |
||||||
|
} |
||||||
|
// Delete all entries that were not written to
|
||||||
|
stateChangeOffsets.RemoveRange(writePos, stateChangeOffsets.Count - writePos); |
||||||
|
stateChanges.RemoveRange(writePos, stateChanges.Count - writePos); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends another RichTextModel after this one.
|
||||||
|
/// </summary>
|
||||||
|
internal void Append(int offset, int[] newOffsets, HighlightingColor[] newColors) |
||||||
|
{ |
||||||
|
Debug.Assert(newOffsets.Length == newColors.Length); |
||||||
|
Debug.Assert(newOffsets[0] == 0); |
||||||
|
// remove everything before offset:
|
||||||
|
while (stateChangeOffsets.Count > 0 && stateChangeOffsets.Last() <= offset) { |
||||||
|
stateChangeOffsets.RemoveAt(stateChangeOffsets.Count - 1); |
||||||
|
stateChanges.RemoveAt(stateChanges.Count - 1); |
||||||
|
} |
||||||
|
// Append the new segments
|
||||||
|
for (int i = 0; i < newOffsets.Length; i++) { |
||||||
|
stateChangeOffsets.Add(offset + newOffsets[i]); |
||||||
|
stateChanges.Add(newColors[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a copy of the HighlightingColor for the specified offset.
|
||||||
|
/// </summary>
|
||||||
|
public HighlightingColor GetHighlightingAt(int offset) |
||||||
|
{ |
||||||
|
return stateChanges[GetIndexForOffsetUseExistingSegment(offset)].Clone(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the HighlightingColor to the specified range of text.
|
||||||
|
/// If the color specifies <c>null</c> for some properties, existing highlighting is preserved.
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyHighlighting(int offset, int length, HighlightingColor color) |
||||||
|
{ |
||||||
|
if (color == null || color.IsEmptyForMerge) { |
||||||
|
// Optimization: don't split the HighlightingState when we're not changing
|
||||||
|
// any property. For example, the "Punctuation" color in C# is
|
||||||
|
// empty by default.
|
||||||
|
return; |
||||||
|
} |
||||||
|
int startIndex = GetIndexForOffset(offset); |
||||||
|
int endIndex = GetIndexForOffset(offset + length); |
||||||
|
for (int i = startIndex; i < endIndex; i++) { |
||||||
|
stateChanges[i].MergeWith(color); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the HighlightingColor for the specified range of text,
|
||||||
|
/// completely replacing the existing highlighting in that area.
|
||||||
|
/// </summary>
|
||||||
|
public void SetHighlighting(int offset, int length, HighlightingColor color) |
||||||
|
{ |
||||||
|
if (length <= 0) |
||||||
|
return; |
||||||
|
int startIndex = GetIndexForOffset(offset); |
||||||
|
int endIndex = GetIndexForOffset(offset + length); |
||||||
|
stateChanges[startIndex] = color != null ? color.Clone() : new HighlightingColor(); |
||||||
|
stateChanges.RemoveRange(startIndex + 1, endIndex - (startIndex + 1)); |
||||||
|
stateChangeOffsets.RemoveRange(startIndex + 1, endIndex - (startIndex + 1)); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the foreground brush on the specified text segment.
|
||||||
|
/// </summary>
|
||||||
|
public void SetForeground(int offset, int length, HighlightingBrush brush) |
||||||
|
{ |
||||||
|
int startIndex = GetIndexForOffset(offset); |
||||||
|
int endIndex = GetIndexForOffset(offset + length); |
||||||
|
for (int i = startIndex; i < endIndex; i++) { |
||||||
|
stateChanges[i].Foreground = brush; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the background brush on the specified text segment.
|
||||||
|
/// </summary>
|
||||||
|
public void SetBackground(int offset, int length, HighlightingBrush brush) |
||||||
|
{ |
||||||
|
int startIndex = GetIndexForOffset(offset); |
||||||
|
int endIndex = GetIndexForOffset(offset + length); |
||||||
|
for (int i = startIndex; i < endIndex; i++) { |
||||||
|
stateChanges[i].Background = brush; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the font weight on the specified text segment.
|
||||||
|
/// </summary>
|
||||||
|
public void SetFontWeight(int offset, int length, FontWeight weight) |
||||||
|
{ |
||||||
|
int startIndex = GetIndexForOffset(offset); |
||||||
|
int endIndex = GetIndexForOffset(offset + length); |
||||||
|
for (int i = startIndex; i < endIndex; i++) { |
||||||
|
stateChanges[i].FontWeight = weight; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the font style on the specified text segment.
|
||||||
|
/// </summary>
|
||||||
|
public void SetFontStyle(int offset, int length, FontStyle style) |
||||||
|
{ |
||||||
|
int startIndex = GetIndexForOffset(offset); |
||||||
|
int endIndex = GetIndexForOffset(offset + length); |
||||||
|
for (int i = startIndex; i < endIndex; i++) { |
||||||
|
stateChanges[i].FontStyle = style; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the highlighted sections in the specified range.
|
||||||
|
/// The highlighted sections will be sorted by offset, and there will not be any nested or overlapping sections.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<HighlightedSection> GetHighlightedSections(int offset, int length) |
||||||
|
{ |
||||||
|
int index = GetIndexForOffsetUseExistingSegment(offset); |
||||||
|
int pos = offset; |
||||||
|
int endOffset = offset + length; |
||||||
|
while (pos < endOffset) { |
||||||
|
int endPos = Math.Min(endOffset, GetEnd(index)); |
||||||
|
yield return new HighlightedSection { |
||||||
|
Offset = pos, |
||||||
|
Length = endPos - pos, |
||||||
|
Color = stateChanges[index].Clone() |
||||||
|
}; |
||||||
|
pos = endPos; |
||||||
|
index++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Highlighting |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A RichTextWriter that writes into a document and RichTextModel.
|
||||||
|
/// </summary>
|
||||||
|
class RichTextModelWriter : PlainRichTextWriter |
||||||
|
{ |
||||||
|
readonly RichTextModel richTextModel; |
||||||
|
readonly DocumentTextWriter documentTextWriter; |
||||||
|
readonly Stack<HighlightingColor> colorStack = new Stack<HighlightingColor>(); |
||||||
|
HighlightingColor currentColor; |
||||||
|
int currentColorBegin = -1; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RichTextModelWriter that inserts into document, starting at insertionOffset.
|
||||||
|
/// </summary>
|
||||||
|
public RichTextModelWriter(RichTextModel richTextModel, IDocument document, int insertionOffset) |
||||||
|
: base(new DocumentTextWriter(document, insertionOffset)) |
||||||
|
{ |
||||||
|
if (richTextModel == null) |
||||||
|
throw new ArgumentNullException("richTextModel"); |
||||||
|
this.richTextModel = richTextModel; |
||||||
|
this.documentTextWriter = (DocumentTextWriter)base.textWriter; |
||||||
|
currentColor = richTextModel.GetHighlightingAt(Math.Max(0, insertionOffset - 1)); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the current insertion offset.
|
||||||
|
/// </summary>
|
||||||
|
public int InsertionOffset { |
||||||
|
get { return documentTextWriter.InsertionOffset; } |
||||||
|
set { documentTextWriter.InsertionOffset = value; } |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void BeginUnhandledSpan() |
||||||
|
{ |
||||||
|
colorStack.Push(currentColor); |
||||||
|
} |
||||||
|
|
||||||
|
void BeginColorSpan() |
||||||
|
{ |
||||||
|
WriteIndentationIfNecessary(); |
||||||
|
colorStack.Push(currentColor); |
||||||
|
currentColor = currentColor.Clone(); |
||||||
|
currentColorBegin = documentTextWriter.InsertionOffset; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void EndSpan() |
||||||
|
{ |
||||||
|
currentColor = colorStack.Pop(); |
||||||
|
currentColorBegin = documentTextWriter.InsertionOffset; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void AfterWrite() |
||||||
|
{ |
||||||
|
base.AfterWrite(); |
||||||
|
richTextModel.SetHighlighting(currentColorBegin, documentTextWriter.InsertionOffset - currentColorBegin, currentColor); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(Color foregroundColor) |
||||||
|
{ |
||||||
|
BeginColorSpan(); |
||||||
|
currentColor.Foreground = new SimpleHighlightingBrush(foregroundColor); |
||||||
|
currentColor.Freeze(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(FontFamily fontFamily) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); // TODO
|
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(FontStyle fontStyle) |
||||||
|
{ |
||||||
|
BeginColorSpan(); |
||||||
|
currentColor.FontStyle = fontStyle; |
||||||
|
currentColor.Freeze(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(FontWeight fontWeight) |
||||||
|
{ |
||||||
|
BeginColorSpan(); |
||||||
|
currentColor.FontWeight = fontWeight; |
||||||
|
currentColor.Freeze(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginSpan(HighlightingColor highlightingColor) |
||||||
|
{ |
||||||
|
BeginColorSpan(); |
||||||
|
currentColor.MergeWith(highlightingColor); |
||||||
|
currentColor.Freeze(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Text; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Utils |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// RichTextWriter implementation that writes plain text only
|
||||||
|
/// and ignores all formatted spans.
|
||||||
|
/// </summary>
|
||||||
|
class PlainRichTextWriter : RichTextWriter |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The text writer that was passed to the PlainRichTextWriter constructor.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly TextWriter textWriter; |
||||||
|
string indentationString = "\t"; |
||||||
|
int indentationLevel; |
||||||
|
char prevChar; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new PlainRichTextWriter instance that writes the text to the specified text writer.
|
||||||
|
/// </summary>
|
||||||
|
public PlainRichTextWriter(TextWriter textWriter) |
||||||
|
{ |
||||||
|
if (textWriter == null) |
||||||
|
throw new ArgumentNullException("textWriter"); |
||||||
|
this.textWriter = textWriter; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the string used to indent by one level.
|
||||||
|
/// </summary>
|
||||||
|
public string IndentationString { |
||||||
|
get { |
||||||
|
return indentationString; |
||||||
|
} |
||||||
|
set { |
||||||
|
indentationString = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void BeginUnhandledSpan() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void EndSpan() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
void WriteIndentation() |
||||||
|
{ |
||||||
|
for (int i = 0; i < indentationLevel; i++) { |
||||||
|
textWriter.Write(indentationString); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the indentation, if necessary.
|
||||||
|
/// </summary>
|
||||||
|
protected void WriteIndentationIfNecessary() |
||||||
|
{ |
||||||
|
if (prevChar == '\n') { |
||||||
|
WriteIndentation(); |
||||||
|
prevChar = '\0'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is called after a write operation.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void AfterWrite() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Write(char value) |
||||||
|
{ |
||||||
|
if (prevChar == '\n') |
||||||
|
WriteIndentation(); |
||||||
|
textWriter.Write(value); |
||||||
|
prevChar = value; |
||||||
|
AfterWrite(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Indent() |
||||||
|
{ |
||||||
|
indentationLevel++; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Unindent() |
||||||
|
{ |
||||||
|
if (indentationLevel == 0) |
||||||
|
throw new NotSupportedException(); |
||||||
|
indentationLevel--; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override Encoding Encoding { |
||||||
|
get { return textWriter.Encoding; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IFormatProvider FormatProvider { |
||||||
|
get { return textWriter.FormatProvider; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string NewLine { |
||||||
|
get { |
||||||
|
return textWriter.NewLine; |
||||||
|
} |
||||||
|
set { |
||||||
|
textWriter.NewLine = value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media; |
||||||
|
using ICSharpCode.AvalonEdit.Highlighting; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Utils |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A text writer that supports creating spans of highlighted text.
|
||||||
|
/// </summary>
|
||||||
|
abstract class RichTextWriter : TextWriter |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets called by the RichTextWriter base class when a BeginSpan() method
|
||||||
|
/// that is not overwritten gets called.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void BeginUnhandledSpan(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the RichText instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Write(RichText richText) |
||||||
|
{ |
||||||
|
Write(richText, 0, richText.Length); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the RichText instance.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Write(RichText richText, int offset, int length) |
||||||
|
{ |
||||||
|
foreach (var section in richText.GetHighlightedSections(offset, length)) { |
||||||
|
BeginSpan(section.Color); |
||||||
|
Write(richText.Text.Substring(section.Offset, section.Length)); |
||||||
|
EndSpan(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin a colored span.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BeginSpan(Color foregroundColor) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin a span with modified font weight.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BeginSpan(FontWeight fontWeight) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin a span with modified font style.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BeginSpan(FontStyle fontStyle) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin a span with modified font family.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BeginSpan(FontFamily fontFamily) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin a highlighted span.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BeginSpan(Highlighting.HighlightingColor highlightingColor) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begin a span that links to the specified URI.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BeginHyperlinkSpan(Uri uri) |
||||||
|
{ |
||||||
|
BeginUnhandledSpan(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks the end of the current span.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void EndSpan(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the indentation level.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Indent(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decreases the indentation level.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Unindent(); |
||||||
|
} |
||||||
|
} |
@ -1,439 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Text; |
|
||||||
using ICSharpCode.AvalonEdit.Utils; |
|
||||||
using ICSharpCode.NRefactory.Utils; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
class TagMatchingHeuristics |
|
||||||
{ |
|
||||||
const int maxConfigurationCount = 10; |
|
||||||
|
|
||||||
AXmlParser parser; |
|
||||||
TrackedSegmentCollection trackedSegments; |
|
||||||
string input; |
|
||||||
List<AXmlObject> tags; |
|
||||||
|
|
||||||
public TagMatchingHeuristics(AXmlParser parser, string input, List<AXmlObject> tags) |
|
||||||
{ |
|
||||||
this.parser = parser; |
|
||||||
this.trackedSegments = parser.TrackedSegments; |
|
||||||
this.input = input; |
|
||||||
this.tags = tags; |
|
||||||
} |
|
||||||
|
|
||||||
public AXmlDocument ReadDocument() |
|
||||||
{ |
|
||||||
AXmlDocument doc = new AXmlDocument() { Parser = parser }; |
|
||||||
|
|
||||||
// AXmlParser.Log("Flat stream: {0}", PrintObjects(tags));
|
|
||||||
List<AXmlObject> valid = MatchTags(tags); |
|
||||||
// AXmlParser.Log("Fixed stream: {0}", PrintObjects(valid));
|
|
||||||
IEnumerator<AXmlObject> validStream = valid.GetEnumerator(); |
|
||||||
validStream.MoveNext(); // Move to first
|
|
||||||
while(true) { |
|
||||||
// End of stream?
|
|
||||||
try { |
|
||||||
if (validStream.Current == null) break; |
|
||||||
} catch (InvalidCastException) { |
|
||||||
break; |
|
||||||
} |
|
||||||
doc.AddChild(ReadTextOrElement(validStream)); |
|
||||||
} |
|
||||||
|
|
||||||
if (doc.Children.Count > 0) { |
|
||||||
doc.StartOffset = doc.FirstChild.StartOffset; |
|
||||||
doc.EndOffset = doc.LastChild.EndOffset; |
|
||||||
} |
|
||||||
|
|
||||||
// Check well formed
|
|
||||||
foreach(AXmlTag xmlDeclaration in doc.Children.OfType<AXmlTag>().Where(t => t.IsProcessingInstruction && string.Equals(t.Name, "xml", StringComparison.OrdinalIgnoreCase))) { |
|
||||||
if (xmlDeclaration.StartOffset != 0) |
|
||||||
TagReader.OnSyntaxError(doc, xmlDeclaration.StartOffset, xmlDeclaration.StartOffset + 5, |
|
||||||
"XML declaration must be at the start of document"); |
|
||||||
} |
|
||||||
int elemCount = doc.Children.OfType<AXmlElement>().Count(); |
|
||||||
if (elemCount == 0) |
|
||||||
TagReader.OnSyntaxError(doc, doc.EndOffset, doc.EndOffset, |
|
||||||
"Root element is missing"); |
|
||||||
if (elemCount > 1) { |
|
||||||
AXmlElement next = doc.Children.OfType<AXmlElement>().Skip(1).First(); |
|
||||||
TagReader.OnSyntaxError(doc, next.StartOffset, next.StartOffset, |
|
||||||
"Only one root element is allowed"); |
|
||||||
} |
|
||||||
foreach(AXmlTag tag in doc.Children.OfType<AXmlTag>()) { |
|
||||||
if (tag.IsCData) |
|
||||||
TagReader.OnSyntaxError(doc, tag.StartOffset, tag.EndOffset, |
|
||||||
"CDATA not allowed in document root"); |
|
||||||
} |
|
||||||
foreach(AXmlText text in doc.Children.OfType<AXmlText>()) { |
|
||||||
if (!text.ContainsOnlyWhitespace) |
|
||||||
TagReader.OnSyntaxError(doc, text.StartOffset, text.EndOffset, |
|
||||||
"Only whitespace is allowed in document root"); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
AXmlParser.Log("Constructed {0}", doc); |
|
||||||
trackedSegments.AddParsedObject(doc, null); |
|
||||||
return doc; |
|
||||||
} |
|
||||||
|
|
||||||
static AXmlObject ReadSingleObject(IEnumerator<AXmlObject> objStream) |
|
||||||
{ |
|
||||||
AXmlObject obj = objStream.Current; |
|
||||||
objStream.MoveNext(); |
|
||||||
return obj; |
|
||||||
} |
|
||||||
|
|
||||||
AXmlObject ReadTextOrElement(IEnumerator<AXmlObject> objStream) |
|
||||||
{ |
|
||||||
AXmlObject curr = objStream.Current; |
|
||||||
if (curr is AXmlText || curr is AXmlElement) { |
|
||||||
return ReadSingleObject(objStream); |
|
||||||
} else { |
|
||||||
AXmlTag currTag = (AXmlTag)curr; |
|
||||||
if (currTag == StartTagPlaceholder) { |
|
||||||
return ReadElement(objStream); |
|
||||||
} else if (currTag.IsStartOrEmptyTag) { |
|
||||||
return ReadElement(objStream); |
|
||||||
} else { |
|
||||||
return ReadSingleObject(objStream); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
AXmlElement ReadElement(IEnumerator<AXmlObject> objStream) |
|
||||||
{ |
|
||||||
AXmlElement element = new AXmlElement(); |
|
||||||
element.IsProperlyNested = true; |
|
||||||
|
|
||||||
// Read start tag
|
|
||||||
AXmlTag startTag = ReadSingleObject(objStream) as AXmlTag; |
|
||||||
AXmlParser.DebugAssert(startTag != null, "Start tag expected"); |
|
||||||
AXmlParser.DebugAssert(startTag.IsStartOrEmptyTag || startTag == StartTagPlaceholder, "Start tag expected"); |
|
||||||
if (startTag == StartTagPlaceholder) { |
|
||||||
element.HasStartOrEmptyTag = false; |
|
||||||
element.IsProperlyNested = false; |
|
||||||
TagReader.OnSyntaxError(element, objStream.Current.StartOffset, objStream.Current.EndOffset, |
|
||||||
"Matching openning tag was not found"); |
|
||||||
} else { |
|
||||||
element.HasStartOrEmptyTag = true; |
|
||||||
element.AddChild(startTag); |
|
||||||
} |
|
||||||
|
|
||||||
// Read content and end tag
|
|
||||||
if (startTag == StartTagPlaceholder || // Check first in case the start tag is null
|
|
||||||
element.StartTag.IsStartTag) |
|
||||||
{ |
|
||||||
while(true) { |
|
||||||
AXmlTag currTag = objStream.Current as AXmlTag; // Peek
|
|
||||||
if (currTag == EndTagPlaceholder) { |
|
||||||
TagReader.OnSyntaxError(element, element.LastChild.EndOffset, element.LastChild.EndOffset, |
|
||||||
"Expected '</{0}>'", element.StartTag.Name); |
|
||||||
ReadSingleObject(objStream); |
|
||||||
element.HasEndTag = false; |
|
||||||
element.IsProperlyNested = false; |
|
||||||
break; |
|
||||||
} else if (currTag != null && currTag.IsEndTag) { |
|
||||||
if (element.HasStartOrEmptyTag && currTag.Name != element.StartTag.Name) { |
|
||||||
TagReader.OnSyntaxError(element, currTag.StartOffset + 2, currTag.StartOffset + 2 + currTag.Name.Length, |
|
||||||
"Expected '{0}'. End tag must have same name as start tag.", element.StartTag.Name); |
|
||||||
} |
|
||||||
element.AddChild(ReadSingleObject(objStream)); |
|
||||||
element.HasEndTag = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
AXmlObject nested = ReadTextOrElement(objStream); |
|
||||||
|
|
||||||
AXmlElement nestedAsElement = nested as AXmlElement; |
|
||||||
if (nestedAsElement != null) { |
|
||||||
if (!nestedAsElement.IsProperlyNested) |
|
||||||
element.IsProperlyNested = false; |
|
||||||
element.AddChildren(Split(nestedAsElement).ToList()); |
|
||||||
} else { |
|
||||||
element.AddChild(nested); |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
element.HasEndTag = false; |
|
||||||
} |
|
||||||
|
|
||||||
element.StartOffset = element.FirstChild.StartOffset; |
|
||||||
element.EndOffset = element.LastChild.EndOffset; |
|
||||||
|
|
||||||
AXmlParser.Assert(element.HasStartOrEmptyTag || element.HasEndTag, "Must have at least start or end tag"); |
|
||||||
|
|
||||||
AXmlParser.Log("Constructed {0}", element); |
|
||||||
trackedSegments.AddParsedObject(element, null); // Need all elements in cache for offset tracking
|
|
||||||
return element; |
|
||||||
} |
|
||||||
|
|
||||||
IEnumerable<AXmlObject> Split(AXmlElement elem) |
|
||||||
{ |
|
||||||
int myIndention = GetIndentLevel(elem); |
|
||||||
// Has start tag and no end tag ? (other then empty-element tag)
|
|
||||||
if (elem.HasStartOrEmptyTag && elem.StartTag.IsStartTag && !elem.HasEndTag && myIndention != -1) { |
|
||||||
int lastAccepted = 0; // Accept start tag
|
|
||||||
while (lastAccepted + 1 < elem.Children.Count) { |
|
||||||
AXmlObject nextItem = elem.Children[lastAccepted + 1]; |
|
||||||
if (nextItem is AXmlText) { |
|
||||||
lastAccepted++; continue; // Accept
|
|
||||||
} else { |
|
||||||
// Include all more indented items
|
|
||||||
if (GetIndentLevel(nextItem) > myIndention) { |
|
||||||
lastAccepted++; continue; // Accept
|
|
||||||
} else { |
|
||||||
break; // Reject
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
// Accepted everything?
|
|
||||||
if (lastAccepted + 1 == elem.Children.Count) { |
|
||||||
yield return elem; |
|
||||||
yield break; |
|
||||||
} |
|
||||||
AXmlParser.Log("Splitting {0} - take {1} of {2} nested", elem, lastAccepted, elem.Children.Count - 1); |
|
||||||
AXmlElement topHalf = new AXmlElement(); |
|
||||||
topHalf.HasStartOrEmptyTag = elem.HasStartOrEmptyTag; |
|
||||||
topHalf.HasEndTag = elem.HasEndTag; |
|
||||||
topHalf.AddChildren(elem.Children.Take(1 + lastAccepted)); // Start tag + nested
|
|
||||||
topHalf.StartOffset = topHalf.FirstChild.StartOffset; |
|
||||||
topHalf.EndOffset = topHalf.LastChild.EndOffset; |
|
||||||
TagReader.OnSyntaxError(topHalf, topHalf.LastChild.EndOffset, topHalf.LastChild.EndOffset, |
|
||||||
"Expected '</{0}>'", topHalf.StartTag.Name); |
|
||||||
|
|
||||||
AXmlParser.Log("Constructed {0}", topHalf); |
|
||||||
trackedSegments.AddParsedObject(topHalf, null); |
|
||||||
yield return topHalf; |
|
||||||
for(int i = lastAccepted + 1; i < elem.Children.Count; i++) { |
|
||||||
yield return elem.Children[i]; |
|
||||||
} |
|
||||||
} else { |
|
||||||
yield return elem; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
int GetIndentLevel(AXmlObject obj) |
|
||||||
{ |
|
||||||
int offset = obj.StartOffset - 1; |
|
||||||
int level = 0; |
|
||||||
while(true) { |
|
||||||
if (offset < 0) break; |
|
||||||
char c = input[offset]; |
|
||||||
if (c == ' ') { |
|
||||||
level++; |
|
||||||
} else if (c == '\t') { |
|
||||||
level += 4; |
|
||||||
} else if (c == '\r' || c == '\n') { |
|
||||||
break; |
|
||||||
} else { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
offset--; |
|
||||||
} |
|
||||||
return level; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stack of still unmatched start tags.
|
|
||||||
/// It includes the cost and backtack information.
|
|
||||||
/// </summary>
|
|
||||||
class Configuration |
|
||||||
{ |
|
||||||
/// <summary> Unmatched start tags </summary>
|
|
||||||
public ImmutableStack<AXmlTag> StartTags { get; set; } |
|
||||||
/// <summary> Properly nested tags </summary>
|
|
||||||
public ImmutableStack<AXmlObject> Document { get; set; } |
|
||||||
/// <summary> Number of needed modificaitons to the document </summary>
|
|
||||||
public int Cost { get; set; } |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dictionary which stores the cheapest configuration
|
|
||||||
/// </summary>
|
|
||||||
class Configurations: Dictionary<ImmutableStack<AXmlTag>, Configuration> |
|
||||||
{ |
|
||||||
public Configurations() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
public Configurations(IEnumerable<Configuration> configs) |
|
||||||
{ |
|
||||||
foreach(Configuration config in configs) { |
|
||||||
this.Add(config); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Overwrite only if cheaper </summary>
|
|
||||||
public void Add(Configuration newConfig) |
|
||||||
{ |
|
||||||
Configuration oldConfig; |
|
||||||
if (this.TryGetValue(newConfig.StartTags, out oldConfig)) { |
|
||||||
if (newConfig.Cost < oldConfig.Cost) { |
|
||||||
this[newConfig.StartTags] = newConfig; |
|
||||||
} |
|
||||||
} else { |
|
||||||
base.Add(newConfig.StartTags, newConfig); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
StringBuilder sb = new StringBuilder(); |
|
||||||
foreach(var kvp in this) { |
|
||||||
sb.Append("\n - '"); |
|
||||||
foreach(AXmlTag startTag in kvp.Value.StartTags.Reverse()) { |
|
||||||
sb.Append('<'); |
|
||||||
sb.Append(startTag.Name); |
|
||||||
sb.Append('>'); |
|
||||||
} |
|
||||||
sb.AppendFormat("' = {0}", kvp.Value.Cost); |
|
||||||
} |
|
||||||
return sb.ToString(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Tags used to guide the element creation
|
|
||||||
readonly AXmlTag StartTagPlaceholder = new AXmlTag(); |
|
||||||
readonly AXmlTag EndTagPlaceholder = new AXmlTag(); |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add start or end tag placeholders so that the documment is properly nested
|
|
||||||
/// </summary>
|
|
||||||
List<AXmlObject> MatchTags(IEnumerable<AXmlObject> objs) |
|
||||||
{ |
|
||||||
Configurations configurations = new Configurations(); |
|
||||||
configurations.Add(new Configuration { |
|
||||||
StartTags = ImmutableStack<AXmlTag>.Empty, |
|
||||||
Document = ImmutableStack<AXmlObject>.Empty, |
|
||||||
Cost = 0, |
|
||||||
}); |
|
||||||
foreach(AXmlObject obj in objs) { |
|
||||||
configurations = ProcessObject(configurations, obj); |
|
||||||
} |
|
||||||
// Close any remaining start tags
|
|
||||||
foreach(Configuration conifg in configurations.Values) { |
|
||||||
while(!conifg.StartTags.IsEmpty) { |
|
||||||
conifg.StartTags = conifg.StartTags.Pop(); |
|
||||||
conifg.Document = conifg.Document.Push(EndTagPlaceholder); |
|
||||||
conifg.Cost += 1; |
|
||||||
} |
|
||||||
} |
|
||||||
// AXmlParser.Log("Configurations after closing all remaining tags:" + configurations.ToString());
|
|
||||||
Configuration bestConfig = configurations.Values.OrderBy(v => v.Cost).First(); |
|
||||||
AXmlParser.Log("Best configuration has cost {0}", bestConfig.Cost); |
|
||||||
|
|
||||||
return bestConfig.Document.Reverse().ToList(); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Get posible configurations after considering given object </summary>
|
|
||||||
Configurations ProcessObject(Configurations oldConfigs, AXmlObject obj) |
|
||||||
{ |
|
||||||
AXmlParser.Log("Processing {0}", obj); |
|
||||||
|
|
||||||
AXmlTag objAsTag = obj as AXmlTag; |
|
||||||
AXmlElement objAsElement = obj as AXmlElement; |
|
||||||
AXmlParser.DebugAssert(objAsTag != null || objAsElement != null || obj is AXmlText, obj.GetType().Name + " not expected"); |
|
||||||
if (objAsElement != null) |
|
||||||
AXmlParser.Assert(objAsElement.IsProperlyNested, "Element not properly nested"); |
|
||||||
|
|
||||||
Configurations newConfigs = new Configurations(); |
|
||||||
|
|
||||||
foreach(var kvp in oldConfigs) { |
|
||||||
Configuration oldConfig = kvp.Value; |
|
||||||
var oldStartTags = oldConfig.StartTags; |
|
||||||
var oldDocument = oldConfig.Document; |
|
||||||
int oldCost = oldConfig.Cost; |
|
||||||
|
|
||||||
if (objAsTag != null && objAsTag.IsStartTag) { |
|
||||||
newConfigs.Add(new Configuration { // Push start-tag (cost 0)
|
|
||||||
StartTags = oldStartTags.Push(objAsTag), |
|
||||||
Document = oldDocument.Push(objAsTag), |
|
||||||
Cost = oldCost, |
|
||||||
}); |
|
||||||
} else if (objAsTag != null && objAsTag.IsEndTag) { |
|
||||||
newConfigs.Add(new Configuration { // Ignore (cost 1)
|
|
||||||
StartTags = oldStartTags, |
|
||||||
Document = oldDocument.Push(StartTagPlaceholder).Push(objAsTag), |
|
||||||
Cost = oldCost + 1, |
|
||||||
}); |
|
||||||
if (!oldStartTags.IsEmpty && oldStartTags.Peek().Name != objAsTag.Name) { |
|
||||||
newConfigs.Add(new Configuration { // Pop 1 item (cost 1) - not mathcing
|
|
||||||
StartTags = oldStartTags.Pop(), |
|
||||||
Document = oldDocument.Push(objAsTag), |
|
||||||
Cost = oldCost + 1, |
|
||||||
}); |
|
||||||
} |
|
||||||
int popedCount = 0; |
|
||||||
var startTags = oldStartTags; |
|
||||||
var doc = oldDocument; |
|
||||||
foreach(AXmlTag poped in oldStartTags) { |
|
||||||
popedCount++; |
|
||||||
if (poped.Name == objAsTag.Name) { |
|
||||||
newConfigs.Add(new Configuration { // Pop 'x' items (cost x-1) - last one is matching
|
|
||||||
StartTags = startTags.Pop(), |
|
||||||
Document = doc.Push(objAsTag), |
|
||||||
Cost = oldCost + popedCount - 1, |
|
||||||
}); |
|
||||||
} |
|
||||||
startTags = startTags.Pop(); |
|
||||||
doc = doc.Push(EndTagPlaceholder); |
|
||||||
} |
|
||||||
} else { |
|
||||||
// Empty tag or other tag type or text or properly nested element
|
|
||||||
newConfigs.Add(new Configuration { // Ignore (cost 0)
|
|
||||||
StartTags = oldStartTags, |
|
||||||
Document = oldDocument.Push(obj), |
|
||||||
Cost = oldCost, |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Log("New configurations:" + newConfigs.ToString());
|
|
||||||
|
|
||||||
Configurations bestNewConfigurations = new Configurations( |
|
||||||
newConfigs.Values.OrderBy(v => v.Cost).Take(maxConfigurationCount) |
|
||||||
); |
|
||||||
|
|
||||||
// AXmlParser.Log("Best new configurations:" + bestNewConfigurations.ToString());
|
|
||||||
|
|
||||||
return bestNewConfigurations; |
|
||||||
} |
|
||||||
|
|
||||||
#region Helper methods
|
|
||||||
/* |
|
||||||
string PrintObjects(IEnumerable<AXmlObject> objs) |
|
||||||
{ |
|
||||||
StringBuilder sb = new StringBuilder(); |
|
||||||
foreach(AXmlObject obj in objs) { |
|
||||||
if (obj is AXmlTag) { |
|
||||||
if (obj == StartTagPlaceholder) { |
|
||||||
sb.Append("#StartTag#"); |
|
||||||
} else if (obj == EndTagPlaceholder) { |
|
||||||
sb.Append("#EndTag#"); |
|
||||||
} else { |
|
||||||
sb.Append(((AXmlTag)obj).OpeningBracket); |
|
||||||
sb.Append(((AXmlTag)obj).Name); |
|
||||||
sb.Append(((AXmlTag)obj).ClosingBracket); |
|
||||||
} |
|
||||||
} else if (obj is AXmlElement) { |
|
||||||
sb.Append('['); |
|
||||||
sb.Append(PrintObjects(((AXmlElement)obj).Children)); |
|
||||||
sb.Append(']'); |
|
||||||
} else if (obj is AXmlText) { |
|
||||||
sb.Append('~'); |
|
||||||
} else { |
|
||||||
throw new InternalException("Should not be here: " + obj); |
|
||||||
} |
|
||||||
} |
|
||||||
return sb.ToString(); |
|
||||||
} |
|
||||||
*/ |
|
||||||
#endregion
|
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,27 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ICSharpCode.Core.Presentation; |
||||||
|
using ICSharpCode.TreeView; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom.ClassBrowser |
||||||
|
{ |
||||||
|
public class AssemblyLoadErrorTreeNode : SharpTreeNode |
||||||
|
{ |
||||||
|
public override object Text { |
||||||
|
get { |
||||||
|
return "(Assembly not loadable)"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override object Icon { |
||||||
|
get { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Event handler for the <see cref="IModelCollection{T}.CollectionChanged"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// We don't use the classic 'EventArgs' model for this event, because a EventArgs-class couldn't be covariant.
|
||||||
|
/// </remarks>
|
||||||
|
public delegate void ModelCollectionChangedEventHandler<in T>(IReadOnlyCollection<T> removedItems, IReadOnlyCollection<T> addedItems); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for <see cref="IModelCollection.CollectionChanged"/> implementations.
|
||||||
|
/// This is necessary because <c>Delegate.Combine</c> does not work with
|
||||||
|
/// co-/contravariant delegates.
|
||||||
|
/// </summary>
|
||||||
|
public class ModelCollectionChangedEvent<T> |
||||||
|
{ |
||||||
|
List<ModelCollectionChangedEventHandler<T>> _handlers = new List<ModelCollectionChangedEventHandler<T>>(); |
||||||
|
|
||||||
|
public void AddHandler(ModelCollectionChangedEventHandler<T> handler) |
||||||
|
{ |
||||||
|
if (handler != null) |
||||||
|
_handlers.Add(handler); |
||||||
|
} |
||||||
|
|
||||||
|
public void RemoveHandler(ModelCollectionChangedEventHandler<T> handler) |
||||||
|
{ |
||||||
|
_handlers.Remove(handler); |
||||||
|
} |
||||||
|
|
||||||
|
public void Fire(IReadOnlyCollection<T> removedItems, IReadOnlyCollection<T> addedItems) |
||||||
|
{ |
||||||
|
foreach (var handler in _handlers.ToArray()) { |
||||||
|
handler(removedItems, addedItems); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public bool ContainsHandlers { |
||||||
|
get { |
||||||
|
return _handlers.Count > 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,59 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Linq; |
||||||
|
using System.IO; |
||||||
|
using ICSharpCode.NRefactory; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
public static class ModelFactoryExtensions |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="ICSharpCode.SharpDevelop.Dom.IAssemblyModel"/> from a file name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelFactory">Model factory.</param>
|
||||||
|
/// <param name="fileName">Assembly file name.</param>
|
||||||
|
/// <returns>Created <see cref="ICSharpCode.SharpDevelop.Dom.IAssemblyModel"/>.</returns>
|
||||||
|
public static IAssemblyModel CreateAssemblyModelFromFile(this IModelFactory modelFactory, string fileName) |
||||||
|
{ |
||||||
|
var loader = new CecilLoader(); |
||||||
|
loader.IncludeInternalMembers = true; |
||||||
|
loader.LazyLoad = true; |
||||||
|
var assembly = loader.LoadAssemblyFile(fileName); |
||||||
|
|
||||||
|
IEntityModelContext context = new AssemblyEntityModelContext(assembly); |
||||||
|
IAssemblyModel model = modelFactory.CreateAssemblyModel(context); |
||||||
|
if (model is IUpdateableAssemblyModel) { |
||||||
|
((IUpdateableAssemblyModel)model).Update(EmptyList<IUnresolvedTypeDefinition>.Instance, assembly.TopLevelTypeDefinitions.ToList()); |
||||||
|
((IUpdateableAssemblyModel) model).AssemblyName = assembly.AssemblyName; |
||||||
|
} |
||||||
|
return model; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="ICSharpCode.SharpDevelop.Dom.IAssemblyModel"/> from a file name and catches
|
||||||
|
/// errors by showing messages to user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelFactory">Model factory.</param>
|
||||||
|
/// <param name="fileName">Assembly file name.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Created <see cref="ICSharpCode.SharpDevelop.Dom.IAssemblyModel"/> or <b>null</b>,
|
||||||
|
/// if model couldn't be created.
|
||||||
|
/// </returns>
|
||||||
|
public static IAssemblyModel SafelyCreateAssemblyModelFromFile(this IModelFactory modelFactory, string fileName) |
||||||
|
{ |
||||||
|
try { |
||||||
|
return modelFactory.CreateAssemblyModelFromFile(fileName); |
||||||
|
} catch (BadImageFormatException) { |
||||||
|
SD.MessageService.ShowWarningFormatted("${res:ICSharpCode.SharpDevelop.Dom.AssemblyInvalid}", Path.GetFileName(fileName)); |
||||||
|
} catch (FileNotFoundException) { |
||||||
|
SD.MessageService.ShowWarningFormatted("${res:ICSharpCode.SharpDevelop.Dom.AssemblyNotAccessible}", fileName); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.AvalonEdit.Highlighting; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Workbench |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The 'Output' pad.
|
||||||
|
/// Allows showing a text log to the user.
|
||||||
|
/// </summary>
|
||||||
|
public interface IOutputPad |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Opens the pad.
|
||||||
|
/// </summary>
|
||||||
|
void BringToFront(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new output category.
|
||||||
|
/// </summary>
|
||||||
|
IOutputCategory CreateCategory(string displayName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes an existing output category.
|
||||||
|
/// </summary>
|
||||||
|
void RemoveCategory(IOutputCategory category); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the current category.
|
||||||
|
/// This property is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
IOutputCategory CurrentCategory { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "Build" category.
|
||||||
|
/// </summary>
|
||||||
|
IOutputCategory BuildCategory { get; } |
||||||
|
} |
||||||
|
|
||||||
|
public interface IOutputCategory |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the display name of this category.
|
||||||
|
/// </summary>
|
||||||
|
string DisplayName { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates this output category in the UI.
|
||||||
|
/// </summary>
|
||||||
|
void Activate(bool bringPadToFront = false); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all text in the category.
|
||||||
|
/// </summary>
|
||||||
|
void Clear(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends text to this category.
|
||||||
|
/// </summary>
|
||||||
|
void AppendText(RichText text); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends text to this category, followed by a newline.
|
||||||
|
/// </summary>
|
||||||
|
void AppendLine(RichText text); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue