2016-11-14 130 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上提出這樣的建議。 –