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
- Enviar dados adicionais ao iniciar um servidor de linguagem
- Definir tipos de documentos personalizados
- Habilitar ou desabilitar um servidor de linguagem
- Usar recursos localizados
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:
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), });
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())); }
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, enquantoLanguageServerInitializationFailureInfo
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
- Siga o tutorial Criar sua primeira extensão para começar a criar uma extensão.
- Consulte o exemplo do Provedor do servidor de linguagem Rust para entender como criar uma extensão usando um provedor do servidor de linguagem.