Skip to content

Commit

Permalink
Use ITextElementFactory or ITextElementProvider in more places
Browse files Browse the repository at this point in the history
Replace references to `TextBlockFactory` with references to those services as they can be more efficient by creating `FastTextBlock` whenever possible.
  • Loading branch information
ElektroKill committed May 11, 2024
1 parent 076a3c0 commit 4556508
Show file tree
Hide file tree
Showing 18 changed files with 138 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using dnSpy.Contracts.Images;
using dnSpy.Contracts.MVVM;
using dnSpy.Contracts.Text.Classification;
Expand Down Expand Up @@ -51,7 +51,7 @@ sealed class InformationQuickInfoContentVM : ViewModelBase {
public object? ExceptionObject { get; }
public bool HasExceptionObject => ExceptionObject is not null;

public InformationQuickInfoContentVM(ITextView textView, InformationQuickInfoContent content, IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService) {
public InformationQuickInfoContentVM(ITextView textView, InformationQuickInfoContent content, IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService, ITextElementFactory textElementFactory) {
if (textView is null)
throw new ArgumentNullException(nameof(textView));
if (content is null)
Expand All @@ -65,30 +65,32 @@ public InformationQuickInfoContentVM(ITextView textView, InformationQuickInfoCon
SymbolImageReference = content.SymbolGlyph.Value.GetImageReference() ?? default;
if (content.WarningGlyph is not null)
WarningImageReference = content.WarningGlyph.Value.GetImageReference() ?? default;
MainDescriptionObject = TryCreateObject(sb, content.MainDescription, classificationFormatMap, themeClassificationTypeService);
DocumentationObject = TryCreateObject(sb, content.Documentation, classificationFormatMap, themeClassificationTypeService);
UsageObject = TryCreateObject(sb, content.UsageText, classificationFormatMap, themeClassificationTypeService);
TypeParameterMapObject = TryCreateObject(sb, content.TypeParameterMap, classificationFormatMap, themeClassificationTypeService);
AnonymousTypesObject = TryCreateObject(sb, content.AnonymousTypes, classificationFormatMap, themeClassificationTypeService);
ExceptionObject = TryCreateObject(sb, content.ExceptionText, classificationFormatMap, themeClassificationTypeService);
MainDescriptionObject = TryCreateObject(sb, content.MainDescription, classificationFormatMap, themeClassificationTypeService, textElementFactory);
DocumentationObject = TryCreateObject(sb, content.Documentation, classificationFormatMap, themeClassificationTypeService, textElementFactory);
UsageObject = TryCreateObject(sb, content.UsageText, classificationFormatMap, themeClassificationTypeService, textElementFactory);
TypeParameterMapObject = TryCreateObject(sb, content.TypeParameterMap, classificationFormatMap, themeClassificationTypeService, textElementFactory);
AnonymousTypesObject = TryCreateObject(sb, content.AnonymousTypes, classificationFormatMap, themeClassificationTypeService, textElementFactory);
ExceptionObject = TryCreateObject(sb, content.ExceptionText, classificationFormatMap, themeClassificationTypeService, textElementFactory);
}

TextBlock? TryCreateObject(StringBuilder sb, ImmutableArray<TaggedText> taggedParts, IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService) {
static FrameworkElement? TryCreateObject(StringBuilder sb, ImmutableArray<TaggedText> taggedParts, IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService, ITextElementFactory textElementFactory) {
if (taggedParts.IsDefaultOrEmpty)
return null;
var text = ToString(sb, taggedParts);
var propsSpans = CreateTextRunPropertiesAndSpans(taggedParts, classificationFormatMap, themeClassificationTypeService);
return TextBlockFactory.Create(text, classificationFormatMap.DefaultTextProperties, propsSpans, TextBlockFactory.Flags.DisableSetTextBlockFontFamily | TextBlockFactory.Flags.DisableFontSize);
return textElementFactory.Create(classificationFormatMap, text, CreateTextClassificationTags(taggedParts, themeClassificationTypeService), TextElementFlags.None);
}

IEnumerable<TextRunPropertiesAndSpan> CreateTextRunPropertiesAndSpans(ImmutableArray<TaggedText> taggedParts, IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService) {
static IList<TextClassificationTag> CreateTextClassificationTags(ImmutableArray<TaggedText> taggedParts, IThemeClassificationTypeService themeClassificationTypeService) {
int pos = 0;
foreach (var part in taggedParts) {
var result = new TextClassificationTag[taggedParts.Length];
for (int i = 0; i < taggedParts.Length; i++) {
var part = taggedParts[i];
var color = TextTagsHelper.ToTextColor(part.Tag);
var classificationType = themeClassificationTypeService.GetClassificationType(color);
yield return new TextRunPropertiesAndSpan(new Span(pos, part.Text.Length), classificationFormatMap.GetTextProperties(classificationType));
result[i] = new TextClassificationTag(new Span(pos, part.Text.Length), classificationType);
pos += part.Text.Length;
}
return result;
}

static string ToString(StringBuilder sb, ImmutableArray<TaggedText> taggedParts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,28 @@ interface IQuickInfoContentCreator {
sealed class QuickInfoContentCreatorProvider : IQuickInfoContentCreatorProvider {
readonly IClassificationFormatMapService classificationFormatMapService;
readonly IThemeClassificationTypeService themeClassificationTypeService;
readonly ITextElementFactory textElementFactory;

[ImportingConstructor]
QuickInfoContentCreatorProvider(IClassificationFormatMapService classificationFormatMapService, IThemeClassificationTypeService themeClassificationTypeService) {
QuickInfoContentCreatorProvider(IClassificationFormatMapService classificationFormatMapService, IThemeClassificationTypeService themeClassificationTypeService, ITextElementFactory textElementFactory) {
this.classificationFormatMapService = classificationFormatMapService;
this.themeClassificationTypeService = themeClassificationTypeService;
this.textElementFactory = textElementFactory;
}

public IQuickInfoContentCreator Create(ITextView textView) => new QuickInfoContentCreator(classificationFormatMapService.GetClassificationFormatMap(AppearanceCategoryConstants.UIMisc), themeClassificationTypeService, textView);
public IQuickInfoContentCreator Create(ITextView textView) => new QuickInfoContentCreator(classificationFormatMapService.GetClassificationFormatMap(AppearanceCategoryConstants.UIMisc), themeClassificationTypeService, textElementFactory, textView);
}

sealed class QuickInfoContentCreator : IQuickInfoContentCreator {
readonly IClassificationFormatMap classificationFormatMap;
readonly IThemeClassificationTypeService themeClassificationTypeService;
readonly ITextView textView;
readonly ITextElementFactory textElementFactory;

public QuickInfoContentCreator(IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService, ITextView textView) {
public QuickInfoContentCreator(IClassificationFormatMap classificationFormatMap, IThemeClassificationTypeService themeClassificationTypeService, ITextElementFactory textElementFactory, ITextView textView) {
this.classificationFormatMap = classificationFormatMap ?? throw new ArgumentNullException(nameof(classificationFormatMap));
this.themeClassificationTypeService = themeClassificationTypeService ?? throw new ArgumentNullException(nameof(themeClassificationTypeService));
this.textElementFactory = textElementFactory ?? throw new ArgumentNullException(nameof(textElementFactory));
this.textView = textView ?? throw new ArgumentNullException(nameof(textView));
}

Expand All @@ -80,7 +84,7 @@ public IEnumerable<object> Create(QuickInfoItem item) {

IEnumerable<object> Create(InformationQuickInfoContent content) {
yield return new InformationQuickInfoContentControl {
DataContext = new InformationQuickInfoContentVM(textView, content, classificationFormatMap, themeClassificationTypeService),
DataContext = new InformationQuickInfoContentVM(textView, content, classificationFormatMap, themeClassificationTypeService, textElementFactory),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@ You should have received a copy of the GNU General Public License

using System;
using System.Collections.Immutable;
using System.Windows.Controls;
using System.Windows;
using Microsoft.CodeAnalysis;

namespace dnSpy.Roslyn.Text.Classification {
/// <summary>
/// Creates a <see cref="TextBlock"/>. Call its <see cref="IDisposable.Dispose"/> method
/// Creates a <see cref="FrameworkElement"/> which displays the given tagged text. Call its <see cref="IDisposable.Dispose"/> method
/// to clean up its resources.
/// </summary>
interface ITaggedTextElementProvider : IDisposable {
/// <summary>
/// Creates a <see cref="TextBlock"/>
/// Creates a <see cref="FrameworkElement"/> which displays the tagged text
/// </summary>
/// <param name="tag">Tag, can be null</param>
/// <param name="taggedParts">Tagged parts to classify</param>
/// <param name="colorize">true if it should be colorized</param>
/// <returns></returns>
TextBlock Create(string tag, ImmutableArray<TaggedText> taggedParts, bool colorize);
FrameworkElement Create(string tag, ImmutableArray<TaggedText> taggedParts, bool colorize);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,29 @@ You should have received a copy of the GNU General Public License

using System;
using System.Collections.Immutable;
using System.Linq;
using System.Windows.Controls;
using System.Windows;
using dnSpy.Contracts.Text.Classification;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace dnSpy.Roslyn.Text.Classification {
sealed class TaggedTextElementProvider : ITaggedTextElementProvider {
readonly ITextClassifierAggregator classifierAggregator;
readonly IContentType contentType;
readonly IClassificationFormatMap classificationFormatMap;
readonly ITextElementProvider textElementProvider;

public TaggedTextElementProvider(IContentType contentType, ITextClassifierAggregatorService textClassifierAggregatorService, IClassificationFormatMap classificationFormatMap) {
if (contentType is null)
throw new ArgumentNullException(nameof(contentType));
if (textClassifierAggregatorService is null)
throw new ArgumentNullException(nameof(textClassifierAggregatorService));
classifierAggregator = textClassifierAggregatorService.Create(contentType);
public TaggedTextElementProvider(IContentType contentType, IClassificationFormatMap classificationFormatMap, ITextElementProvider textElementProvider) {
this.contentType = contentType ?? throw new ArgumentNullException(nameof(contentType));
this.classificationFormatMap = classificationFormatMap ?? throw new ArgumentNullException(nameof(classificationFormatMap));
this.textElementProvider = textElementProvider ?? throw new ArgumentNullException(nameof(textElementProvider));
}

public TextBlock Create(string tag, ImmutableArray<TaggedText> taggedParts, bool colorize) {
public FrameworkElement Create(string tag, ImmutableArray<TaggedText> taggedParts, bool colorize) {
var context = TaggedTextClassifierContext.Create(tag, taggedParts, colorize);
return TextBlockFactory.Create(context.Text, classificationFormatMap.DefaultTextProperties,
classifierAggregator.GetTags(context).Select(a => new TextRunPropertiesAndSpan(a.Span, classificationFormatMap.GetTextProperties(a.ClassificationType))), TextBlockFactory.Flags.DisableSetTextBlockFontFamily | TextBlockFactory.Flags.DisableFontSize);
return textElementProvider.CreateTextElement(classificationFormatMap, context, contentType, TextElementFlags.None);
}

public void Dispose() => classifierAggregator?.Dispose();
public void Dispose() { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ You should have received a copy of the GNU General Public License
namespace dnSpy.Roslyn.Text.Classification {
[Export(typeof(ITaggedTextElementProviderService))]
sealed class TaggedTextElementProviderService : ITaggedTextElementProviderService {
readonly ITextClassifierAggregatorService textClassifierAggregatorService;
readonly IClassificationFormatMapService classificationFormatMapService;
readonly ITextElementProvider textElementProvider;

[ImportingConstructor]
TaggedTextElementProviderService(ITextClassifierAggregatorService textClassifierAggregatorService, IClassificationFormatMapService classificationFormatMapService) {
this.textClassifierAggregatorService = textClassifierAggregatorService;
TaggedTextElementProviderService(IClassificationFormatMapService classificationFormatMapService, ITextElementProvider textElementProvider) {
this.classificationFormatMapService = classificationFormatMapService;
this.textElementProvider = textElementProvider;
}

public ITaggedTextElementProvider Create(IContentType contentType, string category) {
if (contentType is null)
throw new ArgumentNullException(nameof(contentType));
if (category is null)
throw new ArgumentNullException(nameof(category));
return new TaggedTextElementProvider(contentType, textClassifierAggregatorService, classificationFormatMapService.GetClassificationFormatMap(category));
return new TaggedTextElementProvider(contentType, classificationFormatMapService.GetClassificationFormatMap(category), textElementProvider);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License

using System.Windows;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace dnSpy.Contracts.Text.Classification {
/// <summary>
Expand All @@ -34,5 +35,15 @@ public interface ITextElementProvider {
/// <param name="flags">Flags</param>
/// <returns></returns>
FrameworkElement CreateTextElement(IClassificationFormatMap classificationFormatMap, TextClassifierContext context, string contentType, TextElementFlags flags);

/// <summary>
/// Creates a WPF text element
/// </summary>
/// <param name="classificationFormatMap">Classification format map</param>
/// <param name="context">Text classifier context</param>
/// <param name="contentType">Content type</param>
/// <param name="flags">Flags</param>
/// <returns></returns>
FrameworkElement CreateTextElement(IClassificationFormatMap classificationFormatMap, TextClassifierContext context, IContentType contentType, TextElementFlags flags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public enum Flags {
FilterOutNewlines = 8,
}

static string ToString(string s, bool filterOutNewLines) {
internal static string ToString(string s, bool filterOutNewLines) {
if (!filterOutNewLines)
return s;
if (s.IndexOfAny(LineConstants.newLineChars) < 0)
Expand All @@ -79,6 +79,20 @@ static string ToString(string s, bool filterOutNewLines) {
return sb.ToString();
}

static string ToString(string s, int startIndex, int length, bool filterOutNewLines) {
if (!filterOutNewLines)
return s.Substring(startIndex, length);
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
var c = s[startIndex + i];
if (Array.IndexOf(LineConstants.newLineChars, c) >= 0)
sb.Append(' ');
else
sb.Append(c);
}
return sb.ToString();
}

/// <summary>
/// Creates a <see cref="TextBlock"/>
/// </summary>
Expand Down Expand Up @@ -147,12 +161,12 @@ public static TextBlock Create(string text, TextFormattingRunProperties defaultP
int textOffset = 0;
foreach (var tag in propsAndSpansList) {
if (textOffset < tag.Span.Start)
textBlock.Inlines.Add(CreateRun(ToString(text.Substring(textOffset, tag.Span.Start - textOffset), filterOutNewlines), defaultProperties, null, flags));
textBlock.Inlines.Add(CreateRun(ToString(text.Substring(tag.Span.Start, tag.Span.Length), filterOutNewlines), defaultProperties, tag.Properties, flags));
textBlock.Inlines.Add(CreateRun(ToString(text, textOffset, tag.Span.Start - textOffset, filterOutNewlines), defaultProperties, null, flags));
textBlock.Inlines.Add(CreateRun(ToString(text, tag.Span.Start, tag.Span.Length, filterOutNewlines), defaultProperties, tag.Properties, flags));
textOffset = tag.Span.End;
}
if (textOffset < text.Length)
textBlock.Inlines.Add(CreateRun(ToString(text.Substring(textOffset), filterOutNewlines), defaultProperties, null, flags));
textBlock.Inlines.Add(CreateRun(ToString(text, textOffset, text.Length - textOffset, filterOutNewlines), defaultProperties, null, flags));
}

propsAndSpansList.Clear();
Expand Down
22 changes: 3 additions & 19 deletions dnSpy/dnSpy/Controls/TextElementFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ You should have received a copy of the GNU General Public License
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
Expand All @@ -43,21 +42,6 @@ public FrameworkElement Create(IClassificationFormatMap classificationFormatMap,
}

static class TextElementFactory {
static string ToString(string s, bool filterOutNewLines) {
if (!filterOutNewLines)
return s;
if (s.IndexOfAny(LineConstants.newLineChars) < 0)
return s;
var sb = new StringBuilder(s.Length);
foreach (var c in s) {
if (Array.IndexOf(LineConstants.newLineChars, c) >= 0)
sb.Append(' ');
else
sb.Append(c);
}
return sb.ToString();
}

static TextTrimming GetTextTrimming(TextElementFlags flags) {
switch (flags & TextElementFlags.TrimmingMask) {
case TextElementFlags.NoTrimming: return TextTrimming.None;
Expand All @@ -82,7 +66,7 @@ public static FrameworkElement Create(IClassificationFormatMap classificationFor
if (tags.Count != 0) {
if (useFastTextBlock) {
return new FastTextBlock(new TextSrc {
text = ToString(WpfUnicodeUtils.ReplaceBadChars(text), filterOutNewLines),
text = TextBlockFactory.ToString(WpfUnicodeUtils.ReplaceBadChars(text), filterOutNewLines),
classificationFormatMap = classificationFormatMap,
tagsList = tags.ToArray(),
});
Expand All @@ -98,12 +82,12 @@ public static FrameworkElement Create(IClassificationFormatMap classificationFor
FrameworkElement fwElem;
if (useFastTextBlock) {
fwElem = new FastTextBlock() {
Text = ToString(WpfUnicodeUtils.ReplaceBadChars(text), filterOutNewLines)
Text = TextBlockFactory.ToString(WpfUnicodeUtils.ReplaceBadChars(text), filterOutNewLines)
};
}
else {
fwElem = new TextBlock {
Text = ToString(WpfUnicodeUtils.ReplaceBadChars(text), filterOutNewLines),
Text = TextBlockFactory.ToString(WpfUnicodeUtils.ReplaceBadChars(text), filterOutNewLines),
TextTrimming = GetTextTrimming(flags),
TextWrapping = GetTextWrapping(flags),
};
Expand Down
Loading

0 comments on commit 4556508

Please sign in to comment.