Roslyn:在单个源代码行上枚举精确令牌+琐事跨度?

时间:2016-11-14 13:28:11

标签: c# syntax-highlighting roslyn roslyn-code-analysis

我希望有效地实现以下方法:

IEnumerable<ColoredSpan> GetSyntaxHighlightedSpansOnLine(int lineNumber);

我有DocumentSourceTextSyntaxTree等。假设ColoredSpan是一些颜色和字符串(或char s的其他来源)的元组。例如,对于此代码的第三行:

namespace Foo
{ /* Badly formatted coment...
    which continues here... */ class Bar : public IBaz // TODO: rename classes
    {
        ...

我希望用文字提供可枚举的结果:

"    ", "which continues here... */", " ", "class", " ", "Bar", " ",
":", " ", "public", " ", "IBaz", " ", "// TODO: rename classes", "\r\n"

请注意包含空格和评论琐事以及部分多行注释。

Another answer指向导出CSharpSyntaxWalker以遍历AST的整个部分的方法,但不是为了有效地限制遍历到单个线路的节点。在每行的基础上,这是没有效率的,我不能轻易地确定哪个子部分例如Roslyn“琐事”(例如多行评论)返回。它还返回重叠的节点(例如名称空间)。

我试过code as in this answer,la:

var lineSpan = sf.GetText().Lines[lineNumber].Span;
var nodes = syntaxTree.GetRoot()
                      .DescendantNodes()
                      .Where(x => x.Span.IntersectsWith(lineSpan))

但是这会返回整个AST子树,preorder遍历,这也是效率低下的,并且还返回重叠节点(例如名称空间)并且不处理琐事。其他示例适用于整个文档/脚本。我还查阅了零文档旁边的API文档。

代码分析API是否有效地允许此操作?或者为了实现这个方法,我是否需要提前遍历整个AST并存储一个主观庞大的并行内存消耗数据结构,如我自己设计的this answer

1 个答案:

答案 0 :(得分:3)

虽然您可以从AST重建此数据,但是更好的API似乎以&&的形式提供。但是looks expensive

对于同步结果,您需要一个Roslyn Microsoft.CodeAnalysis.Classification.Classifier作为您要突出显示的源代码,您可以通过调用SemanticModel方法从DocumentCompilation获取该源代码。您可以在获取GetSemanticModel()SyntaxTree的同时获取并缓存此内容,即只要您拥有该文档即可。您还需要SourceText。鉴于这些,您可以按需拨打Classifier.GetClassifiedSpans()

如果您无法轻易获得当前Workspace,则可以打电话给Classifier.GetClassifiedSpansAsync(),这将为您构建特定SemanticModel的微型模型。

这两种变体都为你提供了几乎所要求的数量,但并不完全。

首先,它以字符串“enum”的形式为每个跨度返回弱类型分类(类名,关键字,运算符等);这些似乎对应ClassificationTypeNames类的const成员,因此可能它们是可靠的。您可以简单地将ClassificationTypeNames.ClassName等映射到颜色。

其次,由于此调用仅返回分类跨度,因此将缺少 un 分类的跨度,例如,空格。你将不得不重建完整的跨度,包括这样的琐事,如果单调乏味,这很简单:

TextSpan

此代码假设存在IEnumerable<ColoredSpan> DescribeLine(int lineNumber) { var lineSpan = sourceText.Lines[lineNumber].Span; var classified = Classifier.GetClassifiedSpans(semanticModel, lineSpan, workspace); var cursor = lineSpan.Start; // Presuming you need a string rather than a TextSpan. Func<TextSpan, string> textOf = x => sourceText.ToString(x); if (!classified.Any()) yield return new ColoredSpan(defaultStyle, textOf(lineSpan)); foreach (var overlap in classified) { var classified = overlap.TextSpan.Intersection(lineSpan).Value; if (classified.Start > cursor) { var unclassified = new TextSpan(cursor, classified.Start - cursor); cursor = classified.Start; yield return new ColoredSpan(defaultStyle, textOf(unclassified)); } var style = StyleFromClassificationType(overlapping.ClassificationType); yield return new ColoredSpan(style, textOf((TextSpan)classified)); cursor = classified.Start + classified.Length; } if (cursor < lineSpan.Start + lineSpan.Length) { var trailing = new TextSpan(cursor, lineSpan.Start + lineSpan.Length - cursor); yield return new ColoredSpan(defaultStyle, textOf(trailing)); } } (如您的问题所示)和ColoredSpan帮助程序,它将StyleFromClassificationType()映射到颜色。

由于Roslyn目前缺少任何API文档,这可能会传达作者对这些API的意图,因此我建议在使用vim和vigor的实现之前测量性能。

如果分析显示这非常昂贵,那么以这种格式缓存 n 最近查看的源代码行表示会相对微不足道,并且在需要的地方重新计算,如果/当源代码时使该缓存无效变化。