共用方式為


語言伺服器通訊協定

什麼是語言伺服器通訊協定?

傳統上,在編輯器或 IDE 中支援程式代碼自動完成或程式語言的 [ 移至定義 ] 等豐富的編輯功能非常具有挑戰性且耗時。 通常它需要用編輯器或 IDE 的程式語言編寫領域模型(掃描器、解析器、類型檢查器、建構器等)。 例如,在 Eclipse IDE 中提供 C/C++ 支援的 Eclipse CDT 外掛程式是用 Java 編寫的,因為 Eclipse IDE 本身是用 Java 編寫的。 遵循此方法,這表示在 Visual Studio Code 的 TypeScript 中實作 C/C++ 網域模型,並在 Visual Studio 的 C# 中實作個別的網域模型。

如果開發工具可以重複使用現有的語言特定函式庫,建立特定於語言的領域模型也會容易得多。 然而,這些函式庫通常是在程式語言本身中實現的(例如,好的 C/C++ 領域模型是在 C/C++ 中實現的)。 將 C/C++ 函式庫整合到用 TypeScript 編寫的編輯器中在技術上是可行的,但很難做到。

語言伺服器

將函式庫運行於獨立的進程中,並使用進程間通訊與其互動。 來回發送的消息形成一個協議。 語言伺服器通訊協定 (LSP) 是標準化開發工具與語言伺服器進程之間交換訊息的產物。 使用語言伺服器或惡魔並不是一個新奇的想法。 像 Vim 和 Emacs 這樣的編輯器已經這樣做了一段時間,以提供語義自動完成支援。 LSP 的目標是簡化此類集成,並提供一個有用的框架,用於向各種工具公開語言功能。

擁有通用協議可以通過重複利用現有的語言域模型實現,以最小的麻煩將編程語言功能整合到開發工具中。 語言伺服器後端可以用 PHP、Python 或 Java 編寫,而 LSP 可以使其輕鬆整合到各種工具中。 該協議在通用的抽象級別上工作,因此工具可以提供豐富的語言服務,而無需完全理解底層領域模型特定的細微差別。

LSP 的工作如何開始

LSP 隨著時間的推移而發展,今天它已經達到了 3.0 版。 它始於 OmniSharp 採用語言伺服器的概念,為 C# 提供豐富的編輯功能。 最初,OmniSharp 使用帶有 JSON 有效負載的 HTTP 協議,並已集成到包括 Visual Studio Code 在內的多個編輯器中。

大約在同一時間,Microsoft 開始開發 TypeScript 語言伺服器,其想法是在 Emacs 和 Sublime Text 等編輯器中支援 TypeScript。 在此實作中,編輯器透過 stdin/stdout 與 TypeScript 伺服器進程進行通信,並使用受 V8 偵錯器協定啟發的 JSON 有效負載來進行請求和回應。 TypeScript 伺服器已整合到 TypeScript Sublime 外掛程式和 VS Code 中,以實現豐富的 TypeScript 編輯。

在整合了兩個不同的語言伺服器之後,VS Code 團隊開始探索編輯器和 IDE 的通用語言伺服器通訊協定。 通用通訊協定可讓語言提供者建立可供不同 IDE 取用的單一語言伺服器。 語言伺服器取用者只需要實作通訊協定的用戶端一次。 這為語言提供者和語言消費者帶來了雙贏的局面。

語言伺服器協定從 TypeScript 伺服器使用的協定開始,透過受 VS Code 語言 API 啟發的更多語言功能進行了擴展。 該協議因其簡單性和現有庫的關係,利用 JSON-RPC 支持遠程調用。

VS Code 團隊通過實現多個 linter 語言伺服器來創建該協議的原型,這些伺服器會響應檢查文件(lint)的請求,並返回一組已檢測到的警告和錯誤。 目標是在使用者在文件中編輯時對檔案進行 linting ,這表示在編輯器會話期間會有許多 linting 請求。 保持伺服器正常運行是有意義的,這樣就不需要為每次使用者編輯啟動新的 linting 進程。 實作了多個 linter 伺服器,包括 VS Code 的 ESLint 和 TSLint 擴充功能。 這兩個 linter 伺服器都是在 TypeScript/JavaScript 中實現的,並在 Node.js上運行。 它們共用一個程式庫,實作通訊協定的用戶端和伺服器部分。

LSP 的運作方式

