Roslyn:在单个源代码行中枚举确切的标记 + 琐事跨度?
Roslyn: enumerate exact token + trivia spans on a single source line?
我希望有效地实施以下方法:
IEnumerable<ColoredSpan> GetSyntaxHighlightedSpansOnLine(int lineNumber);
我有一个 Document
、SourceText
、SyntaxTree
等。假设 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"
注意包含空格和评论琐事,以及部分多行评论。
指出派生 CSharpSyntaxWalker 来遍历整个 AST 部分的方法,而不是有效地将遍历限制到单行的节点。在每行的基础上,这效率不高,我无法轻易找出例如哪些小节。 Roslyn "trivia"(例如多行注释)到 return。它还 returns 重叠节点(例如命名空间)。
我试过了,啦:
var lineSpan = sf.GetText().Lines[lineNumber].Span;
var nodes = syntaxTree.GetRoot()
.DescendantNodes()
.Where(x => x.Span.IntersectsWith(lineSpan))
但是这 return 是整个 AST 子树,前序遍历,这又是低效的,而且 return 是重叠节点(例如命名空间)并且不处理琐事。其他样本适用于整个 documents/scripts。我还查阅了接近零的 API 文档。
代码分析 API 是否有效地允许这样做?或者实现这个方法,我是否需要提前遍历整个AST并存储一个我自己设计的主观庞大的并行耗内存数据结构?
虽然您可以从 AST 重建此数据,但更好的 API 似乎以 Microsoft.CodeAnalysis.Classification.Classifier
的形式提供。它looks expensive,但是:
对于同步结果,您需要一个 Roslyn SemanticModel
作为您突出显示的源代码,您可以通过调用它们的 [=15= 从 Document
或 Compilation
中获取它] 方法。您可以在获取 SyntaxTree
和 SourceText
的同时获取和缓存它,即一旦您拥有文档。你还需要一个Workspace
。鉴于这些,您可以按需调用Classifier.GetClassifiedSpans()
。
如果您无法轻松获得电流 SemanticModel
,您可以改为调用 Classifier.GetClassifiedSpansAsync()
,它将为您构建特定 TextSpan
的微型模型。
这两种变体几乎都能提供您所要求的可枚举性,但不完全是。
首先,它returns以字符串的形式class弱类型化(class名称、关键字、运算符等)"enum" ;这些似乎对应于 ClassificationTypeNames
class 的 const 成员,因此推测它们是可靠的。您可以简单地将 ClassificationTypeNames.ClassName 等映射到颜色。
其次,由于此调用 returns 仅 classified 跨度将丢失 unclassified 跨度,例如,空格。你将不得不重建包括这些琐事在内的全套跨度,即使乏味也很简单:
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
(如您的问题)和一个将 ClassificationTypeNames
映射到颜色的 StyleFromClassificationType()
助手。
由于 Roslyn 目前缺少任何可能传达作者对这些 API 的意图的 API 文档,我建议在将此实现与 vim 一起使用之前测量性能和活力。
如果分析显示这过于昂贵,那么以这种格式缓存 n 最近查看的源代码行表示并在需要时重新计算,使该缓存无效 n 将相对简单=56=] 源代码更改。
我希望有效地实施以下方法:
IEnumerable<ColoredSpan> GetSyntaxHighlightedSpansOnLine(int lineNumber);
我有一个 Document
、SourceText
、SyntaxTree
等。假设 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"
注意包含空格和评论琐事,以及部分多行评论。
我试过了
var lineSpan = sf.GetText().Lines[lineNumber].Span;
var nodes = syntaxTree.GetRoot()
.DescendantNodes()
.Where(x => x.Span.IntersectsWith(lineSpan))
但是这 return 是整个 AST 子树,前序遍历,这又是低效的,而且 return 是重叠节点(例如命名空间)并且不处理琐事。其他样本适用于整个 documents/scripts。我还查阅了接近零的 API 文档。
代码分析 API 是否有效地允许这样做?或者实现这个方法,我是否需要提前遍历整个AST并存储一个我自己设计的主观庞大的并行耗内存数据结构
虽然您可以从 AST 重建此数据,但更好的 API 似乎以 Microsoft.CodeAnalysis.Classification.Classifier
的形式提供。它looks expensive,但是:
对于同步结果,您需要一个 Roslyn SemanticModel
作为您突出显示的源代码,您可以通过调用它们的 [=15= 从 Document
或 Compilation
中获取它] 方法。您可以在获取 SyntaxTree
和 SourceText
的同时获取和缓存它,即一旦您拥有文档。你还需要一个Workspace
。鉴于这些,您可以按需调用Classifier.GetClassifiedSpans()
。
如果您无法轻松获得电流 SemanticModel
,您可以改为调用 Classifier.GetClassifiedSpansAsync()
,它将为您构建特定 TextSpan
的微型模型。
这两种变体几乎都能提供您所要求的可枚举性,但不完全是。
首先,它returns以字符串的形式class弱类型化(class名称、关键字、运算符等)"enum" ;这些似乎对应于 ClassificationTypeNames
class 的 const 成员,因此推测它们是可靠的。您可以简单地将 ClassificationTypeNames.ClassName 等映射到颜色。
其次,由于此调用 returns 仅 classified 跨度将丢失 unclassified 跨度,例如,空格。你将不得不重建包括这些琐事在内的全套跨度,即使乏味也很简单:
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
(如您的问题)和一个将 ClassificationTypeNames
映射到颜色的 StyleFromClassificationType()
助手。
由于 Roslyn 目前缺少任何可能传达作者对这些 API 的意图的 API 文档,我建议在将此实现与 vim 一起使用之前测量性能和活力。
如果分析显示这过于昂贵,那么以这种格式缓存 n 最近查看的源代码行表示并在需要时重新计算,使该缓存无效 n 将相对简单=56=] 源代码更改。