VSIX IClassifier分配多个ClassificationTypes

时间:2016-06-01 22:00:53

标签: c# visual-studio-2015 vsix

使用标准模板,我设法制作了一个自定义荧光笔,可以将所有出现的字符串“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是我想要分配不同风格的地方。我不明白分类器,格式,提供程序和定义类如何做到这一点(!)之类的东西彼此相关,哪些可以让它们知道多种样式。

1 个答案:

答案 0 :(得分:6)

这些模板可以开始使用,但是一旦你知道自己的方向,就可以更直接地重新实现所有内容。

这里是所有部分如何组合在一起的:

  • 分类器(实际上是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导出的缓存。但是,此缓存通常不会失效。此外,如果您更改现有分类格式定义的默认颜色,则很有可能您的更改无法获取,因为VS会将以前的值保存在注册表中。为了缓解这种情况,最好在编译之间运行批处理脚本,以防任何与MEF或格式相关的更改。以下是VS2013和Exp根后缀的示例(在测试VSIX时默认使用):

@echo off

del "%LOCALAPPDATA%\Microsoft\VisualStudio\12.0Exp\ComponentModelCache\Microsoft.VisualStudio.Default.cache" 2> nul
rmdir /S /Q "%LOCALAPPDATA%\Microsoft\VisualStudio\12.0Exp\ComponentModelCache" 2> nul

reg delete HKCU\Software\Microsoft\VisualStudio\12.0Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A} /f