C# Roslyn:枚举确切的令牌+;单个源行上的琐事跨度?

C# Roslyn:枚举确切的令牌+;单个源行上的琐事跨度?,c#,syntax-highlighting,roslyn,roslyn-code-analysis,C#,Syntax Highlighting,Roslyn,Roslyn Code Analysis,我希望有效地实施以下方法: IEnumerable<ColoredSpan> GetSyntaxHighlightedSpansOnLine(int lineNumber); 我希望通过文本提供可列举的结果: " ", "which continues here... */", " ", "class", " ", "Bar", " ", ":", " ", "public", " ", "IBaz", " ", "// TODO: rename classes", "\r\

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

IEnumerable<ColoredSpan> GetSyntaxHighlightedSpansOnLine(int lineNumber);
我希望通过文本提供可列举的结果:

"    ", "which continues here... */", " ", "class", " ", "Bar", " ",
":", " ", "public", " ", "IBaz", " ", "// TODO: rename classes", "\r\n"
注意包含空格和注释细节,以及部分多行注释

指向派生CSharpSyntaxWalker的方法,以遍历AST的整个部分,但不能有效地将遍历限制到单行的节点。在每行的基础上,这是没有效率的,我不能很容易地确定要返回的小节,例如Roslyn“琐事”(例如多行注释)。它还返回重叠的节点(例如名称空间)

我试过了,一个洛杉矶:

但这会返回整个AST子树,即预序遍历,这同样是低效的,而且还会返回重叠的节点(例如名称空间),并且不会处理琐事。其他示例处理整个文档/脚本。我还查阅了接近于零的API文档


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

,同时您可能能够从AST重建此数据,对于这一点,更好的API似乎以
Microsoft.CodeAnalysis.Classification.Classifier
的形式提供。然而,它:

对于同步结果,您需要一个Roslyn
SemanticModel
用于突出显示的源代码,您可以通过调用其
GetSemanticModel()
方法从
文档
编译
中获取它。您可以在获取
SyntaxTree
SourceText
的同时获取和缓存此文档,即,一旦您拥有文档。您还需要一个
工作区
。有了这些,你可以按需打电话

如果您无法轻松获取当前的
语义模型
,您可以调用该模型为您构建特定
文本span
的minator模型

这两种变体都提供了您要求的几乎可枚举的数据,但不是完全可枚举的

首先,它以字符串“enum”的形式返回每个跨度的弱类型分类(类名、关键字、运算符等);这些似乎与类的const成员相对应,因此推测它们是可靠的。您可以简单地将ClassificationTypeName.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));
    }
}
IEnumerable描述符行(整数行号)
{
var lineSpan=sourceText.Lines[lineNumber].Span;
var classified=Classifier.GetClassifiedSpans(语义模型、行跨度、工作空间);
var cursor=lineSpan.Start;
//假设您需要的是字符串而不是TextSpan。
Func textOf=x=>sourceText.ToString(x);
如果(!classified.Any())
返回新的ColoredSpan(defaultStyle,textOf(lineSpan));
foreach(分类中的var重叠)
{
分类变量=重叠.TextSpan.Intersection(lineSpan).Value;
if(classified.Start>cursor)
{
var unclassified=新文本范围(游标,classified.Start-游标);
cursor=classified.Start;
返回新的ColoredSpan(defaultStyle,textOf(未分类));
}
var style=StyleFromClassificationType(重叠的.ClassificationType);
返回新的ColoredSpan(样式,textOf((TextSpan)分类));
游标=分类的.Start+分类的.Length;
}
如果(光标<线距开始+线距长度)
{
var trailing=新文本span(光标,lineSpan.Start+lineSpan.Length-光标);
返回新的ColoredSpan(defaultStyle,textOf(training));
}
}
此代码假定存在
ColoredSpan
(如您的问题所示)和将
ClassificationTypeNames
映射到颜色的
StyleFromClassificationType()
帮助程序

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


如果分析表明这过于昂贵,那么以这种格式缓存n个最近查看的源代码行表示并在需要时重新计算就相对简单了,如果/当源代码更改时,将使该缓存无效。

不确定我们应该在此处添加哪些文档,这些代码与我们在Visual Studio中使用的代码相同,但确实有一些缓存位于其顶部。缓存问题很棘手,老实说,如果不知道具体情况,就很难回答。@JasonMalinowski这非常让人放心,谢谢-至少在这里是正确的。虽然我们可以看到Roslyn代码非常棒,但是评论很少,因此很难从实现中推断API契约,很难知道一个人是按照预期的API编码的,而不是滥用API/弄错树/为自己的未来制造糟糕的性能问题。顺便说一句,如果能在Roslyn overview wiki中看到语法突出显示和分类器,那就太好了。但我真的应该在Github上提出这样的问题。
var lineSpan = sf.GetText().Lines[lineNumber].Span;
var nodes = syntaxTree.GetRoot()
                      .DescendantNodes()
                      .Where(x => x.Span.IntersectsWith(lineSpan))
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));
    }
}