共用方式為


新增語言伺服器通訊協定延伸模組

語言伺服器通訊協定 (LSP) 是以 JSON RPC v2.0 的形式,用來提供語言服務功能給各種程式碼編輯器的通用通訊協定。 開發人員可以使用通訊協定撰寫單一語言伺服器,以提供 IntelliSense、錯誤診斷、尋找所有參考等語言服務功能給支援 LSP 的各種程式碼編輯器。 傳統上,您可以使用 TextMate 文法檔案來新增 Visual Studio 中的語言服務,以提供基本功能,例如語法醒目提示,或撰寫使用完整 Visual Studio 擴充性 API 集合的自定義語言服務來提供更豐富的數據。 透過 Visual Studio 對 LSP 的支援,有第三個選項。

Visual Studiolanguage server protocol service in Visual Studiolanguage server protocol service in Visual Studio中的語言伺服器通訊協定服務

若要確保最佳的用戶體驗,也請考慮實作 Language Configuration,它提供許多相同作業的本機處理,因此可以改善 LSP 所支援之許多語言特定編輯器作業的效能。

語言伺服器通訊協定

語言伺服器通訊協議實作

本文說明如何建立使用 LSP 語言伺服器的 Visual Studio 延伸模組。 它假設您已經開發 LSP 型語言伺服器,並只想將其整合到 Visual Studio 中。

如需 Visual Studio 內的支援,語言伺服器可以透過任何以數據流為基礎的傳輸機制與客戶端通訊,例如:

  • 標準輸入/輸出數據流
  • 命名管道
  • 套接字(僅限 TCP)

