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

Применимо к:yes Visual Studio noVisual Studio для Mac noVisual Studio Code

Что такое протокол сервера языка?

Поддержка расширенных функций редактирования, таких как автозавершение исходного кода или переход к определению для языка программирования в редакторе или интегрированной среде разработки, традиционно является очень сложной задачей и занимает много времени. Обычно для этого требуется написание модели предметной области (сканер, средство синтаксического анализа, средство проверки типов, построитель и многое другое) на языке программирования редактора или интегрированной среды разработки. Например, подключаемый модуль Eclipse CDT, который обеспечивает поддержку C/C++ в интегрированной среде разработки Eclipse, написан на Java, так как сама интегрированная среда разработки Eclipse написана на Java. Следуя этому подходу, это означает реализацию модели предметной области C/C++ в TypeScript для Visual Studio Code и отдельную модель предметной области в C# для Visual Studio.

Создание моделей предметной области для конкретного языка также гораздо проще, если средство разработки может повторно использовать существующие библиотеки, относящиеся к языку. Однако эти библиотеки обычно реализуются на самом языке программирования (например, хорошие модели предметной области C/C++ реализуются в C/C++). Интеграция библиотеки C/C++ в редактор, написанный на TypeScript, технически возможна, но трудно сделать.

Языковые серверы

Другой подход заключается в том, чтобы запустить библиотеку в собственном процессе и использовать межпроцессное взаимодействие для взаимодействия с ней. Сообщения, отправляемые обратно и вперед, образуют протокол. Протокол сервера языка (LSP) — это продукт стандартизации сообщений, обменивающихся между средством разработки и процессом сервера языка. Использование языковых серверов или демонов не является новой или новой идеей. Редакторы, такие как Vim и Emacs, делали это в течение некоторого времени, чтобы обеспечить поддержку семантического автозавершения. Цель LSP заключается в упрощении таких интеграций и предоставлении полезной платформы для предоставления возможностей языка различным средствам.

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

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

LSP развивался со временем, и сегодня он находится в версии 3.0. Она началась, когда концепция языкового сервера была выбрана OmniSharp для предоставления расширенных возможностей редактирования для C#. Изначально OmniSharp использовал протокол HTTP с полезными данными JSON и был интегрирован в несколько редакторов, включая Visual Studio Code.

Примерно в то же время корпорация Майкрософт начала работать на сервере языка TypeScript с идеей поддержки TypeScript в редакторах, таких как Emacs и Sublime Text. В этой реализации редактор взаимодействует через stdin/stdout с серверным процессом TypeScript и использует полезные данные JSON, вдохновленные протоколом отладчика V8 для запросов и ответов. Сервер TypeScript был интегрирован в подключаемый модуль Sublime TypeScript и VS Code для расширенного редактирования TypeScript.

После интеграции двух разных языковых серверов команда VS Code начала изучать протокол сервера общего языка для редакторов и УД. Общий протокол позволяет поставщику языка создать отдельный языковой сервер, который может использоваться различными средами УД. Потребитель языкового сервера должен реализовать клиентскую сторону протокола только один раз. Это приводит к беспроигрышной ситуации как для поставщика языка, так и для потребителя языка.

Протокол сервера языка начался с протокола, используемого сервером TypeScript, расширяя его с дополнительными функциями языка, вдохновленными API языка VS Code. Протокол поддерживается с помощью JSON-RPC для удаленного вызова из-за простоты и существующих библиотек.

Команда VS Code прототипом протокола путем реализации нескольких серверов языка linter, которые реагируют на запросы на lint (сканирование) файла и возвращают набор обнаруженных предупреждений и ошибок. Цель заключалась в том, чтобы изменить файл по мере редактирования пользователем в документе, что означает, что во время сеанса редактора будет много запросов на выстраивание. Имеет смысл поддерживать работу сервера, чтобы не нужно было запускать новый процесс подстроки для каждого пользователя. Реализовано несколько серверов linter, включая расширения ESLint и TSLint VS Code. Эти два сервера linter реализованы в TypeScript или JavaScript и выполняются в Node.js. Они совместно используют библиотеку, которая реализует клиент и серверную часть протокола.

