Dostawca serwera języka rozszerzalności

Dostawca serwera językowego obejmuje proces, który jest hostowany poza programem Visual Studio i udostępnia funkcje językowe, które nie są obecne w programie Visual Studio.

Te serwery muszą być zgodne z protokołem Language Server Protocol, utworzonym przez projekt rozszerzenia i zaimplementować .LanguageServerProvider

Praca z dostawcami serwerów językowych

To omówienie obejmuje następujące najważniejsze scenariusze pracy z dostawcami serwerów językowych:

Tworzenie dostawcy serwera językowego

Tworzenie dostawcy serwera językowego obejmuje dodanie nowej klasy, która rozszerza Microsoft.VisualStudio.Extensibility.LanguageServer.LanguageServerProvider i stosuje VisualStudioContribution do niego atrybut.

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

Po zdefiniowaniu dostawcy należy wykonać następujące kroki:

  1. Skonfiguruj dostawcę LanguageServerProviderConfiguration , przesłaniając właściwość . Ta właściwość konfiguracji definiuje nazwę wyświetlaną serwera i odpowiednie typy dokumentów. LanguageServerBaseDocumentType jest dostępny dla wszystkich serwerów i wyzwalaczy we wszystkich typach dokumentów. Zobacz Definiowanie niestandardowego typu dokumentu.

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new("My Language Server",
        new[]
        {
           DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
        });
    
  2. Zastąpi metodę CreateServerConnectionAsync , która jest wywoływana przez program Visual Studio, aby powiadomić rozszerzenie o konieczności uruchomienia serwera 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. Zastąpi metodę OnServerInitializationResultAsync , która jest wywoływana przez program Visual Studio po ukończeniu kroków uruchamiania i konfiguracji serwera LSP. ServerInitializationResult udostępnia wynikowy stan serwera i LanguageServerInitializationFailureInfo udostępnia wyjątek, jeśli istnieje.

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

Oto, jak wygląda nasz przykładowy dostawca serwera językowego po wykonaniu wszystkich kroków:

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

Wysyłanie dodatkowych danych podczas uruchamiania serwera językowego

LanguageServerOptions.InitializationOptions Można ustawić w konstruktorze LanguageServerProvider , aby wysyłać dodatkowe dane do serwera za pomocą komunikatu protokołu "inicjowanie".

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

Definiowanie niestandardowych typów dokumentów

Jeśli rozszerzenie obsługuje typy plików, które nie są natywnie obsługiwane przez program Visual Studio, autorzy rozszerzeń mogą implementować niestandardowe typy dokumentów. Tego typu można użyć podczas definiowania LanguageServerProviderConfiguration w celu określenia obsługiwanych typów dokumentów.

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

Ten fragment kodu definiuje dwa nowe typy dokumentów: rust i markdown. Te typy zawierają listę rozszerzeń plików i typ podstawowy, który może obejmować LanguageServerBaseDocumentType wszystkie typy.

Użyj tych typów w programie LanguageServerProviderConfiguration , aby aktywować serwer po otwarciu tych typów dokumentów:

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

Włączanie lub wyłączanie serwera językowego

Włączony serwer językowy może "aktywować" po otwarciu odpowiedniego typu dokumentu. Po wyłączeniu komunikat zatrzymania jest wysyłany do dowolnego odpowiedniego aktywnego serwera językowego i zapobiega dalszej aktywacji.

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

Ten fragment kodu wyłącza serwer językowy, ustawiając wartość this.Enabledfalse , jeśli ServerInitializationResult zostanie ustawiona na Failed wartość po niepowodzeniu inicjowania.

Uwaga

Ta flaga jest publiczna i jeśli ustawiono wartość false, wszystkie uruchomione serwery zostaną zatrzymane.

Korzystanie z zlokalizowanych zasobów

Obsługujemy używanie lokalizacji, definiując string-resources.json plik i używając %tokens% do określania zlokalizowanej zawartości.

string-resources.json

{
  { "LocalizedResource": "LangaugeServerLocalized" }
}

Uzyskiwanie dostępu do zlokalizowanego zasobu

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

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

Następne kroki

  • Postępuj zgodnie z samouczkiem dotyczącym tworzenia pierwszego rozszerzenia , aby rozpocząć tworzenie rozszerzenia.
  • Zobacz przykład dostawcy serwera języka Rust, aby zapoznać się z pełnym przykładem tworzenia rozszerzenia za pomocą dostawcy serwera językowego.