Partilhar via


Extensibilidade do Provedor do servidor de linguagem

Um provedor do servidor de linguagem envolve um processo que é hospedado fora do Visual Studio e que oferece recursos de linguagem que Visual Studio não contém.

Esses servidores devem aderir ao Language Server Protocol, criado por um projeto de extensão, e implementar o LanguageServerProvider.

Trabalhar com provedores de servidor de linguagem

Nesta visão geral, você encontra os principais cenários para trabalhar com provedores de servidor de linguagem:

Criar um provedor do servidor de linguagem

A criação de um provedor de servidor de linguagem envolve a adição de uma nova classe que estende Microsoft.VisualStudio.Extensibility.LanguageServer.LanguageServerProvider e a aplicação do atributo VisualStudioContribution a ela.

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

Depois de definir seu provedor, você precisa:

  1. Configurar o provedor substituindo a propriedade LanguageServerProviderConfiguration. Essa propriedade de configuração define o nome de exibição do servidor e os tipos de documento aplicáveis. LanguageServerBaseDocumentType está disponível para todos os servidores e gatilhos em todos os tipos de documentos. Consulte Definir um tipo de documento personalizado.

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new("My Language Server",
        new[]
        {
           DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
        });
    
  2. Substitua o método CreateServerConnectionAsync, que é chamado pelo Visual Studio para notificar a extensão de que o servidor LSP deve ser iniciado.

    // 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. Substitua o método OnServerInitializationResultAsync, que é chamado pelo Visual Studio depois que o servidor LSP concluiu suas etapas de inicialização e configuração. ServerInitializationResult fornece o estado resultante do servidor, enquanto LanguageServerInitializationFailureInfo fornece uma exceção, se houver.

    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;
    }
    

Veja como fica o provedor de servidor de linguagem de exemplo depois de concluir todas as etapas:

[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;
    }
}

Enviar dados adicionais ao iniciar um servidor de linguagem

LanguageServerOptions.InitializationOptions pode ser definido no construtor de LanguageServerProvider para enviar dados adicionais para o servidor com a mensagem de protocolo "initialize".

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

Definir tipos de documentos personalizados

Quando uma extensão oferece suporte a tipos de arquivos que não são suportados nativamente pelo Visual Studio, os autores da extensão podem implementar tipos de documento personalizados. Esses tipos podem ser usados ao definir LanguageServerProviderConfiguration para especificar os tipos de documentos compatíveis.

[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,
};

Este trecho define dois novos tipos de documentos: rust e markdown. Esses tipos contêm uma lista de extensões de arquivo e um tipo de base, que pode ser LanguageServerBaseDocumentType para cobrir todos os tipos.

Use esses tipos no LanguageServerProviderConfiguration para ativar o servidor quando esses tipos de documentos forem abertos:

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

Habilitar ou desabilitar um servidor de linguagem

Um servidor de linguagem habilitado tem permissão para "ativar" quando um tipo de documento aplicável é aberto. Quando ele está desabilitado, uma mensagem de parada é enviada para qualquer servidor de linguagem ativo aplicável e impede ativações adicionais.

[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;
        }
    }
}

Esse trecho de código desabilita o servidor de linguagem definindo this.Enabled como false if ServerInitializationResult gets set to Failed depois que a inicialização falha.

Observação

Esse sinalizador é público e, se definido como false, todos os servidores em execução são interrompidos.

Usar recursos localizados

A localização é compatível quando definimos um arquivo string-resources.json e usamos %tokens% para especificar o conteúdo localizado.

string-resources.json

{
  { "LocalizedResource": "LangaugeServerLocalized" }
}

Acessar um recurso localizado

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

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

Próximas etapas