Proveedor de servidor de lenguaje de extensibilidad

Un proveedor de servidor de lenguaje es un proceso que se hospeda fuera de Visual Studio y que proporciona funciones de lenguaje que no están presentes en Visual Studio.

Estos servidores deben cumplir el protocolo de servidor de lenguaje, creado por un proyecto de extensión e implementar LanguageServerProvider.

Trabajo con proveedores de servidor de lenguaje

En esta introducción se describen estos principales escenarios para trabajar con proveedores de servidores de lenguaje:

Creación de un proveedor de servidor de lenguaje

La creación de un proveedor de servidor de lenguaje implica agregar una nueva clase que extienda Microsoft.VisualStudio.Extensibility.LanguageServer.LanguageServerProvider y le aplique el atributo VisualStudioContribution.

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

Después de definir el proveedor, debe hacer lo siguiente:

  1. Configure el proveedor anulando la propiedad LanguageServerProviderConfiguration. Esta propiedad de configuración define el nombre para mostrar del servidor y los tipos de documento aplicables. LanguageServerBaseDocumentType está disponible para todos los servidores y desencadenadores en todos los tipos de documento. Consulte Definición de un tipo de documento personalizado.

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new("My Language Server",
        new[]
        {
           DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
        });
    
  2. Anule el método CreateServerConnectionAsync, al que llama Visual Studio para notificar a la extensión que se debe iniciar el servidor 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. Anule el método OnServerInitializationResultAsync, al que llama Visual Studio después de que el servidor LSP haya completado sus pasos de inicio y configuración. ServerInitializationResult proporciona el estado resultante del servidor y LanguageServerInitializationFailureInfo proporciona una excepción si existe.

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

Este es el aspecto de nuestro proveedor de servidor de lenguaje de muestra después de completar todos los pasos:

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

Envío de datos adicionales al iniciar un servidor de lenguaje

LanguageServerOptions.InitializationOptions se puede establecer en el constructor para que LanguageServerProvider envíe datos adicionales al servidor con el mensaje de protocolo "initialize".

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

Definición de tipos de documentos personalizados

Cuando una extensión admite tipos de archivo que no son compatibles de forma nativa con Visual Studio, los autores de extensiones pueden implementar tipos de documentos personalizados. Estos tipos se pueden usar al definir LanguageServerProviderConfiguration para especificar los tipos de documento admitidos.

[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 fragmento de código define dos nuevos tipos de documento: rust y markdown. Estos tipos contienen una lista de extensiones de archivo y un tipo base, que puede ser LanguageServerBaseDocumentType para cubrir todos los tipos.

Use estos tipos en LanguageServerProviderConfiguration para activar el servidor cuando se abran estos tipos de documento:

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

Habilitación o deshabilitación de un servidor de lenguaje

Un servidor de lenguaje habilitado se puede "activar" una vez abierto un tipo de documento aplicable. Cuando está deshabilitado, se envía un mensaje de detención a cualquier servidor de lenguaje activo aplicable y se evitan más activaciones.

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

Este fragmento de código deshabilita el servidor de lenguaje estableciendo this.Enabled en false si ServerInitializationResult se establece en Failed después de que falle la inicialización.

Nota:

Esta marca es pública y, si se establece en falsa, se detienen los servidores en ejecución.

Uso de recursos localizados

Se admite el uso de la localización mediante la definición de un archivo string-resources.json y el uso de %tokens% para especificar contenido localizado.

string-resources.json

{
  { "LocalizedResource": "LangaugeServerLocalized" }
}

Acceso a un recurso localizado

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

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

Pasos siguientes