2016-11-14 129 views
2

我期待有效地实现以下方法:Roslyn:枚举确切的标记+琐事跨越单一源代码行?

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“trivia”(例如多行注释)返回。它也返回重叠的节点(例如命名空间)。

我试图code as in this answer,一拉:

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

但这返回整个子树AST,前序遍历,而这又是低效的,并且还返回重叠节点(例如命名空间),并且不处理琐事。其他样本适用于整个文档/脚本。我还咨询了接近零的API文档。

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

回答

3

虽然你可能能够从AST重建这个数据,这是一个更好的API出现在Microsoft.CodeAnalysis.Classification.Classifier形式可用。它looks expensive,但是:

对于同步的结果,你需要一个罗斯林SemanticModel为你突出的源代码,你可以从一个DocumentCompilation通过调用其GetSemanticModel()方法获取。您可以在获取SyntaxTreeSourceText的同时获取并缓存此内容,即您拥有该文档后即可。您还需要一个Workspace。鉴于这些,您可以根据需要致电Classifier.GetClassifiedSpans()

如果您不能轻易获得当前的SemanticModel,您可以拨打电话Classifier.GetClassifiedSpansAsync(),这将为您建立一个特定的TextSpan的微型模型。

无论哪种变体都能为您提供几乎所需的枚举,但不完全。

首先,它返回弱类型分类(类名称,关键词,运营商等),用于在字符串“枚举”的形式的每个跨度;这些似乎对应于ClassificationTypeNames类的const成员,所以推测它们是可靠的。您可以简单地将ClassificationTypeNames.ClassName等映射到颜色。

其次,由于此调用返回仅分类跨越会有丢失归类为跨度,例如,空格。你将不得不重建全套跨度的,包括这样的琐事,这是直截了当繁琐:

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()帮助它映射ClassificationTypeNames以颜色。

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

如果分析显示这是过分昂贵的,这将是相对微不足道的缓存在这种格式Ñ最近观看的源极线表示,并且重新计算在需要的地方,无效该缓存如果/当所述源代码改变。

+1

不完全确定我们应该在这里添加哪些文档该代码与我们在Visual Studio中使用的代码相同,但是有一些缓存是我们在其上面的。缓存问题很棘手,说实话,如果不知道该场景,很难回答。 –

+0

@JasonMalinowski这是超级安慰,谢谢 - 至少在这里的正确线条。虽然我们可以看到Roslyn代码非常棒,但评论很少,所以很难从实现中推导出API合约,所以很难知道其中的API是按照预期编写的,而不是滥用API /吠叫错误的树/为未来的自我创造糟糕的表现问题。顺便提一下,在Roslyn概述wiki中看到提及语法突出显示和分类器会很高兴。但我真的应该在Github上提出这样的建议。 –