VSIX IClassifier 分配多个 ClassificationTypes
VSIX IClassifier assigning multiple ClassificationTypes
使用标准模板,我设法制作了一个自定义荧光笔,它将所有出现的字符串 "Archive?????Key"(其中 ???? 是变量名称中允许的任何字符集合)变成粉红色。然而,我真正想要的是 "Archive" 和 "Key" 部分变成粉红色,而“????”部分变成栗色。据我了解 VSIX 荧光笔(我真的不了解)这意味着定义两个 ClassificationFormatDefinition
,但每次我尝试都会破坏项目。
我的 GetClassificationSpans
方法(这是与标准模板的唯一显着差异)如下所示:
public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
{
List<ClassificationSpan> spans = new List<ClassificationSpan>();
string text = span.GetText();
int idx0 = 0;
int idx1;
while (true)
{
idx0 = text.IndexOf(keyPrefix, idx0);
if (idx0 < 0)
break;
idx1 = text.IndexOf(keySuffix, idx0 + 6);
if (idx1 < 0)
break;
// TODO: make sure the prefix and suffix are part of the same object identifier.
string name = text.Substring(idx0 + lengthPrefix, idx1 - idx0 - lengthPrefix);
string full = text.Substring(idx0, idx1 - idx0 + keySuffix.Length);
SnapshotSpan span0 = new SnapshotSpan(span.Start + idx0, idx1 - idx0 + lengthSuffix);
SnapshotSpan span1 = new SnapshotSpan(span.Start + idx0 + lengthPrefix, idx1 - idx0 - lengthPrefix);
SnapshotSpan span2 = new SnapshotSpan(span.Start + idx1, lengthSuffix);
spans.Add(new ClassificationSpan(span0, classificationType));
spans.Add(new ClassificationSpan(span1, classificationType)); // I'd like to assign a different IClassificationType to this span.
spans.Add(new ClassificationSpan(span2, classificationType));
idx0 = idx1 + 5;
}
return spans;
}
而 span1
是我要指定不同样式的地方。我不明白分类器、格式、提供者和定义 类 需要如何相互关联,以及哪些可以了解多种样式。
模板适合入门,但通常一旦您知道自己的前进方向,更直接地重新实现所有内容会更简单。
以下是所有部分的组合方式:
- 分类器(实际上,
IClassificationTag
标记器)根据需要为文本缓冲区的给定部分生成分类标记跨度。
- 分类标签跨度由标签适用的缓冲区中的跨度和分类标签本身组成。分类标签只是指定要应用的分类类型。
- 分类类型用于将该分类的标签与给定格式相关联。
- 格式(特别是
ClassificationFormatDefinition
s)通过 MEF(如 EditorFormatDefinition
s)导出,以便 VS 可以发现它们并将它们用于为具有关联分类类型的范围着色。它们也(可选)出现在“字体和颜色”选项中。
- 分类器提供程序通过 MEF 导出,以便 VS 发现它;它为 VS 提供了一种为每个打开的缓冲区实例化分类器的方法(从而发现其中的标签)。
因此,您需要的是定义和导出分别与两种分类类型关联的两种分类格式定义的代码。然后你的分类器需要相应地产生两种类型的标签。这是一个示例(未经测试):
public static class Classifications
{
// These are the strings that will be used to form the classification types
// and bind those types to formats
public const string ArchiveKey = "MyProject/ArchiveKey";
public const string ArchiveKeyVar = "MyProject/ArchiveKeyVar";
// These MEF exports define the types themselves
[Export]
[Name(ArchiveKey)]
private static ClassificationTypeDefinition ArchiveKeyType = null;
[Export]
[Name(ArchiveKeyVar)]
private static ClassificationTypeDefinition ArchiveKeyVarType = null;
// These are the format definitions that specify how things will look
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ArchiveKey)]
[UserVisible(true)] // Controls whether it appears in Fonts & Colors options for user configuration
[Name(ArchiveKey)] // This could be anything but I like to reuse the classification type name
[Order(After = Priority.Default, Before = Priority.High)] // Optionally include this attribute if your classification should
// take precedence over some of the builtin ones like keywords
public sealed class ArchiveKeyFormatDefinition : ClassificationFormatDefinition
{
public ArchiveKeyFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xFF, 0x69, 0xB4); // pink!
DisplayName = "This will display in Fonts & Colors";
}
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ArchiveKeyVar)]
[UserVisible(true)]
[Name(ArchiveKeyVar)]
[Order(After = Priority.Default, Before = Priority.High)]
public sealed class ArchiveKeyVarFormatDefinition : ClassificationFormatDefinition
{
public ArchiveKeyVarFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xB0, 0x30, 0x60); // maroon
DisplayName = "This too will display in Fonts & Colors";
}
}
}
提供商:
[Export(typeof(ITaggerProvider))]
[ContentType("text")] // or whatever content type your tagger applies to
[TagType(typeof(ClassificationTag))]
public class ArchiveKeyClassifierProvider : ITaggerProvider
{
[Import]
public IClassificationTypeRegistryService ClassificationTypeRegistry { get; set; }
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
return buffer.Properties.GetOrCreateSingletonProperty(() =>
new ArchiveKeyClassifier(buffer, ClassificationTypeRegistry)) as ITagger<T>;
}
}
最后,标注器本身:
public class ArchiveKeyClassifier : ITagger<ClassificationTag>
{
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
private Dictionary<string, ClassificationTag> _tags;
public ArchiveKeyClassifier(ITextBuffer subjectBuffer, IClassificationTypeRegistryService classificationRegistry)
{
// Build the tags that correspond to each of the possible classifications
_tags = new Dictionary<string, ClassificationTag> {
{ Classifications.ArchiveKey, BuildTag(classificationRegistry, Classifications.ArchiveKey) },
{ Classifications.ArchiveKeyVar, BuildTag(classificationRegistry, Classifications.ArchiveKeyVar) }
};
}
public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
if (spans.Count == 0)
yield break;
foreach (var span in spans) {
if (span.IsEmpty)
continue;
foreach (var identSpan in LexIdentifiers(span)) {
var ident = identSpan.GetText();
if (!ident.StartsWith("Archive") || !ident.EndsWith("Key"))
continue;
var varSpan = new SnapshotSpan(
identSpan.Start + "Archive".Length,
identSpan.End - "Key".Length);
yield return new TagSpan<ClassificationTag>(new SnapshotSpan(identSpan.Start, varSpan.Start), _tags[Classifications.ArchiveKey]);
yield return new TagSpan<ClassificationTag>(varSpan, _tags[Classifications.ArchiveKeyVar]);
yield return new TagSpan<ClassificationTag>(new SnapshotSpan(varSpan.End, identSpan.End), _tags[Classifications.ArchiveKey]);
}
}
}
private static IEnumerable<SnapshotSpan> LexIdentifiers(SnapshotSpan span)
{
// Tokenize the string into identifiers and numbers, returning only the identifiers
var s = span.GetText();
for (int i = 0; i < s.Length; ) {
if (char.IsLetter(s[i])) {
var start = i;
for (++i; i < s.Length && IsTokenChar(s[i]); ++i);
yield return new SnapshotSpan(span.Start + start, i - start);
continue;
}
if (char.IsDigit(s[i])) {
for (++i; i < s.Length && IsTokenChar(s[i]); ++i);
continue;
}
++i;
}
}
private static bool IsTokenChar(char c)
{
return char.IsLetterOrDigit(c) || c == '_';
}
private static ClassificationTag BuildTag(IClassificationTypeRegistryService classificationRegistry, string typeName)
{
return new ClassificationTag(classificationRegistry.GetClassificationType(typeName));
}
}
另外注意一点:为了加速启动,VS保留了MEF exports的缓存。但是,这个缓存通常没有在应该失效的时候失效。此外,如果您更改现有分类格式定义的默认颜色,您的更改很可能不会被拾取,因为 VS 将以前的值保存在注册表中。为了缓解这种情况,最好在编译之间 运行 一个批处理脚本,当任何与 MEF 或格式相关的更改以清除内容时。下面是 VS2013 和 Exp 根后缀的示例(测试 VSIXes 时默认使用):
@echo off
del "%LOCALAPPDATA%\Microsoft\VisualStudio.0Exp\ComponentModelCache\Microsoft.VisualStudio.Default.cache" 2> nul
rmdir /S /Q "%LOCALAPPDATA%\Microsoft\VisualStudio.0Exp\ComponentModelCache" 2> nul
reg delete HKCU\Software\Microsoft\VisualStudio.0Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A} /f
使用标准模板,我设法制作了一个自定义荧光笔,它将所有出现的字符串 "Archive?????Key"(其中 ???? 是变量名称中允许的任何字符集合)变成粉红色。然而,我真正想要的是 "Archive" 和 "Key" 部分变成粉红色,而“????”部分变成栗色。据我了解 VSIX 荧光笔(我真的不了解)这意味着定义两个 ClassificationFormatDefinition
,但每次我尝试都会破坏项目。
我的 GetClassificationSpans
方法(这是与标准模板的唯一显着差异)如下所示:
public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
{
List<ClassificationSpan> spans = new List<ClassificationSpan>();
string text = span.GetText();
int idx0 = 0;
int idx1;
while (true)
{
idx0 = text.IndexOf(keyPrefix, idx0);
if (idx0 < 0)
break;
idx1 = text.IndexOf(keySuffix, idx0 + 6);
if (idx1 < 0)
break;
// TODO: make sure the prefix and suffix are part of the same object identifier.
string name = text.Substring(idx0 + lengthPrefix, idx1 - idx0 - lengthPrefix);
string full = text.Substring(idx0, idx1 - idx0 + keySuffix.Length);
SnapshotSpan span0 = new SnapshotSpan(span.Start + idx0, idx1 - idx0 + lengthSuffix);
SnapshotSpan span1 = new SnapshotSpan(span.Start + idx0 + lengthPrefix, idx1 - idx0 - lengthPrefix);
SnapshotSpan span2 = new SnapshotSpan(span.Start + idx1, lengthSuffix);
spans.Add(new ClassificationSpan(span0, classificationType));
spans.Add(new ClassificationSpan(span1, classificationType)); // I'd like to assign a different IClassificationType to this span.
spans.Add(new ClassificationSpan(span2, classificationType));
idx0 = idx1 + 5;
}
return spans;
}
而 span1
是我要指定不同样式的地方。我不明白分类器、格式、提供者和定义 类 需要如何相互关联,以及哪些可以了解多种样式。
模板适合入门,但通常一旦您知道自己的前进方向,更直接地重新实现所有内容会更简单。
以下是所有部分的组合方式:
- 分类器(实际上,
IClassificationTag
标记器)根据需要为文本缓冲区的给定部分生成分类标记跨度。 - 分类标签跨度由标签适用的缓冲区中的跨度和分类标签本身组成。分类标签只是指定要应用的分类类型。
- 分类类型用于将该分类的标签与给定格式相关联。
- 格式(特别是
ClassificationFormatDefinition
s)通过 MEF(如EditorFormatDefinition
s)导出,以便 VS 可以发现它们并将它们用于为具有关联分类类型的范围着色。它们也(可选)出现在“字体和颜色”选项中。 - 分类器提供程序通过 MEF 导出,以便 VS 发现它;它为 VS 提供了一种为每个打开的缓冲区实例化分类器的方法(从而发现其中的标签)。
因此,您需要的是定义和导出分别与两种分类类型关联的两种分类格式定义的代码。然后你的分类器需要相应地产生两种类型的标签。这是一个示例(未经测试):
public static class Classifications
{
// These are the strings that will be used to form the classification types
// and bind those types to formats
public const string ArchiveKey = "MyProject/ArchiveKey";
public const string ArchiveKeyVar = "MyProject/ArchiveKeyVar";
// These MEF exports define the types themselves
[Export]
[Name(ArchiveKey)]
private static ClassificationTypeDefinition ArchiveKeyType = null;
[Export]
[Name(ArchiveKeyVar)]
private static ClassificationTypeDefinition ArchiveKeyVarType = null;
// These are the format definitions that specify how things will look
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ArchiveKey)]
[UserVisible(true)] // Controls whether it appears in Fonts & Colors options for user configuration
[Name(ArchiveKey)] // This could be anything but I like to reuse the classification type name
[Order(After = Priority.Default, Before = Priority.High)] // Optionally include this attribute if your classification should
// take precedence over some of the builtin ones like keywords
public sealed class ArchiveKeyFormatDefinition : ClassificationFormatDefinition
{
public ArchiveKeyFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xFF, 0x69, 0xB4); // pink!
DisplayName = "This will display in Fonts & Colors";
}
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ArchiveKeyVar)]
[UserVisible(true)]
[Name(ArchiveKeyVar)]
[Order(After = Priority.Default, Before = Priority.High)]
public sealed class ArchiveKeyVarFormatDefinition : ClassificationFormatDefinition
{
public ArchiveKeyVarFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xB0, 0x30, 0x60); // maroon
DisplayName = "This too will display in Fonts & Colors";
}
}
}
提供商:
[Export(typeof(ITaggerProvider))]
[ContentType("text")] // or whatever content type your tagger applies to
[TagType(typeof(ClassificationTag))]
public class ArchiveKeyClassifierProvider : ITaggerProvider
{
[Import]
public IClassificationTypeRegistryService ClassificationTypeRegistry { get; set; }
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
return buffer.Properties.GetOrCreateSingletonProperty(() =>
new ArchiveKeyClassifier(buffer, ClassificationTypeRegistry)) as ITagger<T>;
}
}
最后,标注器本身:
public class ArchiveKeyClassifier : ITagger<ClassificationTag>
{
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
private Dictionary<string, ClassificationTag> _tags;
public ArchiveKeyClassifier(ITextBuffer subjectBuffer, IClassificationTypeRegistryService classificationRegistry)
{
// Build the tags that correspond to each of the possible classifications
_tags = new Dictionary<string, ClassificationTag> {
{ Classifications.ArchiveKey, BuildTag(classificationRegistry, Classifications.ArchiveKey) },
{ Classifications.ArchiveKeyVar, BuildTag(classificationRegistry, Classifications.ArchiveKeyVar) }
};
}
public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
if (spans.Count == 0)
yield break;
foreach (var span in spans) {
if (span.IsEmpty)
continue;
foreach (var identSpan in LexIdentifiers(span)) {
var ident = identSpan.GetText();
if (!ident.StartsWith("Archive") || !ident.EndsWith("Key"))
continue;
var varSpan = new SnapshotSpan(
identSpan.Start + "Archive".Length,
identSpan.End - "Key".Length);
yield return new TagSpan<ClassificationTag>(new SnapshotSpan(identSpan.Start, varSpan.Start), _tags[Classifications.ArchiveKey]);
yield return new TagSpan<ClassificationTag>(varSpan, _tags[Classifications.ArchiveKeyVar]);
yield return new TagSpan<ClassificationTag>(new SnapshotSpan(varSpan.End, identSpan.End), _tags[Classifications.ArchiveKey]);
}
}
}
private static IEnumerable<SnapshotSpan> LexIdentifiers(SnapshotSpan span)
{
// Tokenize the string into identifiers and numbers, returning only the identifiers
var s = span.GetText();
for (int i = 0; i < s.Length; ) {
if (char.IsLetter(s[i])) {
var start = i;
for (++i; i < s.Length && IsTokenChar(s[i]); ++i);
yield return new SnapshotSpan(span.Start + start, i - start);
continue;
}
if (char.IsDigit(s[i])) {
for (++i; i < s.Length && IsTokenChar(s[i]); ++i);
continue;
}
++i;
}
}
private static bool IsTokenChar(char c)
{
return char.IsLetterOrDigit(c) || c == '_';
}
private static ClassificationTag BuildTag(IClassificationTypeRegistryService classificationRegistry, string typeName)
{
return new ClassificationTag(classificationRegistry.GetClassificationType(typeName));
}
}
另外注意一点:为了加速启动,VS保留了MEF exports的缓存。但是,这个缓存通常没有在应该失效的时候失效。此外,如果您更改现有分类格式定义的默认颜色,您的更改很可能不会被拾取,因为 VS 将以前的值保存在注册表中。为了缓解这种情况,最好在编译之间 运行 一个批处理脚本,当任何与 MEF 或格式相关的更改以清除内容时。下面是 VS2013 和 Exp 根后缀的示例(测试 VSIXes 时默认使用):
@echo off
del "%LOCALAPPDATA%\Microsoft\VisualStudio.0Exp\ComponentModelCache\Microsoft.VisualStudio.Default.cache" 2> nul
rmdir /S /Q "%LOCALAPPDATA%\Microsoft\VisualStudio.0Exp\ComponentModelCache" 2> nul
reg delete HKCU\Software\Microsoft\VisualStudio.0Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A} /f