Поделиться через


Добавление расширения протокола сервера языка

Протокол сервера языка (LSP) — это общий протокол в виде JSON RPC версии 2.0, используемый для предоставления функций языковой службы различным редакторам кода. С помощью протокола разработчики могут написать один языковой сервер для предоставления таких функций службы языка, как IntelliSense, диагностика ошибок, поиск всех ссылок и т. д. различным редакторам кода, поддерживающим LSP. Традиционно языковые службы в Visual Studio можно добавлять с помощью файлов грамматики TextMate для предоставления основных функций, таких как выделение синтаксиса или написание пользовательских языковых служб, использующих полный набор API расширяемости Visual Studio для предоставления более подробных данных. При поддержке Visual Studio для LSP существует третий вариант.

служба протокола языкового сервера в Visual Studio

Чтобы обеспечить наилучший пользовательский интерфейс, рекомендуется также реализовать языковой конфигурации, которая обеспечивает локальную обработку многих из этих же операций и, следовательно, может повысить производительность многих операций редактора, поддерживаемых LSP.

Протокол сервера языка

реализация протокола сервера языка

В этой статье описывается создание расширения Visual Studio, использующего сервер языка на основе LSP. Предполагается, что вы уже разработали языковой сервер на основе LSP и просто хотите интегрировать его в Visual Studio.

Для поддержки в Visual Studio языковые серверы могут взаимодействовать с клиентом (Visual Studio) через любой механизм передачи на основе потоков, например:

  • Стандартные потоки ввода и вывода
  • Именованные каналы
  • Сокеты (только TCP)

