You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Visual Studio中创建上下文分类器:语法高亮扩展技术问询

实现Visual Studio上下文相关语法高亮分类器的解决方案

嘿,我来帮你搞定VS扩展里上下文相关的语法高亮问题!你已经搞定了无上下文的正则匹配高亮,现在要处理那种得看上下文才知道怎么高亮的场景(比如字符串里的变量和普通代码里的关键词高亮不一样,或者注释里的内容不高亮),可以按下面的思路一步步来:

1. 用状态机跟踪代码上下文

首先得搞个状态机来记录当前代码处于什么上下文——比如是在普通代码块、字符串里、注释里,还是函数内部。这个状态机得跟着文本行走,更新当前的上下文状态。

比如先定义状态枚举和跟踪类:

internal enum SyntaxState
{
    Default,       // 普通代码上下文
    InString,      // 处于字符串中
    InMultiLineComment, // 处于多行注释
    InSingleLineComment // 单行注释
}

internal class ContextStateTracker
{
    private SyntaxState _lastState = SyntaxState.Default;

    public SyntaxState GetCurrentState(ITextSnapshotLine line)
    {
        var currentState = _lastState;
        var lineText = line.GetText();

        // 先处理上下文退出的情况
        if (currentState == SyntaxState.InMultiLineComment)
        {
            var commentEndIndex = lineText.IndexOf("*/");
            if (commentEndIndex >= 0)
            {
                // 注释结束了,回到默认状态
                currentState = SyntaxState.Default;
            }
        }
        else if (currentState == SyntaxState.InString)
        {
            // 假设是双引号字符串,检查是否有闭合引号
            var quoteCount = lineText.Count(c => c == '"');
            if (quoteCount % 2 != 0)
            {
                // 这行有奇数个引号,说明字符串还没结束
                currentState = SyntaxState.InString;
            }
            else
            {
                currentState = SyntaxState.Default;
            }
        }

        // 再处理上下文进入的情况
        if (currentState == SyntaxState.Default)
        {
            if (lineText.StartsWith("//"))
            {
                currentState = SyntaxState.InSingleLineComment;
            }
            else if (lineText.Contains("/*"))
            {
                currentState = SyntaxState.InMultiLineComment;
            }
            else if (lineText.Contains("\""))
            {
                var firstQuoteIndex = lineText.IndexOf("\"");
                // 检查引号是否不在注释里(这里简化处理)
                currentState = SyntaxState.InString;
            }
        }

        // 保存当前状态给下一行用
        _lastState = currentState;
        return currentState;
    }
}

2. 修改你的分类器,集成状态跟踪

接下来把状态跟踪集成到你现有的SolKeyword分类器里,不同的上下文用不同的正则规则:

internal class SolKeyword : IClassifier
{
    private readonly List<(Regex, IClassificationType)> _defaultRules;
    private readonly List<(Regex, IClassificationType)> _stringRules;
    private readonly ContextStateTracker _stateTracker;
    private readonly IClassificationTypeRegistryService _registry;

    internal SolKeyword(IClassificationTypeRegistryService registry)
    {
        _registry = registry;
        _stateTracker = new ContextStateTracker();

        // 初始化不同上下文的高亮规则
        _defaultRules = new List<(Regex, IClassificationType)>
        {
            (new Regex(@"\b(function|class|return|var)\b", RegexOptions.Compiled), 
             registry.GetClassificationType("SolKeyword")),
            (new Regex(@"\b(\d+)\b", RegexOptions.Compiled), 
             registry.GetClassificationType("SolNumber"))
        };

        _stringRules = new List<(Regex, IClassificationType)>
        {
            (new Regex(@"\$(\w+)", RegexOptions.Compiled), 
             registry.GetClassificationType("SolStringVariable"))
        };
    }

    public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
    {
        var results = new List<ClassificationSpan>();
        var snapshot = span.Snapshot;
        var startLine = snapshot.GetLineFromPosition(span.Start.Position);
        var endLine = snapshot.GetLineFromPosition(span.End.Position);

        // 遍历所有涉及的行,逐个处理
        for (var line = startLine; line != null && line.LineNumber <= endLine.LineNumber; line = line.NextLine)
        {
            var currentState = _stateTracker.GetCurrentState(line);
            var lineSpan = line.Extent;
            var intersectSpan = span.Intersection(lineSpan);

            if (!intersectSpan.HasValue) continue;

            // 根据当前上下文选择对应的规则
            var activeRules = currentState switch
            {
                SyntaxState.Default => _defaultRules,
                SyntaxState.InString => _stringRules,
                // 注释里不应用任何高亮规则
                _ => new List<(Regex, IClassificationType)>()
            };

            // 应用规则匹配并生成高亮范围
            var lineText = snapshot.GetText(intersectSpan.Value);
            foreach (var (regex, type) in activeRules)
            {
                foreach (Match match in regex.Matches(lineText))
                {
                    var matchStart = intersectSpan.Value.Start + match.Index;
                    var matchSpan = new SnapshotSpan(snapshot, matchStart, match.Length);
                    results.Add(new ClassificationSpan(matchSpan, type));
                }
            }
        }

        return results;
    }
}

3. 处理跨多行的上下文

上面的状态跟踪已经考虑了多行注释和多行字符串的情况——通过保存上一行的状态,下一行会基于这个状态来判断是否还处于同一个上下文。比如多行注释开始后,只要没遇到*/,后续的行都会保持InMultiLineComment状态,不会应用普通代码的高亮规则。

4. 性能优化小技巧

  • 正则表达式加上RegexOptions.Compiled,提前编译提升匹配速度
  • 不要每次分类都重新计算所有行的状态,尽量只在文本变化时更新状态(可以通过监听ITextBuffer.Changed事件)
  • 当文本变化时,通过ClassificationChanged事件只通知VS更新变化的范围,而不是全量更新,这样会更流畅

这样改造后,你的扩展就能根据当前代码的上下文来应用不同的高亮规则了,比如字符串里的变量会有特殊高亮,注释里的内容不会被当成关键词高亮。

内容的提问来源于stack exchange,提问作者Alex Zhukovskiy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:13:54