語言伺服器索引格式 (LSIF)
2019 年 2 月 19 日,Dirk Bäumer 撰寫
無需簽出的豐富程式碼導覽
身為開發人員,您花費大量時間閱讀和檢閱程式碼,而不一定是在撰寫新的原始程式碼。例如,您可能想要瀏覽 GitHub 之類的儲存機制中的現有程式碼庫,或者您可能想要檢閱同事的提取要求。
通常,您會簽出分支或複製儲存機制,將原始程式碼提取到您的本機電腦上,開啟您偏好的開發工具,然後您才能閱讀和瀏覽程式碼。如果您不必先複製儲存庫就能做到這一點,不是很棒嗎?想像一下,在不必下載原始程式碼的情況下,就能獲得智慧程式碼功能,例如懸停資訊、「前往定義」和「尋找所有參考」。部落格文章 豐富程式碼導覽體驗初探 說明了提取要求檢閱的這種情境。
語言伺服器索引格式 (LSIF,發音類似 "else if") 的目標是在開發工具或 Web UI 中支援豐富的程式碼導覽,而無需原始程式碼的本機副本。此格式在精神上類似於 語言伺服器協定 (LSP),後者簡化了將豐富的程式碼編輯功能整合到開發工具中的過程。
為什麼不直接使用現有的 LSP 語言伺服器?LSP 提供豐富的程式碼撰寫功能,例如自動完成、輸入時格式化和豐富的程式碼導覽。為了有效率地提供這些功能,語言伺服器需要所有原始程式碼檔案在本機磁碟上可用。LSP 語言伺服器也可能會將部分或全部檔案讀取到記憶體中,並計算抽象語法樹狀結構以支援這些功能。語言伺服器索引格式的目標是擴充 LSP 協定,以支援豐富的程式碼導覽功能,而無需這些需求。LSIF 定義了一種標準格式,供語言伺服器或其他程式設計工具發出其關於程式碼工作區的知識。這種持續保存的資訊稍後可用於回答同一工作區的 LSP 要求,而無需執行語言伺服器。
語言伺服器索引格式
LSIF 建構於 LSP 之上,並且使用與 LSP 中定義的相同的資料類型。在高階層次上,LSIF 對語言伺服器要求傳回的資料進行建模。與 LSP 相同,LSIF 不包含任何程式符號資訊,LSIF 也不定義任何符號語意 (例如,什麼構成符號的定義,或方法是否覆寫另一個方法)。因此,LSIF 不定義符號資料庫,這與 LSP 方法一致。
使用現有的 LSP 資料類型作為 LSIF 的基礎還有另一個優點,因為 LSIF 可以輕鬆整合到已經理解 LSP 的工具或伺服器中。
讓我們看看一個範例。我們先從一個名為 sample.ts
的簡單 Typescript 檔案開始,內容如下
function bar(): void {}
將滑鼠游標懸停在 bar()
上會在 Visual Studio Code 中顯示以下懸停資訊
此懸停資訊在 LSP 中使用 Hover
類型表示
export interface Hover {
/**
* The hover's content
*/
contents: MarkupContent | MarkedString | MarkedString[];
/**
* An optional range
*/
range?: Range;
}
在上面的範例中,具體值為
{
contents: [{ language: 'typescript', value: 'function bar(): void' }];
}
用戶端工具會透過傳送針對文件 file:///Users/username/sample.ts
在位置 {line: 0, character: 10}
的 textDocument/hover
要求,從語言伺服器擷取懸停內容。
LSIF 定義了一種格式,語言伺服器或獨立工具發出該格式來描述元組 ['textDocument/hover', 'file:///Users/username/sample.ts', {line: 0, character: 10}]
解析為上述懸停。然後可以取得資料並將其保存到資料庫中。
LSP 要求是以位置為基礎的,但是結果通常僅針對範圍而變化,而不是針對單個位置。在上面的懸停範例中,對於識別項 bar
的所有位置,懸停值都相同。這表示當使用者將滑鼠游標懸停在 bar
中的 b
上或 bar
中的 r
上時,會傳回相同的懸停值。為了使發出的資料更精簡,LSIF 使用範圍而不是位置。對於此範例,LSIF 工具發出元組 ['textDocument/hover', 'file:///Users/username/sample.ts', { start: { line: 0, character: 9 }, end: { line: 0, character: 12 }]
,其中包含範圍資訊。
LSIF 使用圖形來發出此資訊。在圖形中,LSP 要求使用邊緣表示。文件、範圍或要求結果 (例如,懸停) 使用頂點表示。此格式具有以下優點
- 對於給定的程式碼範圍,可能會有不同的結果。對於給定的識別項範圍,使用者對懸停值、定義的位置或「尋找所有參考」感興趣。因此,LSIF 將這些結果與範圍連結起來。
- 透過新增新的邊緣或頂點種類,可以輕鬆擴充格式以支援其他要求類型或結果。
- 可以在資料可用時立即發出資料。這可以實現串流,而不是必須在記憶體中儲存大量資料。例如,當剖析進度進行時,應該針對每個檔案完成發出文件的資料。
對於懸停範例,發出的 LSIF 圖形資料如下所示
// a vertex representing the document
{ id: 1, type: "vertex", label: "document", uri: "file:///Users/username/sample.ts", languageId: "typescript" }
// a vertex representing the range for the identifier bar
{ id: 4, type: "vertex", label: "range", start: { line: 0, character: 9}, end: { line: 0, character: 12 } }
// an edge saying that the document with id 1 contains the range with id 4
{ id: 5, type: "edge", label: "contains", outV: 1, inV: 4}
// a vertex representing the actual hover result
{ id: 6, type: "vertex", label: "hoverResult",
result: {
contents: [
{ language: "typescript", value: "function bar(): void" }
]
}
}
// an edge linking the hover result to the range.
{ id: 7, type: "edge", label: "textDocument/hover", outV: 4, inV: 6 }
對應的圖形如下所示
LSP 也支援僅將文件作為參數的要求 (它們不是以位置為基礎的)。程式碼理解有用的範例要求是所有文件符號的清單或計算所有摺疊範圍。這些要求在 LSIF 中以 [request, document]
-> result 的形式建模。
讓我們看看另一個範例
function bar(): void {
console.log('Hello World!');
}
包含上述函式 bar
的文件的摺疊範圍結果發出如下
// a vertex representing the document
{ id: 1, type: "vertex", label: "document", uri: "file:///Users/username/sample.ts", languageId: "typescript" }
// a vertex representing the folding result
{ id: 2, type: "vertex", label: "foldingRangeResult", result: [ { startLine: 0, startCharacter: 20, endLine: 2, endCharacter: 1 } ] }
// an edge connecting the folding result to the document.
{ id: 3, type: "edge", label: "textDocument/foldingRange", outV: 1, inV: 2 }
這些只是 LSIF 支援的 LSP 要求的兩個範例。LSIF 規格 的目前版本也支援文件符號、文件連結、「前往定義」、「前往宣告」、「前往類型定義」、「尋找所有參考」和「前往實作」。
我們需要您的意見反應!
我們在 LSIF 規格方面取得了良好的初步進展,我們希望與社群展開對話,讓您可以瞭解我們正在做什麼。如需意見反應,請在問題 Language Server Index Format 上留言。
如何開始使用
若要開始使用 LSIF,您可以查看以下資源
- LSIF 規格 - 該文件也描述了一些額外的最佳化,這些最佳化已完成以保持發出的資料精簡。
- TypeScript 的 LSIF 索引 - 一種為 TypeScript 產生 LSIF 的工具。README 提供了使用該工具的說明。
- 適用於 LSIF 的 Visual Studio Code 擴充功能 - VS Code 的擴充功能,使用 LSIF JSON 傾印提供語言理解功能。如果您實作新的 LSIF 產生器,您可以使用此擴充功能以任意原始程式碼驗證它。