扩展性语言服务器提供程序

语言服务器提供程序涉及托管在 Visual Studio 外部的进程,并提供 Visual Studio 中不存在的语言功能。

这些服务器必须遵循通过扩展项目创作的语言服务器协议,并实现 LanguageServerProvider

使用语言服务器提供程序

本概述介绍使用语言服务器提供程序的以下主要情形:

创建语言服务器提供程序

创建语言服务器提供程序涉及添加一个新类来扩展 Microsoft.VisualStudio.Extensibility.LanguageServer.LanguageServerProvider 以及将 VisualStudioContribution 属性应用于此新类。

[VisualStudioContribution]
public class MyLanguageServerProvider : LanguageServerProvider
{
    public MyLanguageServerProvider(ExtensionCore container, VisualStudioExtensibility extensibilityObject, TraceSource traceSource)
        : base(container, extensibilityObject)
    {
    }
}

定义提供程序后,你需要:

  1. 通过替代 LanguageServerProviderConfiguration 属性来配置提供程序。 此配置属性定义服务器显示名称和适用的文档类型。 LanguageServerBaseDocumentType 适用于所有服务器并在所有文档类型上触发。 请参阅定义自定义文档类型

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new("My Language Server",
        new[]
        {
           DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
        });
    
  2. 替代 CreateServerConnectionAsync 方法,Visual Studio 会调用此方法,以通知扩展应启动 LSP 服务器。

    // Activate the language server and return a duplex pipe that communicates with the server. 
    public override Task<IDuplexPipe?> CreateServerConnectionAsync(CancellationToken cancellationToken)
    {
        (Stream PipeToServer, Stream PipeToVS) = FullDuplexStream.CreatePair();
    
        // Connect "PipeToServer" to the language server
    
        return Task.FromResult<IDuplexPipe?>(new DuplexPipe(PipeToVS.UsePipeReader(), PipeToVS.UsePipeWriter()));
    }
    
  3. 替代 OnServerInitializationResultAsync 方法,在 LSP 服务器完成其启动和配置步骤后,Visual Studio 会调用此方法。 ServerInitializationResult 提供服务器的结果状态,并且 LanguageServerInitializationFailureInfo 会提供异常(如果有)。

    public override Task OnServerInitializationResultAsync(ServerInitializationResult startState,LanguageServerInitializationFailureInfo?     initializationFailureInfo, CancellationToken cancellationToken)
    {
        // Method called when server activation was completed successfully or failed, denoted by "startState".
        return Task.CompletedTask;
    }
    

完成所有步骤后,示例语言服务器提供程序如下所示:

[VisualStudioContribution]
public class MyLanguageServerProvider : LanguageServerProvider
{
    public MyLanguageServerProvider(ExtensionCore container, VisualStudioExtensibility extensibilityObject, TraceSource traceSource)
        : base(container, extensibilityObject)
    {
    }

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration =>
        new("My Language Server",
            new[]
            {
               DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
            });

    // Activate the language server and return a duplex pipe that communicates with the server. 
    public override Task<IDuplexPipe?> CreateServerConnectionAsync(CancellationToken cancellationToken)
    {
        (Stream PipeToServer, Stream PipeToVS) = FullDuplexStream.CreatePair();

        // Connect "PipeToServer" to the language server

        return Task.FromResult<IDuplexPipe?>(new DuplexPipe(PipeToVS.UsePipeReader(), PipeToVS.UsePipeWriter()));
    }

    public override Task OnServerInitializationResultAsync(ServerInitializationResult startState, LanguageServerInitializationFailureInfo? initializationFailureInfo, CancellationToken cancellationToken)
    {
        // Method called when server activation was completed successfully or failed, denoted by "startState".
        return Task.CompletedTask;
    }
}

启动语言服务器时发送其他数据

LanguageServerOptions.InitializationOptions 可以在 LanguageServerProvider 的构造函数中设置,以便通过“初始化”协议消息将其他数据发送到服务器。

public MyLanguageServerProvider(ExtensionCore container, VisualStudioExtensibility extensibilityObject, TraceSource traceSource)
    : base(container, extensibilityObject)
{
    this.LanguageServerOptions.InitializationOptions = JToken.Parse(@"[{""server"":""initialize""}]");
}

定义自定义文档类型

当扩展支持 Visual Studio 本身不支持的文件类型时,扩展作者可以实现自定义文档类型。 定义 LanguageServerProviderConfiguration 以指定支持的文档类型时,可以使用这些类型。

[VisualStudioContribution]
internal static DocumentTypeConfiguration RustDocumentType => new("rust")
{
    FileExtensions = new[] { ".rs", ".rust" },
    BaseDocumentType = LanguageServerBaseDocumentType,
};

[VisualStudioContribution]
internal static DocumentTypeConfiguration MarkdownDocumentType => new("markdown")
{
    FileExtensions = new[] { ".md" },
    BaseDocumentType = LanguageServerBaseDocumentType,
};

此代码片段定义以下两个新的文档类型:rustmarkdown。 这些类型包含文件扩展名列表和基类型,此基类型可能是用于涵盖所有类型的 LanguageServerBaseDocumentType

打开这些文档类型时,请在 LanguageServerProviderConfiguration 中使用这些类型激活服务器:

public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration =>
    new("My Language Server",
        new[]
        {
            DocumentFilter.FromDocumentType(RustDocumentType),
            DocumentFilter.FromDocumentType(MarkdownDocumentType),
        });

启用或禁用语言服务器

打开适用的文档类型后,允许“激活”启用的语言服务器。 禁用后,会将停止消息发送到任何适用的活动语言服务器,并阻止进一步激活。

[VisualStudioContribution]
public class MyLanguageServerProvider : LanguageServerProvider
{
    ...

    public override Task OnServerInitializationResultAsync(ServerInitializationResult startState, LanguageServerInitializationException? initializationFailureInfo, CancellationToken cancellationToken)
    {
        if (startState == ServerInitializationResult.Failed)
        {
            Telemetry.LogEvent(initializationFailureInfo.StatusMessage, initializationFailureInfo.Exception)

            // Disable the language server.
            this.Enabled = false;
        }
    }
}

如果 ServerInitializationResult 在初始化失败后设置为 Failed,则此代码片段会通过将 this.Enabled 设置为 false 来禁用语言服务器。

注意

此标志是公共标志,如果设置为 false,则会停止任何正在运行的服务器。

使用本地化资源

我们支持通过定义 string-resources.json 文件来使用本地化,以及使用 %tokens% 来指定本地化内容。

string-resources.json

{
  { "LocalizedResource": "LangaugeServerLocalized" }
}

访问本地化资源

[VisualStudioContribution]
public class MyLanguageServer : LanguageServerProvider
{
    ...

    /// <inheritdoc/>
    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration =>
        new("%LocalizedResource%",
            new[]
            {
                DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType)
            });
}

后续步骤