在 Visual Studio 中创建上下文分类器

Creating contextual classifier in Visual Studio

我正在为 visual studio 编写一个简单的扩展,应该提供语法突出显示。它适用于我只使用正则表达式来识别文本范围 class:

的无上下文场景
internal class SolKeyword : IClassifier
{
    private readonly List<(Regex, IClassificationType)> _map;

    /// <summary>
    /// Initializes a new instance of the <see cref="SolKeyword"/> class.
    /// </summary>
    /// <param name="registry">Classification registry.</param>
    internal SolKeyword(IClassificationTypeRegistryService registry)
    {
        _map = new List<(Regex, IClassificationType)>
        {
            (new Regex(@"/\*.+\*/", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolComment)),
            (new Regex(@""".*?""", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolString)),
            (new Regex($@"\b({string.Join("|", VerboseConstant.BuiltinTypes.Concat(VerboseConstant.Keywords))})\b", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolKeyword)),
            (new Regex($@"\b({VerboseConstant.Operators})\b", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolKeyword)),
            (new Regex(@"-?(\d+(\.\d*)*)|(\.\d+)", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolNumber)),
        };
    }
}

...

然后通过标准机制使用它:

[Export(typeof(IClassifierProvider))]
[ContentType("sol")]
internal class SolEditorClassifierProvider : IClassifierProvider
{
    // Disable "Field is never assigned to..." compiler's warning. Justification: the field is assigned by MEF.
#pragma warning disable 649

    /// <summary>
    /// Classification registry to be used for getting a reference
    /// to the custom classification type later.
    /// </summary>
    [Import]
    private IClassificationTypeRegistryService classificationRegistry;

#pragma warning restore 649

    #region IClassifierProvider

    /// <summary>
    /// Gets a classifier for the given text buffer.
    /// </summary>
    /// <param name="buffer">The <see cref="ITextBuffer"/> to classify.</param>
    /// <returns>A classifier for the text buffer, or null if the provider cannot do so in its current state.</returns>
    public IClassifier GetClassifier(ITextBuffer buffer)
    {
        return buffer.Properties.GetOrCreateSingletonProperty<SolKeyword>(creator: () => new SolKeyword(this.classificationRegistry));
    }
...

但是,我被困在上下文 class 中,例如 struct。我需要添加功能,就好像文本文件中的任何地方都有 struct Foo,然后所有 Foo 都应该突出显示。所以我的问题是它如何实现以及它如何与打字适当地工作(当 Foo 更改为 Bar 或其他东西时)。我想我可能会以某种方式在 GetClassifier 方法中使用某种类型的聚合,但我不确定这是正确的方法。

我找到了一些解决方法。

就像以前一样,我们有一个分类器:

internal class SolKeyword : IClassifier
{
    private readonly List<(Regex, IClassificationType)> _map;
    private readonly IClassificationType _typeClassification;

    /// <summary>
    /// Initializes a new instance of the <see cref="SolKeyword"/> class.
    /// </summary>
    /// <param name="registry">Classification registry.</param>
    internal SolKeyword(IClassificationTypeRegistryService registry)
    {
        _map = new List<(Regex, IClassificationType)>
        {
            (new Regex(@"/\*.+\*/", RegexOptions.Compiled | RegexOptions.Multiline), registry.GetClassificationType(Classification.SolComment)),
            (new Regex(@"//.+", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolComment)),
            (new Regex(@""".*?""", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolString)),
            (new Regex($@"\b({string.Join("|", VerboseConstant.BuiltinTypes.Concat(VerboseConstant.Keywords))})\b", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolKeyword)),
            (new Regex($@"\b({VerboseConstant.Operators})\b", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolKeyword)),
            (new Regex(@"\b-?(\d+(\.\d*)*)|(\.\d+)", RegexOptions.Compiled), registry.GetClassificationType(Classification.SolNumber)),
        };

        _typeClassification = registry.GetClassificationType(Classification.SolType);
    }

但现在我们有以下显示所有类型的代码:

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
    {
        IList<ClassificationSpan> list = new List<ClassificationSpan>();
        ITextSnapshotLine line = span.Start.GetContainingLine();
        string text = line.GetText();

        foreach (var tuple in _map)
        {
            AddMatchingHighlighting(tuple.Item1, text, line, list, tuple.Item2);
        }

        string fullFileText = span.Snapshot.GetText();
        var contracts = Regex.Matches(fullFileText, @"(?:contract|struct|enum)\W+([\w_]+)", RegexOptions.Compiled).Cast<Match>().Select(x => x.Groups[1].Value);
        var matchingItems = new Regex($@"\b({string.Join("|", contracts)})\b");
        AddMatchingHighlighting(matchingItems, text, line, list, _typeClassification);

        return list;
    }

    private static void AddMatchingHighlighting(Regex regex, string text, ITextSnapshotLine line, ICollection<ClassificationSpan> list, IClassificationType classificationType)
    {
        foreach (Match match in regex.Matches(text))
        {
            var str = new SnapshotSpan(line.Snapshot, line.Start.Position + match.Index, match.Length);

            if (list.Any(s => s.Span.IntersectsWith(str)))
                continue;

            list.Add(new ClassificationSpan(str, classificationType));
        }
    }

现在它按预期工作了。它可能会给出误报(因为它不使用语义分析)并且它只显示在同一文件中声明的类型,但对我来说没问题。