Поставщик сервера языка расширяемости

Поставщик сервера языка включает в себя процесс, размещенный за пределами Visual Studio, и который предоставляет функции языка, отсутствующие в Visual Studio.

Эти серверы должны соответствовать протоколу сервера языка, созданному проектом расширения, и реализовыватьLanguageServerProvider.

Работа с поставщиками серверов языка

В этом обзоре рассматриваются следующие основные сценарии работы с поставщиками серверов языка:

Создание поставщика сервера языка

Создание поставщика сервера языка включает добавление нового класса, расширяющего Microsoft.VisualStudio.Extensibility.LanguageServer.LanguageServerProvider и применяющего VisualStudioContribution атрибут к нему.

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

После определения поставщика необходимо выполнить следующие действия.

  1. Настройте поставщика, переопределив LanguageServerProviderConfiguration свойство. Это свойство конфигурации определяет отображаемое имя сервера и применимые типы документов. LanguageServerBaseDocumentType доступен для всех серверов и триггеров для всех типов документов. См. раздел "Определение пользовательского типа документа".

    public override LanguageServerProviderConfiguration LanguageServerProviderConfiguration => new("My Language Server",
        new[]
        {
           DocumentFilter.FromDocumentType(LanguageServerBaseDocumentType),
        });
    
  2. Переопределите CreateServerConnectionAsync метод, который вызывается Visual Studio, чтобы уведомить расширение о том, что сервер 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. Переопределите OnServerInitializationResultAsync метод, который вызывается Visual Studio после завершения действий по запуску и настройке сервера LSP. ServerInitializationResult предоставляет результирующее состояние сервера и LanguageServerInitializationFailureInfo предоставляет исключение, если таковой имеется.

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

Вот как выглядит наш пример поставщика языкового сервера после выполнения всех действий.

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

Отправка дополнительных данных при запуске сервера языка

LanguageServerOptions.InitializationOptions Можно задать в конструкторе для LanguageServerProvider отправки дополнительных данных на сервер с сообщением протокола initialize.

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

Определение пользовательских типов документов

Если расширение поддерживает типы файлов, которые изначально не поддерживаются Visual Studio, авторы расширений могут реализовать пользовательские типы документов. Эти типы можно использовать при определении LanguageServerProviderConfiguration для указания поддерживаемых типов документов.

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

Этот фрагмент определяет два новых типа документов: rust и markdown. Эти типы содержат список расширений файлов и базовый тип, который может LanguageServerBaseDocumentType охватывать все типы.

Используйте эти типы для LanguageServerProviderConfiguration активации сервера при открытии этих типов документов:

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

Включение или отключение сервера языка

После открытия применимого типа документа разрешено активировать сервер языка. При отключении сообщение остановки отправляется на любой применимый активный языковой сервер и предотвращает дальнейшие активации.

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

Этот фрагмент кода отключает сервер языка, задав this.Enabledfalse значение, если ServerInitializationResult задано значение Failed после сбоя инициализации.

Примечание.

Этот флаг является общедоступным и если задано значение false, все запущенные серверы остановлены.

Использование локализованных ресурсов

Мы поддерживаем локализацию, определяя string-resources.json файл и используя для %tokens% указания локализованного содержимого.

string-resources.json

{
  { "LocalizedResource": "LangaugeServerLocalized" }
}

Доступ к локализованным ресурсам

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

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

Следующие шаги