語言伺服器在自己的進程中執行,Visual Studio 或 VS Code 等工具會透過 JSON-RPC 使用語言通訊協定與伺服器通訊。 在專用進程中運作的語言伺服器的另一個優點是避免了與單一進程模型相關的效能問題。 實際的傳輸通道可以是 stdio、通訊端、具名管道,或者是 Node.js 的 IPC(節點間通信),前提是客戶端和伺服器都用 Node.js 編寫。

以下是工具和語言伺服器如何在例行編輯工作階段期間通訊的範例:

LSP 流程圖

  • 使用者在工具中開啟檔案(稱為文件):工具通知語言伺服器文件已開啟('textDocument/didOpen')。 從現在開始,文檔內容的真相不再在文件系統上,而是由工具保存在內存中。

  • 使用者進行編輯:該工具通知伺服器文件變更('textDocument/didChange'),並且程式的語義資訊由語言伺服器更新。 發生這種情況時,語言伺服器會分析此資訊,並將偵測到的錯誤和警告通知工具 ('textDocument/publishDiagnostics')。

  • 使用者在編輯器中的符號上執行「移至定義」:工具會傳送具有兩個參數的「textDocument/definition」請求:(1) 文件 URI 和 (2) 向伺服器發起「移至定義」請求的文字位置。 伺服器會以文件 URI 和符號定義在文件內的位置來回應。

  • 使用者關閉文件(檔案):工具發送「textDocument/didClose」通知,通知語言伺服器文件現在不再在記憶體中,並且目前內容現在在檔案系統上是最新的。

此範例說明通訊協定如何在編輯器功能層級與語言伺服器通訊,例如「移至定義」、「尋找所有參考」。 協定使用的資料類型是編輯器或 IDE 的「資料類型」,例如目前開啟的文字文件和游標的位置。 資料類型不在程式設計語言領域模型的層級,該模型通常會提供抽象語法樹狀結構和編譯器符號 (例如,解析類型、命名空間等)。這顯著簡化了協議。

現在讓我們更詳細地看看「textDocument/definition」請求。 以下是在 C++ 文件中「移至定義」要求的用戶端工具和語言伺服器之間傳遞的承載。

這是請求:

{
    "jsonrpc": "2.0",
    "id" : 1,
    "method": "textDocument/definition",
    "params": {
        "textDocument": {
            "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/use.cpp"
        },
        "position": {
            "line": 3,
            "character": 12
        }
    }
}

這是回應:

{
    "jsonrpc": "2.0",
    "id": "1",
    "result": {
        "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/provide.cpp",
        "range": {
            "start": {
                "line": 0,
                "character": 4
            },
            "end": {
                "line": 0,
                "character": 11
            }
        }
    }
}

回想起來,在編輯器層面而不是程式語言模型層面描述資料類型是語言伺服器協定成功的原因之一。 與跨不同程式設計語言標準化抽象語法樹狀結構和編譯器符號相比,標準化文字文件 URI 或游標位置要簡單得多。

當使用者使用不同的語言時,VS Code 通常會為每種程式設計語言啟動語言伺服器。 下列範例顯示使用者處理 Java 和 SASS 檔案的工作階段。

Java 和 Sass

能力

並非每個語言伺服器都能支援通訊協定所定義的所有功能。 因此,用戶端和伺服器會透過「功能」來宣告其支援的功能集。 例如,伺服器宣布它可以處理「textDocument/definition」請求,但可能無法處理「工作區/符號」請求。 同樣地,用戶端可以宣布他們能夠在儲存文件之前提供「即將儲存」通知,以便伺服器可以計算文字編輯以自動格式化已編輯的文件。

整合語言伺服器

語言伺服器實際整合到特定工具中,並非由語言伺服器通訊協定定義,而是留給工具實作者。 某些工具透過具有可以啟動任何類型的語言伺服器並與之通訊的擴充功能來一般整合語言伺服器。 其他 (例如 VS Code) 會為每個語言伺服器建立自訂延伸模組,讓延伸模組仍能夠提供一些自訂語言功能。

為了簡化語言伺服器和用戶端的實作,用戶端和伺服器部分有程式庫或 SDK。 這些程式庫是針對不同的語言提供的。 例如,有一個 語言客戶端 npm 模組 可以簡化將語言伺服器整合到 VS Code 擴充功能中,還有另一個 語言伺服器 npm 模組 可以使用 Node.js編寫語言伺服器。 這是目前支援程式庫 的清單

在 Visual Studio 中使用語言伺服器通訊協定