Fournisseur de serveur de langage d'extensibilité

Un fournisseur de serveur de langage implique un processus hébergé en dehors de Visual Studio, et qui fournit des fonctionnalités de langage non présentes dans Visual Studio.

Ces serveurs doivent adhérer au protocole de serveur de langage, rédigé par un projet d'extension, et implémenter LanguageServerProvider.

Travailler avec des fournisseurs de serveur de langage

Cet aperçu couvre ces scénarios principaux pour travailler avec des fournisseurs de serveur de langage :

Créer un fournisseur de serveur de langage

Créer un fournisseur de serveur de langage implique d'ajouter une nouvelle classe qui étend Microsoft.VisualStudio.Extensibility.LanguageServer.LanguageServerProvider et d'appliquer l'attribut VisualStudioContribution à celle-ci.

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

Après avoir défini votre fournisseur, vous devez :

  1. Configurer votre fournisseur en surchargeant la propriété LanguageServerProviderConfiguration. Cette propriété de configuration définit le nom d'affichage du serveur et les types de documents applicables. LanguageServerBaseDocumentType est disponible pour tous les serveurs et se déclenche sur tous les types de documents. Consultez Définir un type de document personnalisé.

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new("My Language Server",
        new[]
        {
           DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
        });
    
  2. Forcer la méthode CreateServerConnectionAsync, qui est appelée par Visual Studio pour notifier l'extension que le serveur LSP devrait être démarré.

    // 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. Forcer la méthode OnServerInitializationResultAsync, qui est appelée par Visual Studio après que le serveur LSP a complété ses étapes de démarrage et de configuration. ServerInitializationResult fournit l'état résultant du serveur, et LanguageServerInitializationFailureInfo fournit une exception le cas échéant.

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

Voici à quoi ressemble notre exemple de fournisseur de serveur de langage après avoir complété toutes les étapes :

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

Envoyer des données supplémentaires lors du démarrage d'un serveur de langage

LanguageServerOptions.InitializationOptions peut être défini dans le constructeur pour LanguageServerProvider pour envoyer des données supplémentaires au serveur avec le message de protocole « initialize ».

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

Définir des types de documents personnalisés

Lorsqu'une extension prend en charge des types de fichiers qui ne sont pas nativement pris en charge par Visual Studio, les auteurs d'extensions peuvent implémenter des types de documents personnalisés. Ces types peuvent être utilisés lors de la définition de LanguageServerProviderConfiguration pour spécifier les types de documents pris en charge.

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

Cet extrait définit deux nouveaux types de documents : rust et markdown. Ces types contiennent une liste d'extensions de fichiers et un type de base, qui peut être LanguageServerBaseDocumentType pour couvrir tous les types.

Utilisez ces types dans LanguageServerProviderConfiguration pour activer votre serveur lorsque ces types de documents sont ouverts :

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

Activer ou désactiver un serveur de langage

Un serveur de langage activé est autorisé à « s'activer » une fois qu'un type de document applicable est ouvert. Lorsqu'il est désactivé, un message d'arrêt est envoyé à tout serveur de langage actif applicable et empêche de nouvelles activations.

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

Ce fragment de code désactive le serveur de langage en réglant this.Enabled sur false si ServerInitializationResult est réglé sur Failed après une initialisation échouée.

Remarque

Ce drapeau est public et s'il est réglé sur faux, tous les serveurs en cours sont arrêtés.

Utiliser des ressources localisées

Nous prenons en charge l'utilisation de la localisation en définissant un fichier string-resources.json et en utilisant %tokens% pour spécifier le contenu localisé.

string-resources.json

{
  { "LocalizedResource": "LangaugeServerLocalized" }
}

Accéder à une ressource localisée

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

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

Étapes suivantes