Принцип работы LSP

Сервер языка работает в собственном процессе, а такие средства, как Visual Studio или VS Code, взаимодействуют с сервером с помощью протокола языка через JSON-RPC. Еще одним преимуществом сервера языка, работающего в выделенном процессе, является предотвращение проблем с производительностью, связанных с одной моделью процессов. Фактическим каналом транспорта могут быть stdio, сокеты, именованные каналы или ipc узла, если клиент и сервер записываются в Node.js.

Ниже приведен пример того, как средство и сервер языка взаимодействуют во время обычного сеанса редактирования:

lsp flow diagram

  • Пользователь открывает файл (который называется документом) в средстве: средство уведомляет сервер языка о том, что документ открыт (textDocument/didOpen). Теперь правда о содержимом документа больше не находится в файловой системе, но хранится средством в памяти.

  • Пользователь вносит изменения: средство уведомляет сервер об изменении документа (textDocument/didChange), а семантическая информация программы обновляется сервером языка. В этом случае сервер языка анализирует эти сведения и уведомляет средство об обнаруженных ошибках и предупреждениях (textDocument/publishDiagnostics).

  • Пользователь выполняет "Перейти к определению" для символа в редакторе: средство отправляет запрос textDocument/definition с двумя параметрами: (1) URI документа и (2) текстовое положение, из которого был инициирован запрос "Перейти к определению". Сервер отвечает с помощью универсального кода ресурса (URI) документа и положения определения символа в документе.

  • Пользователь закрывает документ (файл): уведомление textDocument/didClose отправляется из средства, уведомляя языковой сервер о том, что документ больше не находится в памяти и что текущее содержимое в файловой системе обновлено.

В этом примере показано, как протокол взаимодействует с языковым сервером на уровне функций редактора, таких как "Перейти к определению", "Найти все ссылки". Типы данных, используемые протоколом, — это редактор или интегрированная среда разработки данных, например открытый текстовый документ и положение курсора. Типы данных не находятся на уровне модели предметной области языка программирования, которая обычно предоставляет абстрактные деревья синтаксиса и символы компилятора (например, разрешенные типы, пространства имен, ...). Это значительно упрощает протокол.

Теперь рассмотрим запрос textDocument/definition более подробно. Ниже приведены полезные данные, которые выполняются между клиентским средством и языковым сервером для запроса "Перейти к определению" в документе C++.

Это запрос:

{
    "jsonrpc": "2.0",
    "id" : 1,
    "method": "textDocument/definition",
    "params": {
        "textDocument": {
            "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/use.cpp"
        },
        "position": {
            "line": 3,
            "character": 12
        }
    }
}

Это ответ:

{
    "jsonrpc": "2.0",
    "id": "1",
    "result": {
        "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/provide.cpp",
        "range": {
            "start": {
                "line": 0,
                "character": 4
            },
            "end": {
                "line": 0,
                "character": 11
            }
        }
    }
}

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

Когда пользователь работает с различными языками, VS Code обычно запускает языковой сервер для каждого языка программирования. В приведенном ниже примере показан сеанс, в котором пользователь работает с файлами Java и SASS.

java and sass

Возможности

Не каждый языковой сервер может поддерживать все функции, определенные протоколом. Таким образом, клиент и сервер объявляют о своей поддерживаемой функции с помощью "возможностей". Например, сервер объявляет, что может обрабатывать запрос textDocument/definition, но может не обрабатывать запрос "workspace/symbol". Аналогичным образом клиенты могут сообщить, что они могут предоставлять уведомления "о сохранении" перед сохранением документа, чтобы сервер мог вычислить текстовые изменения для автоматического форматирования измененного документа.

Интеграция языкового сервера

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

Чтобы упростить реализацию языковых серверов и клиентов, существуют библиотеки или пакеты SDK для клиентских и серверных частей. Эти библиотеки предоставляются для разных языков. Например, существует модуль npm клиента языка , который упрощает интеграцию языкового сервера с расширением VS Code и другим модулем npm сервера языка для записи языкового сервера с помощью Node.js. Это текущий список библиотек поддержки.

Использование протокола сервера языка в Visual Studio