Introduce 'RichText' class and use it in new output pad API.

This commit is contained in:
Daniel Grunwald 2013-07-21 23:40:57 +02:00
Родитель 04b796ccb6
Коммит c004edc4a4
7 изменённых файлов: 309 добавлений и 56 удалений

Просмотреть файл

@ -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 &lt;span style="..."&gt; 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 &lt;span style="..."&gt; 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>