在 Visual Studio 中,LSP 和支援的意圖是將不屬於 Visual Studio 產品的語言服務上線。 它不適合在Visual Studio中擴充現有的語言服務(例如 C#)。 若要擴充現有的語言,請參閱語言服務的擴充性指南(例如,“Roslyn” .NET Compiler Platform),或參閱 擴充編輯器和語言服務

如需通訊協定本身的詳細資訊,請參閱這裡的檔案

如需如何建立範例語言伺服器或如何將現有語言伺服器整合到Visual Studio Code 的詳細資訊,請參閱這裡 檔。

語言伺服器通訊協定支援的功能

下表顯示 Visual Studio 支援哪些 LSP 功能:

訊息 在 Visual Studio 中具有支援
初始化 是的
初始化 是的
關機 是的
結束 是的
$/cancelRequest 是的
視窗/顯示訊息 是的
顯示訊息請求 是的
window/記錄訊息 是的
遙測/事件
client/registerCapability (客戶端/註冊功能)
用戶端/解除註冊功能
workspace/didChangeConfiguration 是的
workspace/didChangeWatchedFiles 是的
工作區/符號 是的
工作區/執行命令 是的
工作區/應用編輯 是的
textDocument/publishDiagnostics 是的
textDocument/didOpen 是的
textDocument/didChange 是的
textDocument/willSave
textDocument/willSaveWaitUntil
textDocument/didSave 是的
textDocument/didClose 是的
文本文件/完成 是的
完成/解決 是的
textDocument/hover 是的
文本文件/簽名幫助 是的
textDocument/引用 是的
文本文件/文件突出顯示 是的
textDocument/documentSymbol 是的
textDocument/formatting 是的
文件/範圍格式化 是的
textDocument/onTypeFormatting
文字文件/定義 是的
textDocument/codeAction 是的
textDocument/codeLens
codeLens/resolve
文件文檔/文件鏈接
文件鏈接/解析
文件/重新命名 是的

開始

注意

從 Visual Studio 2017 15.8 版開始,通用語言伺服器通訊協議的支援會內建於 Visual Studio 中。 如果您使用 Preview Language Server Client VSIX 版本建置 LSP 擴充功能,當您升級至 15.8 版或更高版本之後,它們就會停止運作。 您必須執行下列動作,才能讓 LSP 延伸模組再次運作:

  1. 卸載 Microsoft Visual Studio 語言伺服器通訊協定預覽 VSIX。

    從 15.8 版開始,每次您在 Visual Studio 中執行升級時,都會自動偵測並移除預覽 VSIX。

  2. 請將您的 Nuget 參考更新至最新的非預覽版本,以適用於 LSP 套件

  3. 移除 VSIX 清單中對 Microsoft Visual Studio 語言伺服器協定預覽 VSIX 的相依性。

  4. 請確定 VSIX 指定 Visual Studio 2017 15.8 版 Preview 3 做為安裝目標的下限。

  5. 重建和重新部署。

建立 VSIX 專案

若要使用以 LSP 為基礎的語言伺服器建立語言服務延伸模組,請先確定您已安裝 VS 實例的 Visual Studio 延伸模塊開發 工作負載。

接下來,導航至 File>New ProjectVisual C#>>擴充性>VSIX 專案來建立新的 VSIX 專案:

建立 vsix 專案

語言伺服器和運行時間安裝

根據預設,在 Visual Studio 中建立以支援 LSP 語言伺服器的延伸模組不會包含執行它們所需的語言伺服器本身或運行時間。 擴充功能開發人員負責分發語言伺服器和所需的執行環境。 有數種方式可以執行此動作:

  • 語言伺服器可以內嵌在 VSIX 中做為內容檔案。
  • 建立 MSI 以安裝語言伺服器和/或所需的執行階段。
  • 提供 Marketplace 的說明,告知使用者如何取得執行環境和語言伺服器。

TextMate 文法檔案

LSP 不包含如何為語言提供文字色彩標示的規格。 若要在 Visual Studio 中提供語言的自訂色彩設定,延伸模塊開發人員可以使用 TextMate 文法檔案。 若要新增自定義 TextMate 文法或主題檔案,請遵循下列步驟:

  1. 在您的延伸模組內建立名為 「Grammars」 的資料夾(或可以是您選擇的任何名稱)。

  2. Grammars 資料夾中,包括您希望提供自定義色彩的任何 *.tmlanguage*.plist*.tmtheme*.json 檔案。

    提示

    .tmtheme 檔案會定義範圍如何映射至 Visual Studio 分類(具名色彩索引鍵)。 如需指引,您可以在 目錄中參考全局 < 檔案。

  3. 建立 .pkgdef 檔案,並新增類似以下的行:

    [$RootKey$\TextMate\Repositories]
    "MyLang"="$PackageFolder$\Grammars"
    
  4. 以滑鼠右鍵按下檔案,然後選取 [[屬性]。 將 [建置] 動作變更為 [Content],並將 VSIX 屬性中的 [包含] 變更為 true

完成上述步驟之後,會將 Grammars 資料夾新增至套件的安裝目錄,作為名為 'MyLang' 的存放庫來源('MyLang' 只是釐清的名稱,而且可以是任何唯一字符串)。 此目錄中的所有文法(.tmlanguage 檔案)和主題檔案(.tmtheme 檔案)都會被挑選為潛力,並取代 TextMate 提供的內建文法。 如果文法檔案宣告的擴展名與所開啟檔案的副檔名匹配,TextMate將會介入。

建立簡單的語言用戶端

主要介面 - ILanguageClient

建立 VSIX 項目之後,請將下列 NuGet 套件新增至您的專案:

注意

完成先前步驟之後,當您相依於 NuGet 套件時,也會將 Newtonsoft.Json 和 StreamJsonRpc 套件新增至您的專案。 除非您確定這些新版本會安裝在擴充功能以為目標的 Visual Studio 版本上,否則請勿更新這些套件。 VSIX 中不會包含組件,而是會從 Visual Studio 安裝目錄取得它們。 如果您參考的元件版本比使用者計算機上安裝的版本還新,您的擴充功能將無法運作。

接著,您可以建立實作 ILanguageClient 介面 的新類別,這是連線到 LSP 型語言伺服器之語言用戶端所需的主要介面。

以下是範例:

namespace MockLanguageExtension
{
    [ContentType("bar")]
    [Export(typeof(ILanguageClient))]
    public class BarLanguageClient : ILanguageClient
    {
        public string Name => "Bar Language Extension";

        public IEnumerable<string> ConfigurationSections => null;

        public object InitializationOptions => null;

        public IEnumerable<string> FilesToWatch => null;

        public event AsyncEventHandler<EventArgs> StartAsync;
        public event AsyncEventHandler<EventArgs> StopAsync;

        public async Task<Connection> ActivateAsync(CancellationToken token)
        {
            await Task.Yield();

            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Server", @"MockLanguageServer.exe");
            info.Arguments = "bar";
            info.RedirectStandardInput = true;
            info.RedirectStandardOutput = true;
            info.UseShellExecute = false;
            info.CreateNoWindow = true;

            Process process = new Process();
            process.StartInfo = info;

            if (process.Start())
            {
                return new Connection(process.StandardOutput.BaseStream, process.StandardInput.BaseStream);
            }

            return null;
        }

        public async Task OnLoadedAsync()
        {
            await StartAsync.InvokeAsync(this, EventArgs.Empty);
        }

        public Task OnServerInitializeFailedAsync(Exception e)
        {
            return Task.CompletedTask;
        }

        public Task OnServerInitializedAsync()
        {
            return Task.CompletedTask;
        }
    }
}

需要實作的主要方法是 OnLoadedAsyncActivateAsync當 Visual Studio 載入您的延伸模組,且您的語言伺服器已準備好啟動時,就會呼叫 onLoadedAsync。 在此方法中,您可以立即叫用 StartAsync 委派,以發出語言伺服器應該啟動的訊號,或者您可以稍後執行其他邏輯並叫用 startAsync 若要啟用您的語言伺服器,您必須在某個時間點呼叫 StartAsync。

ActivateAsync 是呼叫 startAsync 委派 最終叫用的方法。 其中包含啟動語言伺服器的邏輯,並與其建立連線。 必須傳回一個連接物件,其中包含用於寫入伺服器和從伺服器讀取的串流。 此處拋出的任何例外狀況都會透過 Visual Studio 中的 InfoBar 訊息被捕捉並顯示給使用者。

啟動

實作語言用戶端類別之後,您必須定義兩個屬性,以定義如何將它載入 Visual Studio 並啟動:

  [Export(typeof(ILanguageClient))]
  [ContentType("bar")]

MEF

Visual Studio 使用 MEF (Managed Extensibility Framework) 來管理其擴充點。 Export 屬性會向 Visual Studio 指出,此類別應該取用為延伸點,並在適當時間載入。

若要使用MEF,您也必須將MEF定義為 VSIX 指令清單中的資產。

開啟 VSIX 指令清單設計工具,並流覽至 [Assets] 索引標籤:

新增MEF資產

按一下 [新增] 來建立新的資產:

定義MEF資產

  • 類型:Microsoft.VisualStudio.MefComponent
  • 來源:目前方案中的專案
  • 專案:[您的專案]

內容類型定義

目前,載入 LSP 語言伺服器延伸模組的唯一方式是檔案內容類型。 也就是說,在定義語言用戶端類別時(實作 ILanguageClient),您必須定義開啟時會導致延伸模組載入的文件類型。 如果未開啟任何符合您定義內容類型的檔案,則不會載入您的延伸模組。

這是透過定義一或多個 ContentTypeDefinition 類別來完成:

namespace MockLanguageExtension
{
    public class BarContentDefinition
    {
        [Export]
        [Name("bar")]
        [BaseDefinition(CodeRemoteContentDefinition.CodeRemoteContentTypeName)]
        internal static ContentTypeDefinition BarContentTypeDefinition;

        [Export]
        [FileExtension(".bar")]
        [ContentType("bar")]
        internal static FileExtensionToContentTypeDefinition BarFileExtensionDefinition;
    }
}

在上一個範例中,會針對以 .bar 擴展名結尾的檔案建立內容類型定義。 內容類型定義的名稱為 「bar」,而且必須衍生自 CodeRemoteContentTypeName

新增內容類型定義之後,您就可以在語言用戶端類別中定義何時載入語言用戶端延伸模組:

    [ContentType("bar")]
    [Export(typeof(ILanguageClient))]
    public class BarLanguageClient : ILanguageClient
    {
    }

新增 LSP 語言伺服器的支援不需要您在 Visual Studio 中實作自己的項目系統。 客戶可以在 Visual Studio 中開啟單一檔案或資料夾,以開始使用您的語言服務。 事實上,LSP 語言伺服器的支持是設計為只能在開啟的資料夾/檔案案例中運作。 如果實作自定義項目系統,某些功能(例如設定)將無法運作。

進階功能

設定

自定義語言伺服器特定設定的支援可供使用,但仍在改善過程中。 設定專屬於語言伺服器支援的內容,且通常會控制語言伺服器發出數據的方式。 例如,語言伺服器可能會有報告錯誤數目上限的設定。 延伸模組作者會定義預設值,用戶可以針對特定項目變更此值。

請遵循下列步驟,將設定的支援新增至您的 LSP 語言服務延伸模組:

  1. 將 JSON 檔案 (例如,MockLanguageExtensionSettings.json) 新增至包含設定及其預設值的專案。 例如:

    {
        "foo.maxNumberOfProblems": -1
    }
    
  2. 在 JSON 檔案上點擊滑鼠右鍵,然後選擇 屬性。 將 Build 動作變更為 “Content”,並將 [Include in VSIX] 屬性變更為 true

  3. 實作 ConfigurationSections 並傳回 JSON 檔案中所定義之設定的前置詞列表(在 Visual Studio Code 中,這會對應至 package.json中的組態區段名稱):

    public IEnumerable<string> ConfigurationSections
    {
        get
        {
            yield return "foo";
        }
    }
    
  4. 將 .pkgdef 檔案新增至專案(新增文本檔,並將擴展名變更為 .pkgdef)。 pkgdef 檔案應該包含此資訊:

    [$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\[settings-name]]
    @="$PackageFolder$\[settings-file-name].json"
    

    樣本:

    [$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\MockLanguageExtension]
    @="$PackageFolder$\MockLanguageExtensionSettings.json"
    
  5. 以滑鼠右鍵按鍵按下 .pkgdef 檔案,然後選取 [屬性]。 將 [建置] 動作變更為 Content,並將 Include in VSIX 屬性變更為 true

  6. 開啟 source.extension.vsixmanifest 檔案,並在 [Asset] 索引卷標中新增資產:

    編輯 vspackage 資產

    • 類型:Microsoft.VisualStudio.VsPackage
    • 來源:文件系統上的檔案
    • 路徑:[.pkgdef 檔案的路徑]

用戶編輯工作區的設定

  1. 用戶會開啟包含您伺服器擁有檔案的工作區。

  2. 使用者會在名為 VSWorkspaceSettings.json.vs 資料夾中新增檔案。

  3. 用戶將一行新增至 VSWorkspaceSettings.json 檔案,設定由伺服器提供。 例如:

    {
        "foo.maxNumberOfProblems": 10
    }
    

啟用診斷追蹤

您可以啟用診斷追蹤來輸出客戶端與伺服器之間的所有訊息,這在偵錯問題時很有用。 若要啟用診斷追蹤,請執行下列動作:

  1. 開啟或建立工作區配置檔 VSWorkspaceSettings.json(請參閱「用戶編輯工作區的設定」)。
  2. 在設定 json 檔案中新增下列這一行:
{
    "foo.trace.server": "Off"
}

追蹤詳細資訊有三個可能的值:

  • “Off”: 追蹤完全關閉
  • 「訊息」:追蹤已開啟,但只會追蹤方法名稱和響應標識符。
  • 「Verbose」:追蹤功能已開啟,將追蹤整個 RPC 訊息。

開啟追蹤時,內容會寫入至 %temp%\VisualStudio\LSP 目錄中的檔案。 記錄會遵循命名格式 [LanguageClientName]-[日期時間標記].log。 目前,只能針對開啟的資料夾案例啟用追蹤。 開啟單一檔案以啟動語言伺服器並無診斷追蹤支援。

自訂訊息

有 API 可用來協助將訊息傳遞至不屬於標準語言伺服器通訊協定的語言伺服器,以及接收訊息。 若要處理自定義訊息,請在語言用戶端類別中實作 ILanguageClientCustomMessage2 介面。 VS-StreamJsonRpc 程式庫用於在語言客戶端與語言伺服器之間傳輸自定義訊息。 由於您的 LSP 語言用戶端延伸模組就像任何其他 Visual Studio 延伸模組一樣,因此您可以決定透過自定義訊息將其他功能(LSP 不支援的功能)新增至 Visual Studio(使用其他 Visual Studio API)。

接收自訂訊息

若要從語言伺服器接收自訂訊息,請在 ILanguageClientCustomMessage2 上實作 [CustomMessageTarget](/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) 屬性,並傳回知道如何處理自訂訊息的物件。 下列範例:

(/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget)屬性在 ILanguageClientCustomMessage2 並傳回知道如何處理自訂訊息的物件。 下列範例:

internal class MockCustomLanguageClient : MockLanguageClient, ILanguageClientCustomMessage2
{
    private JsonRpc customMessageRpc;

    public MockCustomLanguageClient() : base()
    {
        CustomMessageTarget = new CustomTarget();
    }

    public object CustomMessageTarget
    {
        get;
        set;
    }

    public class CustomTarget
    {
        public void OnCustomNotification(JToken arg)
        {
            // Provide logic on what happens OnCustomNotification is called from the language server
        }

        public string OnCustomRequest(string test)
        {
            // Provide logic on what happens OnCustomRequest is called from the language server
        }
    }
}

傳送自定義訊息

若要將自定義訊息傳送至語言伺服器,請在 ILanguageClientCustomMessage2上實作 AttachForCustomMessageAsync 方法。 當您的語言伺服器啟動並準備好接收訊息時,會叫用這個方法。 JsonRpc 物件作為參數傳遞,您接著可以使用 VS-StreamJsonRpc API 將訊息傳送至語言伺服器。 下列範例:

internal class MockCustomLanguageClient : MockLanguageClient, ILanguageClientCustomMessage2
{
    private JsonRpc customMessageRpc;

    public MockCustomLanguageClient() : base()
    {
        CustomMessageTarget = new CustomTarget();
    }

    public async Task AttachForCustomMessageAsync(JsonRpc rpc)
    {
        await Task.Yield();

        this.customMessageRpc = rpc;
    }

    public async Task SendServerCustomNotification(object arg)
    {
        await this.customMessageRpc.NotifyWithParameterObjectAsync("OnCustomNotification", arg);
    }

    public async Task<string> SendServerCustomMessage(string test)
    {
        return await this.customMessageRpc.InvokeAsync<string>("OnCustomRequest", test);
    }
}

中間層

有時候擴充功能開發人員可能會想要攔截從語言伺服器傳送和接收的 LSP 訊息。 例如,延伸模塊開發人員可能想要改變針對特定 LSP 訊息傳送的訊息參數,或修改從語言伺服器傳回之 LSP 功能的結果(例如完成)。 必要時,延伸模塊開發人員可以使用 MiddleLayer API 來攔截 LSP 訊息。

若要攔截特定訊息,請建立實作 ILanguageClientMiddleLayer 介面的類別。 然後,在語言用戶端類別中實作 ILanguageClientCustomMessage2 介面,並在 MiddleLayer 屬性中傳回物件的實例。 下列範例:

public class MockLanguageClient : ILanguageClient, ILanguageClientCustomMessage2
{
  public object MiddleLayer => DiagnosticsFilterMiddleLayer.Instance;

  private class DiagnosticsFilterMiddleLayer : ILanguageClientMiddleLayer
  {
    internal readonly static DiagnosticsFilterMiddleLayer Instance = new DiagnosticsFilterMiddleLayer();

    private DiagnosticsFilterMiddleLayer() { }

    public bool CanHandle(string methodName)
    {
      return methodName == "textDocument/publishDiagnostics";
    }

    public async Task HandleNotificationAsync(string methodName, JToken methodParam, Func<JToken, Task> sendNotification)
    {
      if (methodName == "textDocument/publishDiagnostics")
      {
        var diagnosticsToFilter = (JArray)methodParam["diagnostics"];
        // ony show diagnostics of severity 1 (error)
        methodParam["diagnostics"] = new JArray(diagnosticsToFilter.Where(diagnostic => diagnostic.Value<int?>("severity") == 1));

      }
      await sendNotification(methodParam);
    }

    public async Task<JToken> HandleRequestAsync(string methodName, JToken methodParam, Func<JToken, Task<JToken>> sendRequest)
    {
      return await sendRequest(methodParam);
    }
  }
}

中介層功能仍在開發中,尚未全面。

範例 LSP 語言伺服器延伸模組

若要在 Visual Studio 中使用 LSP 用戶端 API 查看範例延伸模組的原始程式碼,請參閱 VSSDK-Extensibility-Samples LSP 範例

FAQ

我想建置自定義項目系統來補充 LSP 語言伺服器,以在 Visual Studio 中提供更豐富的功能支援,我要如何執行這項操作?

Visual Studio 中 LSP 型語言伺服器的支援仰賴 開啟資料夾功能,並設計為不需要自訂專案系統。 您可以依照這裡的指示 建置自己的自定義項目系統,,但某些功能,例如設定可能無法運作。 LSP 語言伺服器的預設初始化邏輯是傳入目前開啟之資料夾的根資料夾位置,因此如果您使用自定義項目系統,您可能需要在初始化期間提供自定義邏輯,以確保語言伺服器可以正常啟動。

如何新增調試程序支援?

我們將在未來版本中提供 常見偵錯通訊協定 的支援。

如果已安裝 VS 支援的語言服務(例如 JavaScript),我仍然可以安裝 LSP 語言伺服器延伸模組,以提供其他功能(例如 linting)?

是,但並非所有功能都能正常運作。 LSP 語言伺服器延伸模組的最終目標是啟用 Visual Studio 原生不支援的語言服務。 您可以建立使用 LSP 語言伺服器提供額外支援的延伸模組,但某些功能(例如 IntelliSense)不會是順暢的體驗。 一般而言,建議使用 LSP 語言伺服器延伸模組來提供新的語言體驗,而不是擴充現有的語言。

我在哪裡發佈已完成的 LSP 語言伺服器 VSIX?

請參閱 Marketplace 的說明 此處