我最终想出了一个解决方案,至少对于 VS2010 是这样。虽然我已经用它来为 ' #region
' 和 ' #endregion
' 标签着色,但类似的解决方案应该适用于 Visual Studio 窗口中的任何文本内容。
似乎可以通过创建一个IViewTaggerProvider
将源代码的“分类”部分“标记”来解决此类问题。Visual Studio 将为标记有该分类的文本提供样式,然后用户可以通过Tools > Options... > Environment > Fonts and Colors将其更改为所需的样式。
标记器提供程序如下所示:
[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
事件,当提供的文本视图的内容发生变化时(例如,因为用户输入了某些内容)会触发该事件。
然后它必须在文本中搜索它感兴趣的文本区域(称为“跨度”)。#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));
}
}
}
为简洁起见,我省略了命名空间和 using 语句,它们通常采用Microsoft.VisualStudio.Text.*
. 要使这些可用,必须首先下载Visual Studio 2010 SDK 。
在过去的几个月里,我一直在使用这个解决方案,没有任何问题。
我注意到的一个限制是颜色不是“混合”的,因此不透明度小于 100% 的颜色不会“淡出”跨度中的现有颜色——这可能有助于保留语法突出显示。
我也不太了解它的效率,因为它看起来会在每次按键时重复搜索文档。我还没有进行研究以查看 Visual Studio 是否以某种方式对其进行了优化。我确实注意到大文件(> ~1000 行)上的 Visual Studio 速度变慢,但我也使用 Resharper,所以我不能单独将此归因于这个插件。
由于这主要是使用猜测进行编码,因此我欢迎任何可以澄清或简化事情或改进代码性能的评论或代码更改。