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

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

language server protocol service in Visual Studio

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

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

language server protocol implementation

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

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

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

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

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

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

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

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

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

Начать

Примечание.

Начиная с 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) в манифесте VSIX.

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

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

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

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

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

create vsix project

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

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

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

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

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

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

  2. В папку "Грамматики" включайте все файлы *.tmlanguage, *.plist, *.tmtheme или *.json файлы, которые обеспечивают настраиваемую цветовую настройку.

    Совет

    Файл .tmtheme определяет, как область s сопоставляется с классификациями 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. Щелкните правой кнопкой мыши файлы и выберите пункт "Свойства". Измените действие сборки на Content и измените значение Include в свойстве VSIX на true.

После выполнения предыдущих шагов папка "Грамматики " добавляется в каталог установки пакета в качестве источника репозитория с именем MyLang (MyLang" — это просто имя для диамбигуации и может быть любой уникальной строкой). Все грамматики (Tmlanguage-файлы ) и файлы тем (.tmtheme files) в этом каталоге выбираются как потенциальные, и они заменяют встроенные грамматики, предоставляемые 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 и перейдите на вкладку "Активы" :

add MEF asset

Нажмите кнопку "Создать" , чтобы создать новый ресурс:

define MEF asset

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

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

В настоящее время единственным способом загрузки расширения сервера языка на основе 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-файл (например, MockLanguageExtension Параметры.json) в проект, содержащий параметры и их значения по умолчанию. Например:

    {
        "foo.maxNumberOfProblems": -1
    }
    
  2. Щелкните правой кнопкой мыши JSON-файл и выберите "Свойства". Измените действие сборки на Content и свойство Include in 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 и добавьте ресурс на вкладке "Ресурс":

    edit vspackage asset

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

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

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

  2. Пользователь добавляет файл в папку VSWorkspace Параметры.json.

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

    {
        "foo.maxNumberOfProblems": 10
    }
    

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

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

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

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

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

Если трассировка включена, содержимое записывается в файл в каталоге %temp%\VisualStudio\LSP . Журнал следует формату именования [LanguageClientName]-[Метка datetime].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, см. пример LSP VSSDK-Extensibility-Samples LSP.

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

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

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

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

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

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

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

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

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