Цель LSP и его поддержки в Visual Studio заключается в подключении языковых служб, которые не являются частью продукта Visual Studio. Он не предназначен для расширения существующих языковых служб (например, C#) в Visual Studio. Чтобы расширить существующие языки, обратитесь к руководству по расширяемости службы языков (например, платформа компиляторов "Roslyn" .NET) или см. Расширьте редактор и языковые службы.

Дополнительные сведения о самом протоколе см. в документации здесь.

Дополнительные сведения о том, как создать пример сервера языка или как интегрировать существующий языковой сервер в Visual Studio Code, см. в документации здесь.

Поддерживаемые функции протокола сервера языка

В следующих таблицах показано, какие функции LSP поддерживаются в Visual Studio:

Сообщение Имеется поддержка в Visual Studio
инициализировать да
инициализировано да
выключение да
выход да
$/cancelRequest да
окно/показатьСообщение да
window/showMessageRequest (показать запрос сообщения) да
window/logMessage да
телеметрия/событие
client/registerCapability
client/unregisterCapability
workspace/didChangeConfiguration да
workspace/didChangeWatchedFiles да
рабочая область или символ да
workspace/executeCommand да
рабочее пространство/применить изменения да
textDocument/publishDiagnostics да
textDocument/didOpen да
textDocument/didChange да
textDocument/willSave
textDocument/willSaveWaitUntil
textDocument/didSave да
textDocument/didClose да
textDocument/completion да
завершение/решение да
textDocument/hover да
textDocument/signatureHelp да
textDocument/ссылки да
документТекст/выделениеДокумента да
ТекстовыйДокумент/СимволДокумента да
textDocument/formatting да
textDocument/rangeFormatting да
textDocument/onTypeFormatting
textDocument/definition да
textDocument/codeAction да
textDocument/codeLens
codeLens/resolve
textDocument/documentLink
documentLink/resolve
textDocument/переименовать да

Начало работы

Заметка

Начиная с Visual Studio 2017 версии 15.8 поддержка протокола common Language Server встроена в Visual Studio. Если вы создали расширения LSP, используя предварительную клиентскую версию Language Server Client VSIX, они перестанут работать после обновления до версии 15.8 или более поздней. Для повторной работы расширений LSP вам потребуется выполнить следующие действия:

  1. Удалите пакет предварительной версии Microsoft Visual Studio Language Server Protocol VSIX.

    Начиная с версии 15.8 при каждом обновлении в Visual Studio предварительная версия VSIX автоматически обнаруживается и удаляется.

  2. Обновите ссылку на Nuget до последней стабильной версии, не являющейся предварительной, для пакетов LSP.

  3. Удалите зависимость от предварительной версии Microsoft Visual Studio Language Server Protocol (VSIX) в манифесте VSIX.

  4. Убедитесь, что VSIX указывает Visual Studio 2017 версии 15.8( предварительная версия 3) в качестве нижней границы для целевого объекта установки.

  5. Перестроение и повторное развертывание.

Создание проекта VSIX

Чтобы создать расширение языковой службы с помощью сервера языка на основе LSP, сначала убедитесь, что для вашего экземпляра VS установлен Visual Studio extension development Workload.

Затем создайте проект VSIX, перейдя к Файл>Новый проект>Visual C#>Расширяемость>Проект VSIX:

создание проекта vsix

Установка сервера языка и среды выполнения

По умолчанию расширения, созданные для поддержки серверов языка на основе LSP в Visual Studio, не содержат сами языковые серверы или среды выполнения, необходимые для их выполнения. Разработчики расширений отвечают за распространение языковых серверов и необходимых сред выполнения. Это можно сделать несколькими способами.

  • Языковые серверы могут быть внедрены в VSIX в виде файлов содержимого.
  • Создайте MSI для установки языкового сервера и (или) необходимых сред выполнения.
  • Предоставьте инструкции в Marketplace, информируя пользователей о том, как получать среды выполнения и языковые серверы.

Файлы грамматики TextMate

LSP не содержит спецификации о том, как предоставлять цвет текста для языков. Чтобы обеспечить настраиваемую цветизацию для языков в Visual Studio, разработчики расширений могут использовать файл грамматики TextMate. Чтобы добавить пользовательские файлы грамматики TextMate или темы, выполните следующие действия.

  1. Создайте папку с именем "Грамматики" внутри расширения (или это может быть любое имя, выбранное вами).

  2. В папке Грамматики включите все *.tmlanguage, *.plist, *.tmthemeили *.json файлы, которые вы хотите использовать для настройки цветов.

    Совет

    Файл .tmtheme определяет, как области связаны с классификациями Visual Studio (именованные ключи цвета). Для справки можно ссылаться на глобальный файл .tmtheme в каталоге %ProgramFiles(x86)%\Microsoft Visual Studio\<версии>\<SKU>\Common7\IDE\CommonExtensions\Microsoft\TextMate\Starterkit\Themesg.

  3. Создайте файл PKGDEF и добавьте строку, аналогичную следующей:

    [$RootKey$\TextMate\Repositories]
    "MyLang"="$PackageFolder$\Grammars"
    
  4. Щелкните правой кнопкой мыши на файлы и выберите Свойства. Измените действие сборки на Content и измените свойство Include в VSIX на true.

После выполнения предыдущих шагов папка Грамматики добавляется в каталог установки пакета в качестве источника репозитория с именем 'MyLang' ('MyLang' — это просто имя для дезамбигуации и может быть любой уникальной строкой). Все грамматики (.tmlanguage файлы) и файлы тем (.tmtheme файлы) в этом каталоге рассматриваются как потенциальные и заменяют собой встроенные грамматики, предоставляемые TextMate. Если объявленные расширения файла грамматики соответствуют расширению открываемого файла, TextMate выполнит шаг.

Создание простого клиента языка

Основной интерфейс — ILanguageClient

После создания проекта VSIX добавьте в проект следующие пакеты NuGet:

Заметка

После выполнения предыдущих действий в проект добавляются зависимости от пакета NuGet. Пакеты Newtonsoft.Json и StreamJsonRpc также добавляются в проект. Не обновляйте эти пакеты, если вы не уверены, что эти новые версии будут установлены в версии Visual Studio, на которую предназначено расширение. Сборки не будут включены в VSIX; вместо этого они будут получены из каталога установки Visual Studio. Если вы ссылаетесь на более новую версию сборок, чем то, что установлено на компьютере пользователя, расширение не будет работать.

Затем можно создать новый класс, реализующий интерфейс ILanguageClient, который является основным интерфейсом, необходимым для клиентов языка, подключающихся к серверу языка на основе LSP.

Ниже приведен пример:

namespace MockLanguageExtension
{
    [ContentType("bar")]
    [Export(typeof(ILanguageClient))]
    public class BarLanguageClient : ILanguageClient
    {
        public string Name => "Bar Language Extension";

        public IEnumerable<string> ConfigurationSections => null;

        public object InitializationOptions => null;

        public IEnumerable<string> FilesToWatch => null;

        public event AsyncEventHandler<EventArgs> StartAsync;
        public event AsyncEventHandler<EventArgs> StopAsync;

        public async Task<Connection> ActivateAsync(CancellationToken token)
        {
            await Task.Yield();

            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Server", @"MockLanguageServer.exe");
            info.Arguments = "bar";
            info.RedirectStandardInput = true;
            info.RedirectStandardOutput = true;
            info.UseShellExecute = false;
            info.CreateNoWindow = true;

            Process process = new Process();
            process.StartInfo = info;

            if (process.Start())
            {
                return new Connection(process.StandardOutput.BaseStream, process.StandardInput.BaseStream);
            }

            return null;
        }

        public async Task OnLoadedAsync()
        {
            await StartAsync.InvokeAsync(this, EventArgs.Empty);
        }

        public Task OnServerInitializeFailedAsync(Exception e)
        {
            return Task.CompletedTask;
        }

        public Task OnServerInitializedAsync()
        {
            return Task.CompletedTask;
        }
    }
}

Основными методами, которые необходимо реализовать, являются OnLoadedAsync и ActivateAsync. OnLoadedAsync вызывается, когда расширение Visual Studio загружено и сервер языка готов к запуску. В этом методе можно вызвать делегат StartAsync немедленно, чтобы сообщить о том, что сервер языка должен быть запущен, или можно выполнить дополнительную логику и вызвать StartAsync позже. Чтобы активировать сервер языка, необходимо вызвать StartAsync в какой-то момент.

ActivateAsync — это метод, который в конечном счёте вызывается через вызов делегата StartAsync. Он содержит логику запуска сервера языка и установления подключения к нему. Объект подключения, содержащий потоки для записи на сервер и чтения с сервера, должен быть возвращен. Любые исключения, которые выбрасываются здесь, перехватываются и посредством сообщения InfoBar в Visual Studio отображаются пользователю.

Активация

После реализации клиентского класса языка необходимо определить два атрибута, чтобы определить, как он будет загружен в Visual Studio и активирован:

  [Export(typeof(ILanguageClient))]
  [ContentType("bar")]

MEF

Visual Studio использует MEF (Managed Extensibility Framework) для управления точками расширяемости. Атрибут экспорта указывает в Visual Studio, что этот класс должен быть выбран в качестве точки расширения и загружен в соответствующее время.

Чтобы использовать MEF, необходимо также определить MEF как ресурс в манифесте VSIX.

Откройте редактор манифестов VSIX и перейдите на вкладку Активы:

добавить актив MEF

Щелкните Новый, чтобы создать новый ресурс.

определение ресурса MEF

  • тип: Microsoft.VisualStudio.MefComponent
  • Источник: проект в текущем решении
  • Project: [Ваш проект]

Определение типа контента

В настоящее время единственным способом загрузки расширения сервера языка, основанного на LSP, является тип содержимого файла. То есть при определении клиентского класса языка (который реализует ILanguageClient), необходимо определить типы файлов, которые при открытии приведут к загрузке расширения. Если файлы, соответствующие определенному типу контента, открыты не будут, расширение не будет загружено.

Это делается путем определения одного или нескольких классов ContentTypeDefinition:

namespace MockLanguageExtension
{
    public class BarContentDefinition
    {
        [Export]
        [Name("bar")]
        [BaseDefinition(CodeRemoteContentDefinition.CodeRemoteContentTypeName)]
        internal static ContentTypeDefinition BarContentTypeDefinition;

        [Export]
        [FileExtension(".bar")]
        [ContentType("bar")]
        internal static FileExtensionToContentTypeDefinition BarFileExtensionDefinition;
    }
}

В предыдущем примере определение типа контента создается для файлов, которые заканчиваются расширением .bar. Определение типа контента присваивается имени "bar" и должно быть производным от CodeRemoteContentTypeName.

После добавления определения типа контента можно определить время загрузки расширения клиента языка в классе клиента языка:

    [ContentType("bar")]
    [Export(typeof(ILanguageClient))]
    public class BarLanguageClient : ILanguageClient
    {
    }

Добавление поддержки для серверов языка LSP не требует реализации собственной системы проектов в Visual Studio. Клиенты могут открыть один файл или папку в Visual Studio, чтобы начать использовать языковую службу. На самом деле поддержка языковых серверов LSP предназначена для работы только в сценариях с открытыми папками и файлами. Если пользовательская система проектов реализована, некоторые функции (например, параметры) не будут работать.

Дополнительные функции

Параметры

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

Выполните следующие действия, чтобы добавить поддержку параметров в расширение языковой службы LSP:

  1. Добавьте JSON-файл (например, MockLanguageExtensionSettings.json) в проект, содержащий параметры и значения по умолчанию. Например:

    {
        "foo.maxNumberOfProblems": -1
    }
    
  2. Щелкните файл JSON правой кнопкой мыши и выберите Свойства. Измените действие сборки на Content и свойство "Включить в VSIX", чтобы true.

  3. Реализуйте configurationSections и верните список префиксов для параметров, определенных в JSON-файле (в Visual Studio Code это будет сопоставляться с именем раздела конфигурации в package.json):

    public IEnumerable<string> ConfigurationSections
    {
        get
        {
            yield return "foo";
        }
    }
    
  4. Добавьте pkgdef-файл в проект (добавьте новый текстовый файл и измените расширение файла на PKGDEF). Pkgdef-файл должен содержать следующие сведения:

    [$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\[settings-name]]
    @="$PackageFolder$\[settings-file-name].json"
    

    Образец:

    [$RootKey$\OpenFolder\Settings\VSWorkspaceSettings\MockLanguageExtension]
    @="$PackageFolder$\MockLanguageExtensionSettings.json"
    
  5. Щелкните правой кнопкой мыши на файле .pkgdef и выберите Свойства. Измените действие сборки на Content и Include в свойстве VSIX, чтобы true.

  6. Откройте файл source.extension.vsixmanifest и добавьте ресурс на вкладке Asset:

    редактирование ресурса vspackage

    • Тип: Microsoft.VisualStudio.VsPackage
    • источник: файл в файловой системе
    • Путь: [Путь к файлу .pkgdef]

Изменение параметров рабочей области пользователем

  1. Пользователь открывает рабочую область, содержащую файлы, принадлежащие вашему серверу.

  2. Пользователь добавляет файл в папку .vs с именем VSWorkspaceSettings.json.

  3. Пользователь добавляет строку в файл VSWorkspaceSettings.json для задания сервера. Например:

    {
        "foo.maxNumberOfProblems": 10
    }
    

Включите трассировку диагностики

Включение трассировки диагностики позволяет выводить все сообщения между клиентом и сервером, что может быть полезно при отладке проблем. Чтобы включить трассировку диагностики, сделайте следующее:

  1. Откройте или создайте файл параметров рабочей области VSWorkspaceSettings.json (см. раздел "Изменение пользователем параметров для рабочей области").
  2. Добавьте следующую строку в json-файл параметров:
{
    "foo.trace.server": "Off"
}

Существует три возможных значения для детализации трассировки:

  • "Выключено: трассировка отключена полностью"
  • "Сообщения": трассировка включена, но трассируются только имя метода и идентификатор ответа.
  • "Подробно": включена трассировка; выполняется трассировка всего сообщения RPC.

При включении трассировки содержимое записывается в файл в каталоге %temp%\VisualStudio\LSP. Лог следует формату именования [LanguageClientName]-[Метка даты и времени].log. В настоящее время трассировка может быть включена только для сценариев открытой папки. Открытие одного файла для активации языкового сервера не поддерживает отслеживание диагностики.

Пользовательские сообщения

Существуют API для упрощения передачи сообщений и получения сообщений с сервера языка, которые не являются частью стандартного протокола сервера языка. Чтобы обрабатывать пользовательские сообщения, реализуйте интерфейс ILanguageClientCustomMessage2 в клиентском классе языка. библиотека VS-StreamJsonRpc используется для передачи пользовательских сообщений между языковым клиентом и языковым сервером. Так как расширение клиента языка LSP аналогично любому другому расширению Visual Studio, вы можете добавить дополнительные функции (которые не поддерживаются LSP) в Visual Studio (с помощью других API Visual Studio) в расширение с помощью пользовательских сообщений.

Получать пользовательские сообщения

Чтобы получать пользовательские сообщения с языкового сервера, реализуйте это свойство [CustomMessageTarget]((/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) на ILanguageClientCustomMessage2 и верните объект, который умеет обрабатывать пользовательские сообщения. Пример ниже:

(/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage.custommessagetarget) в ILanguageClientCustomMessage2 и возвращает объект, который знает, как обрабатывать пользовательские сообщения. Пример ниже:

internal class MockCustomLanguageClient : MockLanguageClient, ILanguageClientCustomMessage2
{
    private JsonRpc customMessageRpc;

    public MockCustomLanguageClient() : base()
    {
        CustomMessageTarget = new CustomTarget();
    }

    public object CustomMessageTarget
    {
        get;
        set;
    }

    public class CustomTarget
    {
        public void OnCustomNotification(JToken arg)
        {
            // Provide logic on what happens OnCustomNotification is called from the language server
        }

        public string OnCustomRequest(string test)
        {
            // Provide logic on what happens OnCustomRequest is called from the language server
        }
    }
}

Отправка пользовательских сообщений

Чтобы отправить пользовательские сообщения на языковой сервер, реализуйте метод AttachForCustomMessageAsync в ILanguageClientCustomMessage2. Этот метод вызывается при запуске и готовности сервера языка к получению сообщений. Объект JsonRpc передается в качестве параметра, который можно затем сохранить для отправки сообщений на языковой сервер с использованием API VS-StreamJsonRpc. Пример ниже:

internal class MockCustomLanguageClient : MockLanguageClient, ILanguageClientCustomMessage2
{
    private JsonRpc customMessageRpc;

    public MockCustomLanguageClient() : base()
    {
        CustomMessageTarget = new CustomTarget();
    }

    public async Task AttachForCustomMessageAsync(JsonRpc rpc)
    {
        await Task.Yield();

        this.customMessageRpc = rpc;
    }

    public async Task SendServerCustomNotification(object arg)
    {
        await this.customMessageRpc.NotifyWithParameterObjectAsync("OnCustomNotification", arg);
    }

    public async Task<string> SendServerCustomMessage(string test)
    {
        return await this.customMessageRpc.InvokeAsync<string>("OnCustomRequest", test);
    }
}

Средний слой

Иногда разработчик расширений может перехватывать сообщения LSP, отправленные и полученные с сервера языка. Например, разработчик расширений может изменить параметр сообщения, отправленный для определенного сообщения LSP, или изменить результаты, возвращаемые с сервера языка для функции LSP (например, завершения). При необходимости разработчики расширений могут использовать API MiddleLayer для перехвата сообщений LSP.

Чтобы перехватить определенное сообщение, создайте класс, реализующий интерфейс ILanguageClientMiddleLayer. Затем реализуйте интерфейс ILanguageClientCustomMessage2 в клиентском классе языка и возвратите экземпляр объекта в свойстве MiddleLayer. Пример ниже:

public class MockLanguageClient : ILanguageClient, ILanguageClientCustomMessage2
{
  public object MiddleLayer => DiagnosticsFilterMiddleLayer.Instance;

  private class DiagnosticsFilterMiddleLayer : ILanguageClientMiddleLayer
  {
    internal readonly static DiagnosticsFilterMiddleLayer Instance = new DiagnosticsFilterMiddleLayer();

    private DiagnosticsFilterMiddleLayer() { }

    public bool CanHandle(string methodName)
    {
      return methodName == "textDocument/publishDiagnostics";
    }

    public async Task HandleNotificationAsync(string methodName, JToken methodParam, Func<JToken, Task> sendNotification)
    {
      if (methodName == "textDocument/publishDiagnostics")
      {
        var diagnosticsToFilter = (JArray)methodParam["diagnostics"];
        // ony show diagnostics of severity 1 (error)
        methodParam["diagnostics"] = new JArray(diagnosticsToFilter.Where(diagnostic => diagnostic.Value<int?>("severity") == 1));

      }
      await sendNotification(methodParam);
    }

    public async Task<JToken> HandleRequestAsync(string methodName, JToken methodParam, Func<JToken, Task<JToken>> sendRequest)
    {
      return await sendRequest(methodParam);
    }
  }
}

Компонент среднего слоя по-прежнему находится в процессе разработки и еще не исчерпывающий.

Пример расширения сервера языка LSP

Чтобы просмотреть исходный код примера расширения, использующего клиентский API LSP в Visual Studio, см. VSSDK- LSP примерExtensibility-Samples .

Вопросы и ответы

я хотел бы создать пользовательскую систему проектов, чтобы дополнить сервер языка LSP, чтобы обеспечить более богатую поддержку функций в Visual Studio, как это сделать?

Поддержка языковых серверов на основе LSP в Visual Studio зависит от функции открытой папки и не требует кастомизированной системы проектов. Вы можете создать собственную настраиваемую систему проектов, следуя инструкциям здесь, но некоторые функции, такие как параметры, могут не работать. Логика инициализации по умолчанию для серверов языка LSP — передать расположение корневой папки открываемой папки, поэтому, если вы используете пользовательскую систему проектов, может потребоваться предоставить пользовательскую логику во время инициализации, чтобы обеспечить правильность запуска сервера языка.

Как добавить поддержку отладчика?

Мы предоставим поддержку общего протокола отладки в одной из следующих версий.

Если уже установлена поддерживаемая языковая служба VS (например, JavaScript), можно ли установить расширение сервера языка LSP, которое предлагает дополнительные функции (например, linting)?

Да, но не все функции будут работать должным образом. Конечная цель расширений сервера языка LSP — включить языковые службы, которые изначально не поддерживаются Visual Studio. Вы можете создавать расширения, которые обеспечивают дополнительную поддержку с помощью языковых серверов LSP, но некоторые функции (например, IntelliSense) не будут гладкими. Как правило, рекомендуется использовать расширения сервера языка LSP для предоставления новых языковых возможностей, а не расширения существующих.

Где опубликовать завершенный сервер языка LSP VSIX?

См. инструкции Маркеплейса здесь.