Я в конце концов нашел решение, по крайней мере, для VS2010.
Хотя я использовал это для окрашивания тегов '#region
' и '#endregion
', аналогичное решение должно быть применимо для любого текстового содержимого в окне Visual Studio.
Похоже, что проблему такого рода можно решить, создав IViewTaggerProvider
, который будет «помечать» части исходного кода «классификацией».
Visual Studio предоставит стиль для текста, помеченного этой классификацией, который затем может быть изменен пользователем на нужный стиль с помощью Инструменты> Параметры ...> Среда> Шрифты и цвета .
Поставщик Tagger выглядит так:
[Export(typeof(IViewTaggerProvider))]
[ContentType("any")]
[TagType(typeof(ClassificationTag))]
public sealed class RegionTaggerProvider : IViewTaggerProvider
{
[Import]
public IClassificationTypeRegistryService Registry;
[Import]
internal ITextSearchService TextSearchService { get; set; }
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
{
if (buffer != textView.TextBuffer)
return null;
var classType = Registry.GetClassificationType("region-foreground");
return new RegionTagger(textView, TextSearchService, classType) as ITagger<T>;
}
}
При этом создается объект ITagger
, который, учитывая текстовое представление Visual Studio, будет помечать части текста заданным типом классификации. Обратите внимание, что это будет работать для всех текстовых представлений (то есть редактора исходного кода, окон «Найти результаты» и т. Д.). Это можно изменить, отредактировав атрибут ContentType
(просто C#
?).
Тип классификации (в данном случае «регион-передний план») определяется как:
public static class TypeExports
{
[Export(typeof(ClassificationTypeDefinition))]
[Name("region-foreground")]
public static ClassificationTypeDefinition OrdinaryClassificationType;
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "region-foreground")]
[Name("region-foreground")]
[UserVisible(true)]
[Order(After = Priority.High)]
public sealed class RegionForeground : ClassificationFormatDefinition
{
public RegionForeground()
{
DisplayName = "Region Foreground";
ForegroundColor = Colors.Gray;
}
}
Атрибут Order
определяет, когда будет применяться классификация, по сравнению с другими классификациями, которые могут также применяться к диапазону текста.
DisplayName
будет использоваться в диалоговом окне Инструменты> Параметры ... .
Как только классификация определена, класс ITagger
может осуществлять поиск текста представления и предоставлять классификации для применимых разделов текста, который он находит.
Проще говоря, его работа заключается в прослушивании события ViewLayoutChanged
текстового представления, которое запускается при изменении содержимого предоставленного текстового представления (например, потому что пользователь что-то набрал).
Затем он должен искать в тексте интересующую его область текста (называемую «span»). Здесь он возвращает отрезки строк, содержащие либо #region
, либо #endregion
. Я сохранил это просто, но TextSearchService
, используемый для поиска совпадений, также может искать с помощью регулярных выражений.
Наконец, Visual Studio предоставляет метод для извлечения тегов найденного текста, который называется GetTags()
. Для данной коллекции промежутков будут возвращаться текстовые промежутки с тегами классификации, то есть области тех промежутков, которые должны быть классифицированы определенным образом.
Его код:
public sealed class RegionTagger : ITagger<ClassificationTag>
{
private readonly ITextView m_View;
private readonly ITextSearchService m_SearchService;
private readonly IClassificationType m_Type;
private NormalizedSnapshotSpanCollection m_CurrentSpans;
public event EventHandler<SnapshotSpanEventArgs> TagsChanged = delegate { };
public RegionTagger(ITextView view, ITextSearchService searchService, IClassificationType type)
{
m_View = view;
m_SearchService = searchService;
m_Type = type;
m_CurrentSpans = GetWordSpans(m_View.TextSnapshot);
m_View.GotAggregateFocus += SetupSelectionChangedListener;
}
private void SetupSelectionChangedListener(object sender, EventArgs e)
{
if (m_View != null)
{
m_View.LayoutChanged += ViewLayoutChanged;
m_View.GotAggregateFocus -= SetupSelectionChangedListener;
}
}
private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
if (e.OldSnapshot != e.NewSnapshot)
{
m_CurrentSpans = GetWordSpans(e.NewSnapshot);
TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(e.NewSnapshot, 0, e.NewSnapshot.Length)));
}
}
private NormalizedSnapshotSpanCollection GetWordSpans(ITextSnapshot snapshot)
{
var wordSpans = new List<SnapshotSpan>();
wordSpans.AddRange(FindAll(@"#region", snapshot).Select(regionLine => regionLine.Start.GetContainingLine().Extent));
wordSpans.AddRange(FindAll(@"#endregion", snapshot).Select(regionLine => regionLine.Start.GetContainingLine().Extent));
return new NormalizedSnapshotSpanCollection(wordSpans);
}
private IEnumerable<SnapshotSpan> FindAll(String searchPattern, ITextSnapshot textSnapshot)
{
if (textSnapshot == null)
return null;
return m_SearchService.FindAll(
new FindData(searchPattern, textSnapshot) {
FindOptions = FindOptions.WholeWord | FindOptions.MatchCase
});
}
public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
if (spans == null || spans.Count == 0 || m_CurrentSpans.Count == 0)
yield break;
ITextSnapshot snapshot = m_CurrentSpans[0].Snapshot;
spans = new NormalizedSnapshotSpanCollection(spans.Select(s => s.TranslateTo(snapshot, SpanTrackingMode.EdgeExclusive)));
foreach (var span in NormalizedSnapshotSpanCollection.Intersection(m_CurrentSpans, spans))
{
yield return new TagSpan<ClassificationTag>(span, new ClassificationTag(m_Type));
}
}
}
Для краткости я опустил пространства имен и операторы использования, которые обычно имеют форму Microsoft.VisualStudio.Text.*
. Чтобы они были доступны, сначала необходимо загрузить Visual Studio 2010 SDK .
Последние несколько месяцев я пользуюсь этим решением без проблем.
Я заметил одно ограничение: цвета не «смешиваются», поэтому цвет с непрозрачностью менее 100% не «затухает» существующие цвета в диапазоне, что может быть полезно для сохранения подсветки синтаксиса.
Я также мало представляю его эффективность, так как похоже, что он будет многократно искать документ при каждом нажатии клавиши. Я не провел исследования, чтобы увидеть, оптимизирует ли Visual Studio это как-то. Я замечаю замедление Visual Studio для больших файлов (> ~ 1000 строк), но я также использую Resharper, поэтому я не могу приписать это только этому плагину.
Поскольку это было написано в основном с использованием догадок, я приветствую любые комментарии или изменения кода, которые могли бы прояснить или упростить вещи или улучшить производительность кода.