147 lines
6.4 KiB
C#
Raw Normal View History

2021-09-20 18:20:01 +02:00
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Media;
using dnSpy.Contracts.Text;
using dnSpy.Contracts.Text.Classification;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
// Colorizes all text files.
// All "if" words use the Yellow color (default: no background, yellow foreground color)
// All 2 letter words use green background (foreground not changed)
// All 3 letter words use a white foreground and a red background
// All 4 letter words use the Error color (default: no background, red foreground color)
namespace Example2.Extension {
// Define our classification types. A classification type is converted to a color
static class Constants {
// Use unique names
public const string Color1_ClassificationTypeName = "Example2.Extension.Color1";
public const string Color2_ClassificationTypeName = "Example2.Extension.Color2";
// Disable compiler warnings. The fields aren't referenced, just exported so
// the metadata can be added to some table. The fields will always be null.
#pragma warning disable CS0169
// Export the classes that define the name, and base types
[Export(typeof(ClassificationTypeDefinition))]
[Name(Color1_ClassificationTypeName)]
[BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)]
static ClassificationTypeDefinition? Color1ClassificationTypeDefinition;
[Export(typeof(ClassificationTypeDefinition))]
[Name(Color2_ClassificationTypeName)]
[BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)]
static ClassificationTypeDefinition? Color2ClassificationTypeDefinition;
#pragma warning restore CS0169
// Export the classes that define the colors and order
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = Color1_ClassificationTypeName)]
[Name("My Color #1")]
[UserVisible(true)]
[Order(After = Priority.High)]
sealed class Color1ClassificationFormatDefinition : ClassificationFormatDefinition {
Color1ClassificationFormatDefinition() => BackgroundBrush = Brushes.Green;
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = Color2_ClassificationTypeName)]
[Name("My Color #2")]
[UserVisible(true)]
[Order(After = Priority.High)]
sealed class Color2ClassificationFormatDefinition : ClassificationFormatDefinition {
Color2ClassificationFormatDefinition() {
ForegroundBrush = Brushes.White;
BackgroundBrush = Brushes.Red;
}
}
}
// Export our tagger provider. Each time a new text editor is created with our supported
// content type (TEXT), this class gets called to create the tagger.
[Export(typeof(ITaggerProvider))]
[TagType(typeof(IClassificationTag))]
[ContentType(ContentTypes.Text)]
sealed class TextTaggerProvider : ITaggerProvider {
readonly IClassificationTypeRegistryService classificationTypeRegistryService;
[ImportingConstructor]
TextTaggerProvider(IClassificationTypeRegistryService classificationTypeRegistryService) => this.classificationTypeRegistryService = classificationTypeRegistryService;
public ITagger<T>? CreateTagger<T>(ITextBuffer buffer) where T : ITag =>
// All text content types (including C#/VB code) derive from the TEXT content
// type, so our tagger will get called to colorize every text file that's shown
// in a text editor.
new TextTagger(classificationTypeRegistryService) as ITagger<T>;
}
// This class gets called to colorize supported files
sealed class TextTagger : ITagger<IClassificationTag> {
// We don't raise it, so add empty add/remove methods to prevent compiler warnings.
// This event must be raised when you detect changes to spans in the document. If
// your GetTags() method does async work, you should raise it when the async work
// is completed.
public event EventHandler<SnapshotSpanEventArgs>? TagsChanged {
add { }
remove { }
}
readonly IClassificationType color1;
readonly IClassificationType color2;
readonly IClassificationType color3;
readonly IClassificationType color4;
public TextTagger(IClassificationTypeRegistryService classificationTypeRegistryService) {
// Get the classification types we need
color1 = classificationTypeRegistryService.GetClassificationType(Constants.Color1_ClassificationTypeName);
color2 = classificationTypeRegistryService.GetClassificationType(Constants.Color2_ClassificationTypeName);
// Get some classification types created by some other code
color3 = classificationTypeRegistryService.GetClassificationType(ThemeClassificationTypeNames.Yellow);
color4 = classificationTypeRegistryService.GetClassificationType(ThemeClassificationTypeNames.Error);
}
// Gets called to colorize a range of the file. It's typically called once per visible line
public IEnumerable<ITagSpan<IClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
if (spans.Count == 0)
yield break;
// All spans have the same snapshot since it's a normalized collection (sorted, and no span intersects any other span)
var snapshot = spans[0].Snapshot;
foreach (var span in spans) {
foreach (var word in GetWords(span.GetText())) {
// Create a new span. word.Item2 is the offset within the string, so add span.Start to
// get the offset in the snapshot.
var wordSpan = new SnapshotSpan(snapshot, new Span(span.Span.Start + word.offset, word.word.Length));
if (word.word == "if")
yield return new TagSpan<IClassificationTag>(wordSpan, new ClassificationTag(color3));
else if (word.word.Length == 2)
yield return new TagSpan<IClassificationTag>(wordSpan, new ClassificationTag(color1));
else if (word.word.Length == 3)
yield return new TagSpan<IClassificationTag>(wordSpan, new ClassificationTag(color2));
else if (word.word.Length == 4)
yield return new TagSpan<IClassificationTag>(wordSpan, new ClassificationTag(color4));
else {
// Ignore the rest
}
}
}
}
IEnumerable<(string word, int offset)> GetWords(string s) {
int offset = 0;
for (;;) {
while (offset < s.Length && char.IsWhiteSpace(s[offset]))
offset++;
int wordOffset = offset;
while (offset < s.Length && !char.IsWhiteSpace(s[offset]))
offset++;
if (wordOffset == offset)
break;
yield return (s.Substring(wordOffset, offset - wordOffset), wordOffset);
}
}
}
}