Introduce 'RichText' class and use it in new output pad API.
This commit is contained in:
Родитель
04b796ccb6
Коммит
c004edc4a4
|
@ -9,6 +9,7 @@ using System.Text;
|
|||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using ICSharpCode.NRefactory.TypeSystem.Implementation;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
|
@ -23,29 +24,18 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// </remarks>
|
||||
public sealed class HighlightedInlineBuilder
|
||||
{
|
||||
sealed class HighlightingState
|
||||
static HighlightingBrush MakeBrush(Brush b)
|
||||
{
|
||||
internal Brush Foreground;
|
||||
internal Brush Background;
|
||||
internal FontFamily Family;
|
||||
internal FontWeight? Weight;
|
||||
internal FontStyle? Style;
|
||||
|
||||
public HighlightingState Clone()
|
||||
{
|
||||
return new HighlightingState {
|
||||
Foreground = this.Foreground,
|
||||
Background = this.Background,
|
||||
Family = this.Family,
|
||||
Weight = this.Weight,
|
||||
Style = this.Style
|
||||
};
|
||||
}
|
||||
SolidColorBrush scb = b as SolidColorBrush;
|
||||
if (scb != null)
|
||||
return new SimpleHighlightingBrush(scb);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly string text;
|
||||
List<int> stateChangeOffsets = new List<int>();
|
||||
List<HighlightingState> stateChanges = new List<HighlightingState>();
|
||||
List<HighlightingColor> stateChanges = new List<HighlightingColor>();
|
||||
|
||||
int GetIndexForOffset(int offset)
|
||||
{
|
||||
|
@ -71,10 +61,22 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
throw new ArgumentNullException("text");
|
||||
this.text = text;
|
||||
stateChangeOffsets.Add(0);
|
||||
stateChanges.Add(new HighlightingState());
|
||||
stateChanges.Add(new HighlightingColor());
|
||||
}
|
||||
|
||||
HighlightedInlineBuilder(string text, List<int> offsets, List<HighlightingState> states)
|
||||
/// <summary>
|
||||
/// Creates a new HighlightedInlineBuilder instance.
|
||||
/// </summary>
|
||||
public HighlightedInlineBuilder(RichText text)
|
||||
{
|
||||
if (text == null)
|
||||
throw new ArgumentNullException("text");
|
||||
this.text = text.Text;
|
||||
stateChangeOffsets.AddRange(text.stateChangeOffsets);
|
||||
stateChanges.AddRange(text.stateChanges);
|
||||
}
|
||||
|
||||
HighlightedInlineBuilder(string text, List<int> offsets, List<HighlightingColor> states)
|
||||
{
|
||||
this.text = text;
|
||||
stateChangeOffsets = offsets;
|
||||
|
@ -104,15 +106,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
int startIndex = GetIndexForOffset(offset);
|
||||
int endIndex = GetIndexForOffset(offset + length);
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
HighlightingState state = stateChanges[i];
|
||||
if (color.Foreground != null)
|
||||
state.Foreground = color.Foreground.GetBrush(null);
|
||||
if (color.Background != null)
|
||||
state.Background = color.Background.GetBrush(null);
|
||||
if (color.FontStyle != null)
|
||||
state.Style = color.FontStyle;
|
||||
if (color.FontWeight != null)
|
||||
state.Weight = color.FontWeight;
|
||||
stateChanges[i].MergeWith(color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,8 +117,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
{
|
||||
int startIndex = GetIndexForOffset(offset);
|
||||
int endIndex = GetIndexForOffset(offset + length);
|
||||
var hbrush = MakeBrush(brush);
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
stateChanges[i].Foreground = brush;
|
||||
stateChanges[i].Foreground = hbrush;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,8 +130,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
{
|
||||
int startIndex = GetIndexForOffset(offset);
|
||||
int endIndex = GetIndexForOffset(offset + length);
|
||||
var hbrush = MakeBrush(brush);
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
stateChanges[i].Background = brush;
|
||||
stateChanges[i].Background = hbrush;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +144,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
int startIndex = GetIndexForOffset(offset);
|
||||
int endIndex = GetIndexForOffset(offset + length);
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
stateChanges[i].Weight = weight;
|
||||
stateChanges[i].FontWeight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +156,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
int startIndex = GetIndexForOffset(offset);
|
||||
int endIndex = GetIndexForOffset(offset + length);
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
stateChanges[i].Style = style;
|
||||
stateChanges[i].FontStyle = style;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +168,8 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
int startIndex = GetIndexForOffset(offset);
|
||||
int endIndex = GetIndexForOffset(offset + length);
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
stateChanges[i].Family = family;
|
||||
throw new NotSupportedException();
|
||||
//stateChanges[i].FontFamily = family;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,25 +178,15 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// </summary>
|
||||
public Run[] CreateRuns()
|
||||
{
|
||||
Run[] runs = new Run[stateChanges.Count];
|
||||
for (int i = 0; i < runs.Length; i++) {
|
||||
int startOffset = stateChangeOffsets[i];
|
||||
int endOffset = i + 1 < stateChangeOffsets.Count ? stateChangeOffsets[i + 1] : text.Length;
|
||||
Run r = new Run(text.Substring(startOffset, endOffset - startOffset));
|
||||
HighlightingState state = stateChanges[i];
|
||||
if (state.Foreground != null)
|
||||
r.Foreground = state.Foreground;
|
||||
if (state.Background != null)
|
||||
r.Background = state.Background;
|
||||
if (state.Weight != null)
|
||||
r.FontWeight = state.Weight.Value;
|
||||
if (state.Family != null)
|
||||
r.FontFamily = state.Family;
|
||||
if (state.Style != null)
|
||||
r.FontStyle = state.Style.Value;
|
||||
runs[i] = r;
|
||||
}
|
||||
return runs;
|
||||
return ToRichText().CreateRuns();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RichText instance.
|
||||
/// </summary>
|
||||
public RichText ToRichText()
|
||||
{
|
||||
return new RichText(text, stateChangeOffsets.ToArray(), stateChanges.Select(FreezableHelper.GetFrozenClone).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -296,5 +296,13 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RichText"/> that stores the text and highlighting of this line.
|
||||
/// </summary>
|
||||
public RichText ToRichText()
|
||||
{
|
||||
return ToInlineBuilder().ToRichText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Documents;
|
||||
using ICSharpCode.AvalonEdit.Utils;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a immutable piece text with highlighting information.
|
||||
/// </summary>
|
||||
public class RichText
|
||||
{
|
||||
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;
|
||||
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(GetHighlightedSections(0, this.Length));
|
||||
}
|
||||
|
||||
/// <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;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates the specified rich texts.
|
||||
/// </summary>
|
||||
public static RichText Concat(params RichText[] texts)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// <summary>
|
||||
/// Stores rich-text formatting.
|
||||
/// </summary>
|
||||
public sealed class RichTextModel
|
||||
public sealed class RichTextModel // TODO: maybe rename to HighlightingModel?
|
||||
{
|
||||
CompressingTreeList<HighlightingColor> list = new CompressingTreeList<HighlightingColor>(object.Equals);
|
||||
|
||||
|
@ -34,6 +34,16 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
list.InsertRange(0, documentLength, HighlightingColor.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RichTextModel from a CONTIGUOUS list of HighlightedSections.
|
||||
/// </summary>
|
||||
internal RichTextModel(IEnumerable<HighlightedSection> sections)
|
||||
{
|
||||
foreach (var section in sections) {
|
||||
list.InsertRange(section.Offset, section.Length, section.Color);
|
||||
}
|
||||
}
|
||||
|
||||
#region UpdateOffsets
|
||||
/// <summary>
|
||||
/// Updates the start and end offsets of all segments stored in this collection.
|
||||
|
@ -80,7 +90,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
/// <summary>
|
||||
/// Gets the HighlightingColor for the specified offset.
|
||||
/// </summary>
|
||||
public HighlightingColor GetHighlighting(int offset)
|
||||
public HighlightingColor GetHighlightingAt(int offset)
|
||||
{
|
||||
return list[offset];
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
|
|||
if (richTextModel.DocumentLength == 0)
|
||||
currentColor = HighlightingColor.Empty;
|
||||
else
|
||||
currentColor = richTextModel.GetHighlighting(Math.Max(0, insertionOffset - 1));
|
||||
currentColor = richTextModel.GetHighlightingAt(Math.Max(0, insertionOffset - 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -207,6 +207,7 @@
|
|||
<Compile Include="Highlighting\HighlightingSpan.cs" />
|
||||
<Compile Include="Highlighting\IHighlightingDefinitionReferenceResolver.cs">
|
||||
</Compile>
|
||||
<Compile Include="Highlighting\RichText.cs" />
|
||||
<Compile Include="Highlighting\RichTextColorizer.cs" />
|
||||
<Compile Include="Highlighting\RichTextModel.cs" />
|
||||
<Compile Include="Highlighting\RichTextModelWriter.cs" />
|
||||
|
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Utils
|
||||
{
|
||||
|
@ -19,6 +20,26 @@ namespace ICSharpCode.AvalonEdit.Utils
|
|||
/// </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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче