在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
相关产品推荐
相关产品推荐

