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

Область применения:yesVisual StudionoVisual Studio для Mac noVisual Studio Code

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечание

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

  1. Удалите VSIX с предварительной версией протокола языкового сервера Microsoft Visual Studio.

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

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

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

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

  5. заново собрать или повторно развернуть ресурс.

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

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

Затем создайте проект VSIX, перейдя в раздел Файл>Новый проект>Visual C#>Extensibility>VSIX Project:

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

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

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

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

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

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

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

  2. В папку Grammars добавьте все файлы *.tmlanguage, *.plist, *.tmtheme или *.json , которые предоставляют пользовательскую раскраску.

    Совет

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

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

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

После выполнения предыдущих шагов папка Grammars добавляется в каталог установки пакета в качестве источника репозитория 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) для управления точками расширяемости. Атрибут Export указывает 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-файл и выберите Свойства. Измените действие Сборка на "Содержимое" и свойство "Включить в 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-файл правой кнопкой мыши и выберите Пункт Свойства. Измените действие Сборка на Содержимое , а для свойства Включить в VSIX — значение true.

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

    Изменение ресурса 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.ilanguageclientcustommesssage.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-Extensibility-Samples LSP.

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

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

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

Разделы справки добавить поддержку отладчика?

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

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

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

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

См. инструкции по Marketplace здесь.

См. также раздел