Аннотация
LSP отделяет языковой анализ от редактора, но не делает их взаимодействие бессостояниевым. Совместимость держится на четырёх договорённостях: какие возможности действительно поддерживаются, в каких единицах считаются позиции, какой текст сейчас принадлежит клиенту и к какому состоянию относится результат асинхронного вычисления. Совпадения JSON-схем и надписи «LSP supported» для этого недостаточно.12
Данные на 2026-11-03. Нормативная база обзора — LSP 3.18, обозначенная официальным сайтом как current; 3.19 — upcoming, а не действующая замена. Для иллюстраций реализации взяты явно закреплённые, не обязательно последние версии: Node protocol package 3.18.5, Neovim v0.11.5, rust-analyzer 2025-10-27 и clangd 21.1.0. Допущение автономного обзора: читатель интегрирует существующий language server либо пишет клиент для своего редактора, а не проектирует новый язык. Это анализ спецификации, документации и исходников, без запуска редакторов и end-to-end испытаний совместимости.34
Главный практический вывод: начинать интеграцию стоит не с completion, а с document state. Ошибка UTF-16-смещения или последовательности contentChanges повреждает серверную копию текста; неверная политика устаревших ответов превращает корректный результат вчерашнего состояния в ошибку сегодняшнего интерфейса. Новые возможности 3.18 расширяют контракт, но не снимают эту ответственность с клиента и сервера.1
Введение
Актуальность. Даже текущая версия протокола не означает одновременную поддержку всех функций. Документация IntelliJ Platform, обновлённая 24 июля 2026 года, относит pull diagnostics к 2025.1.2, inlay hints и folding — к 2025.2.2, rename — к 2026.1.1. Это пример поэтапной интеграции конкретного клиента, а не таблица зрелости всей экосистемы. Проверять нужно конкретную пару и конкретную функцию.5
Цель — проследить успешную сессию от запуска до завершения и найти инварианты, которые нельзя нарушать при параллельной работе. Разбор отвечает на шесть вопросов: как согласуются возможности; где проходят границы сообщений; как переводятся позиции; как обновляется текст; как применять результаты и edits; что действительно добавила 3.18. За рамками остаются внутренние парсеры языков, настройка каждого редактора, performance-рейтинг серверов и удалённая файловая система.
От ближайших обзоров о MCP, CalDAV/CardDAV и local-first синхронизации этот текст отличается единицей согласования: здесь рассматривается одна интерактивная сессия редактора и language server, а не доставка инструментального вызова, серверная коллекция или слияние независимых реплик. Для самого LSP принципиален обмен состоянием редактируемого документа, описанный в его спецификации.1
1. Номер спецификации — не результат handshake
Версия LSP группирует возможности и отмечает их первое появление. При инициализации стороны не выбирают единый режим «включить 3.18 целиком»: клиент передаёт InitializeParams.capabilities, сервер возвращает InitializeResult.capabilities. Совместимость новых функций строится на capability flags. Из номера установленного SDK нельзя вывести, что пользовательский интерфейс умеет показать каждый предусмотренный результат.1
Features in the spec are kept compatible using so called capability flags which are exchanged between the client and the server during initialization.1
Это двусторонний договор. Сервер сообщает, что умеет вычислять, клиент — что умеет принимать и применять. Например, поддержку snippets в TextDocumentEdit ограничивает workspace.workspaceEdit.snippetEditSupport: серверу не следует отдавать их клиенту, который не объявил эту возможность. Проверка только server provider недостаточна: результат может иметь форму, которую клиент не умеет обработать.1
Отсутствующая capability обычно означает отсутствие соответствующей возможности; неизвестные свойства другой стороны следует игнорировать. Но превращать это в правило «любое отсутствующее поле отключает базовую функцию» нельзя. Client capabilities появились в 3.0 и не отменяют обязательных возможностей старого протокола; спецификация отдельно предупреждает об этом для document synchronization. В реализации полезнее разбирать конкретное поле и его default, чем иметь один флаг supportsLsp3_18.1
Статическая и динамическая регистрация
Статическая регистрация входит в результат initialize. Динамическая — отдельный request сервера клиенту client/registerCapability, разрешённый только при поддержке соответствующей feature-specific dynamicRegistration. Клиент, поддерживающий динамику для formatting, этим не обещает динамику для diagnostic provider. Одновременная статическая и динамическая регистрация одной возможности для того же document selector запрещена.1
У регистрации есть собственный string id, method и опциональные registerOptions. Этот id не равен внешнему JSON-RPC request ID: первый обозначает долгоживущую запись registry, второй — один обмен запросом и ответом. В unregister payload исторически сохранено написание unregisterations. Исправить его на более грамотное unregistrations в собственной модели — значит нарушить wire-контракт, а не улучшить спецификацию.1
Следствие для архитектуры клиента: принимать обратные requests придётся не только ради диалоговых окон. Нужен registry с жизненным циклом и document selectors. Руководство VS Code иллюстрирует разделение onInitialize, где читаются capabilities, и onInitialized, где возможна последующая регистрация; пример SDK полезен как образец структуры, но нормативные условия каждой возможности определяет спецификация.61
2. Сессия: два барьера на входе и два шага на выходе
Нормальная успешная последовательность выглядит так: initialize request → ответ с InitializeResult → initialized notification → обычная работа. initialize должен быть первым клиентским request; до ответа клиент не отправляет дополнительные requests и notifications. initialized посылается после результата, но перед следующим обычным клиентским сообщением. Это notification без собственного подтверждения, не второй handshake-request.17
Запрос до инициализации серверу следует завершать ServerNotInitialized (-32002), ранние notifications — отбрасывать, кроме exit. При этом во время обработки самого initialize серверу разрешены некоторые сообщения, включая обратный window/showMessageRequest. Поэтому схема «перестать читать входящие, пока не получен ответ initialize» потенциально создаёт взаимное ожидание. Это вывод из разрешённого обмена, не результат испытания конкретного клиента.1
Общее правило говорит, что initialize посылается один раз. Отдельно описан error/retry path через InitializeError.retry и выбор пользователя. Эти положения не стоит ни смешивать в разрешение повторной инициализации работающей сессии, ни пересказывать как абсолютный запрет предусмотренного повтора после ошибки. Lifecycle успешной сессии и восстановление после неуспешного входа — разные случаи.1
Выход тоже двухступенчатый. shutdown — request с успешным result: null, который не должен завершать процесс до возможности отдать ответ; после его отправки клиенту нельзя слать ничего, кроме exit. Дождаться ответа перед exit рекомендуется. Сам exit — notification, ответа на него нет. Спецификация рекомендует exit code 0, если ранее получен shutdown request, иначе 1; условие именно получение, не доказанное успешное завершение shutdown.1
3. Framing: JSON-RPC не сообщает длину сообщения
JSON-RPC определяет request, response и notification, а также корреляцию по id; сам он не привязан к конкретному транспорту. LSP добавляет framing: ASCII-заголовки, пустую строку \r\n\r\n и JSON content. Обязательный Content-Length считает байты content, не число символов, не UTF-16 units и не длину всего frame. JSON content передаётся в UTF-8.71
На stdio один системный read может дать часть frame или несколько frames сразу. Поэтому практическая реализация reader должна накапливать байты, отделять headers, ждать указанную длину body и оставлять хвост для следующего сообщения. Это инженерное следствие framing, а не дополнительная RPC-команда. Ограничение допустимого размера и глубины JSON тоже приходится задавать осознанно: RFC 8259 разрешает parser limits, но не выбирает за приложение безопасный бюджет памяти.18
Отдельный риск — отладочный вывод в stdout при stdio-транспорте: он попадает в тот же поток, где ожидаются headers. История официальной Node-реализации содержит исправление перенаправления console.info, чтобы не повреждать stdio. Это свидетельство конкретного дефекта, не оценка частоты проблемы. Для своей интеграции разумно выделить протоколу чистый поток и проверять поведение запуска, ошибок и завершения, а не только успешный запрос.9
4. Три значения слова character
Транспортная кодировка и кодировка позиций — независимые слои. general.positionEncodings сообщает предпочтения клиента; сервер выбирает capabilities.positionEncoding. Согласование появилось ещё в 3.17, не в 3.18. UTF-16 остаётся обязательной совместимой основой; если сервер не вернул кодировку, default — utf-16. Даже отсутствие utf-16 в предложенном клиентом списке не отменяет этой совместимости.110
Position — zero-based line и character. Последнее означает UTF-8 code units, то есть bytes, либо UTF-16 code units, либо UTF-32 code units, совпадающие с Unicode code points. Это не экранная колонка и не индекс видимого grapheme cluster. Для строки a𐐀b позиция перед b имеет offset 5 в UTF-8, 3 в UTF-16 и 2 в UTF-32; пример рассчитан по закодированному префиксу, а не измеряет производительность.1
Практическое следствие: тесты только на ASCII не проверяют преобразование позиций. Предлагаемый минимальный набор интеграции должен включать non-BMP символ, комбинируемый знак, LF/CRLF, пустую строку и файл без финального newline. Спецификация перечисляет допустимые line endings и запрещает адресовать позицию внутри CRLF. Это предлагаемые тест-кейсы на границы нормативного контракта, а не заявление, что здесь прогонялась conformance suite.1
Neovim v0.11.5 показывает, где платится цена преобразования: API применения workspace edit принимает position_encoding, а diagnostic handler переводит protocol offsets в byte columns через vim.str_byteindex. Конверсия нужна не только при отправке курсора на сервер: она нужна обратно для diagnostics и text edits. Исправить encoding в одном request-builder недостаточно.1112
5. Открытый документ принадлежит клиенту
textDocument/didOpen означает передачу управления содержимым клиенту. Пока документ открыт в этом смысле, сервер не должен перечитывать URI как источник актуального текста. «Открыт» не равно «виден во вкладке»: клиент может управлять невидимым буфером. Для одного документа open/close должны балансироваться, повторный open без close запрещён. Это объясняет, почему чтение файла с диска даёт корректный сохранённый текст и одновременно неправильный ответ IDE.1
В full sync клиент отправляет полное содержимое; в incremental sync — полный текст при open и изменения после него. До state-dependent request клиент обязан обеспечить синхронизацию состояния. Однако didChange — notification: отдельного ответа «version принята» протокол здесь не вводит. Важно сохранить причинный порядок доставки и обработки изменений относительно зависимого запроса, а не выдумать обязательный ack.1
didClose возвращает master туда, куда указывает URI, например на диск для file URI. Это не требование уничтожить весь индекс проекта: спецификация разрешает серверу отвечать и по закрытым документам. Поэтому очистка кэша, закрытие вкладки и прекращение владения несохранённым текстом — три операции, которые нельзя автоматически считать одной.1
Версия — не hash и не обязательно previous + 1
The number doesn’t need to be consecutive.1
Версия обозначает состояние документа и увеличивается после изменения, включая undo/redo. Допустимый пропуск числа не доказывает потери notification: требование next == previous + 1 сильнее спецификации. Возврат к тому же тексту через undo не обязан возвращать старую version, поскольку version — не content hash. Для didChange переданный номер относится к состоянию после всех элементов contentChanges.1
У OptionalVersionedTextDocumentIdentifier поле version имеет тип integer | null; это не всеобщее разрешение опустить поле. В описанном серверном сообщении для документа, не открытого в editor, null допускает master на диске. В clangd 21.1.0 дополнительно существует собственное расширение с необязательной version и её синтезом. Принимать такое поведение за нормативное правило всех серверов нельзя.113
6. Два массива edits с противоположной семантикой
Самая опасная похожесть в LSP — contentChanges и TextEdit[]. В didChange.contentChanges изменения последовательны: первый переводит S в S′, второй вычислен уже на S′. Применять нужно notifications в порядке получения и элементы внутри notification в порядке массива. Если переставить элементы или интерпретировать их относительно исходного текста, сервер получит другую копию документа.1
В TextEdit[], напротив, ranges относятся к одному исходному snapshot. Массив описывает один переход состояния без промежуточных координат. Диапазоны не должны перекрываться; предусмотрены уточнения для нескольких вставок в одну позицию, где порядок массива определяет порядок вставленных строк. Последовательное применение с пересчётом второго range по уже изменённому тексту нарушает этот контракт.1
Рассчитанная иллюстрация: исходный текст abc, вставка X в позицию 1 и замена диапазона [2,3) на Y. Как последовательные incremental changes это даёт aXYc: второй range адресует b уже после вставки. Как два TextEdit относительно исходного abc это даёт aXbY: заменяется исходная c. Одинаковые JSON-подобные ranges не означают одинаковую семантику операции.1
Для WorkspaceEdit.documentChanges с resource operations есть ещё один порядок: create/rename/delete и изменения файлов исполняются как переданы. Создать файл, затем вставить текст — не то же самое, что сначала вставить, затем создать. Значит, универсальная функция «отсортировать все edits в обратном порядке» не подходит одновременно для incremental sync, текстового edit-пакета и workspace operations.1
7. Refactoring не равен атомарной транзакции
WorkspaceEdit.changes содержит отображение URI → TextEdit[], без встроенной per-document version. documentChanges позволяет передать versioned TextDocumentEdit, если клиент заявил поддержку. Это даёт возможность проверить snapshot перед применением; но наличие versioned edits само по себе не превращает весь refactoring в транзакцию.1
Стратегия ошибки объявляется в workspace.workspaceEdit.failureHandling. При abort уже выполненные операции сохраняются; transactional обещает all-or-nothing; textOnlyTransactional обеспечивает транзакционность только для текстовых изменений без resource operations; undo означает попытку отката без гарантии успеха. Поэтому «rename либо прошёл весь, либо ничего не изменил» допустимо обещать только с учётом конкретного capability-контракта.1
В 3.18 snippets и metadata улучшают выразительность изменений, не их атомарность. ApplyWorkspaceEditParams.metadata может нести isRefactoring; это дополнительная информация редактору, а не commit-marker. workspace/applyEdit возвращает applied, может содержать failureReason и, в зависимости от стратегии, failedChange. Интеграция должна учитывать ответ, а не считать отправленный request выполненным refactoring.1
8. Отмена не отменяет обязанности ответить
$/cancelRequest — notification с ID исходного request. На сам notification response не посылается, но отменённый исходный request всё равно должен завершиться ответом. Результат может успеть прийти после cancel; cancellation не обещает немедленно остановленный CPU. При error response на отмену спецификация рекомендует RequestCancelled, но не требует превращать любой завершившийся request именно в эту ошибку.17
Три кода различаются по причине: RequestCancelled (-32800) — клиентская отмена замечена сервером; ContentModified (-32801) — содержимое изменилось вне нормальных условий; ServerCancelled (-32802) — серверная отмена для явно поддерживающих её методов. Спецификация отдельно не рекомендует выдавать ContentModified лишь потому, что в очереди виден ещё не обработанный content change: старый результат может оставаться полезным клиенту.1
Ответы могут возвращаться не в порядке requests, если это не нарушает корректность. Поэтому pending calls сопоставляются по id, не по FIFO-позиции. Но это не разрешение переставлять документные изменения. Предлагаемая модель реализации — упорядоченное обновление состояния плюс вычисления на выбранных snapshots; политику принятия результата клиент выбирает с учётом того, что пользователь успел изменить.71
Архитектурный документ rust-analyzer, закреплённый тегом 2025-10-27, иллюстрирует внутреннюю invalidation: Salsa использует revision counter, вычисление замечает новую revision, а отмена преобразуется на границе IDE в Result<T, Cancelled>. Это стратегия остановки устаревшей работы внутри сервера. Из неё одной нельзя вывести конкретный LSP error response или доказать нарушение wire-контракта.14
9. Почему диагностики могут быть корректными и устаревшими
В push-модели textDocument/publishDiagnostics новый массив заменяет прежний, а пустой очищает его; клиент не должен сливать эти наборы. Поле document version опционально, а textDocument.publishDiagnostics.versionSupport сообщает, интерпретирует ли его клиент. Механизм позволяет связать результат с текстом, но не устанавливает универсальную политику отбрасывания любой старой диагностики всеми редакторами.1
Pull diagnostics, появившиеся в 3.17, дают клиенту управление тем, для каких документов и когда запрашивать расчёт. textDocument/diagnostic относится к currently synced version, но его параметры содержат обычный TextDocumentIdentifier, а не явную version. Если пользователь снова напечатал текст во время вычисления, одна pull-модель автоматически race не устраняет. Клиенту по-прежнему нужна связь request с состоянием и решение о пригодности ответа.110
resultId и previousResultId связывают последовательные diagnostic reports; это не document version. Ответ unchanged означает актуальность предыдущего набора diagnostics, не тождество текста. Для long-running workspace pull при поддержке streaming клиентом спецификация допускает несколько reports по одному URI; при пересечении с document pull рекомендует предпочитать более высокую document version, а document pull — workspace pull. Это описанная стратегия согласования разных потоков, не единый глобальный freshness counter.1
clangd в закреплённой документации объясняет, что не пересчитывает diagnostics на каждую version: его расширение wantDiagnostics позволяет принудить или подавить расчёт определённой версии, а без него возможен результат для этой или последующей. Поэтому «после каждого didChange не пришёл publish» — недостаточное доказательство нарушения. Нужны объявленный контракт, период наблюдения и проверка того, догоняет ли результат остановившийся ввод.1513
Neovim v0.11.5 даёт ещё один узкий пример: document-pull handler повторяет запрос после ServerCancelled, если retriggerRequest явно не равен false; unchanged не перезаписывает diagnostics. Это прочитанный implementation path, не доказательство общей полноты stale filtering редактора. Отличать локально проверенный handler от всего pipeline важно не меньше, чем отличать документную version от result ID.12
10. Что действительно новое в 3.18
3.18 расширяет словарь возможностей, но основные механизмы предыдущих разделов старше неё. В changelog 3.17 уже есть position encoding negotiation, pull diagnostics, inlay hints, inline values и notebook support. Называть их «главными новинками 3.18» значило бы спутать текущую доступность с первым появлением.10
Содержательные изменения 3.18 включают textDocument/inlineCompletion, динамическое получение read-only текста через workspace/textDocumentContent и его refresh, workspace/foldingRange/refresh, а также textDocument/rangesFormatting для нескольких диапазонов. Это разные capability-гейты. Поддержка старого range formatting не доказывает поддержку множественного, а folding refresh позволяет клиенту отложить пересчёт, например для невидимого editor.1
Inline completion нельзя путать с inlay hints или inline values. Новый request предназначен для более крупных фрагментов; спецификация допускает syntactically или semantically incorrect варианты. Она не требует LLM и не обещает корректность кода. Это договор о доставке и представлении предложения, не контракт качества генерации.1
Для изменений текста добавлены SnippetTextEdit и metadata применения; для completion — CompletionList.applyKind, определяющий Replace/Merge отдельных полей. При Merge commitCharacters объединяются, data получает shallow merge с приоритетом item. Сами completion list defaults, включая data, присутствовали уже в 3.17. Обновлять SDK и переписывать историю протокола — разные задачи.110
Официальная Node-реализация особенно хорошо показывает различие уровней: release protocol/3.18.5 опубликован 22 октября 2026 года, а закреплённый README сопоставляет protocol 3.18.5, JSON-RPC package 10.0.0 и client/server 11.0.0. Там же прямо сказано, что requests, notifications и responses в этом патче не менялись. Зато исправлялись document-open ordering и offsets при образовании CRLF на границе incremental edits: семантика state machine требует исправлений даже без нового сообщения протокола.169
11. Как проверять интеграцию, а не наличие обработчика
Предлагаемая проверка должна фиксировать наблюдаемый контракт: объявленные capabilities, выбранную position encoding, document URI/version, порядок изменений, RPC ID и решение по stale response. Затем стоит проверить ранние сообщения вокруг initialize, быстрый ввод во время pending request, undo/redo с возрастающей version, несколько changes в notification, versioned refactoring и graceful shutdown. Это проект тест-плана на основе правил выше, не выполненный в обзоре набор испытаний.1
Исследование LspFuzz: Hunting Bugs in Language Servers, arXiv:2510.00532v2, подкрепляет сам подход к входам: тестировать предлагается комбинации исходного кода и editor operations, а не только JSON отдельно. Прочитаны целевые разделы полного HTML — approach, evaluation и limitations. Но работа не является conformance suite для 3.18: авторы исследовали четыре C/C++ реализации серверов, а постановка ориентирована на crashes и memory corruption.17
LspFuzz focuses on bugs that lead to crashes or memory corruptions.17
Авторы отдельно оставляют multi-file и evolving-source сценарии за рамками текущей постановки и предлагают более сильные функциональные oracles. Следовательно, прохождение такого fuzzing не доказывало бы правильности rename, freshness diagnostics или межредакторной совместимости. Для собственной интеграции полезно разделять safety, семантическую правильность и соответствие протоколу как разные проверяемые свойства.17
Дискуссионные вопросы и противоречия
Переносимость против глубины IDE. Официальный overview обещает отделение language features от инструмента, но не определяет конкретную UI-интеграцию. JetBrains прямо отмечает, что native Custom Language Support охватывает больше IDE-возможностей, чем LSP. Противоречие снимается границей задачи: стандарт уменьшает повторение общей языковой логики, а не делает все редакторы одинаковыми.25
Расширение решает проблему и создаёт зависимость. Стандартная Command задаёт форму, но LSP не определяет общий каталог well-known commands. В abstract работы о SLSP авторы описывают, как ad-hoc расширения для specification languages возвращают необходимость новой реализации при переходе на другую IDE. Здесь прочитан только abstract и metadata, поэтому источник используется для этого сформулированного тезиса, не для оценки эффективности предложенного расширения.118
README против спецификации. Закреплённый README Node-реализации относит расширение itemDefaults.data к 3.18, хотя поле уже есть в 3.17. Старый текст clangd extensions также говорит об обязательных UTF-16 offsets в общем виде, но рядом объявляет legacy offsetEncoding deprecated в пользу positionEncoding, введённого в 3.17. Для современных нормативных утверждений приоритет у versioned specification, для конкретной реализации — у её закреплённого кода и документации; расхождение нельзя молча превращать в новое правило.161015
Схема против временного контракта. Генерация типов и наличие handler проверяют структуру сообщения, но не гарантируют правильный snapshot. Исправления ordering и CRLF в официальной реализации, а также ограниченность crash-oracle LspFuzz показывают разные классы проблем. Наш вывод: schema validation полезна, однако не заменяет проверки последовательностей состояний. Это аналитический вывод, не измеренная вероятность дефекта.1617
Недостаточность данных
В обзоре нет экспериментальной матрицы «редактор × сервер × новая capability 3.18». Поиск включал interoperability, compatibility, limitations, testing и fuzzing, но прочитанные источники дают нормативные правила, отдельные implementation paths и ограниченные исследования. Поэтому здесь не объявляется победитель по совместимости и не делается вывод об отсутствии какой-либо функции только потому, что её нет в перечне одной страницы.
Не измерялись latency, расход памяти, частота рассинхронизации и время догоняющего расчёта diagnostics. Исторические закреплённые baselines нужны для воспроизводимости чтения, не для заявления о последних релизах продуктов. Страницы спецификации и документация JetBrains изменяемы; срез означает чтение на указанную дату, а не обещание неизменности URL. Одна академическая работа изучена лишь по abstract; предел явно отмечен в соответствующем выводе.
Заключение
LSP полезно понимать как stateful-контракт поверх асинхронного RPC. Capabilities определяют допустимую форму и поведение функций; position encoding — смысл координат; open/change/close — источник текущего текста; version — связь со snapshot. Ни номер пакета, ни успешный completion на ASCII не проверяют весь этот контракт.116
Практический порядок интеграции следует из этих зависимостей: сначала framing и lifecycle, затем точное зеркало документа и преобразование координат, после — policy для устаревших результатов и edits, и только затем новые features. Особенно важно не смешивать последовательные contentChanges с snapshot-based TextEdit[], не считать отмену исчезновением request и не обещать атомарный refactoring без соответствующего failureHandling.1
3.18 даёт полезные новые формы взаимодействия, но не универсальный переключатель совместимости. Защищаемый результат этого обзора — перечень инвариантов и границ доказательств, а не сертификат пары editor/server. Следующий шаг для конкретной интеграции — выполнить предложенные sequence tests на её версиях и сохранить реальные traces; до этого её соответствие остаётся непроверенным.
Quality Metrics
| Metric | Value |
|---|---|
| Sources found | 60 |
| Sources read | 18 |
| Sources cited | 18 |
| Academic sources | 2 |
| Official/documentation | 16 |
| Industry reports | 0 |
| News/journalism | 0 |
| Blog/forum | 0 |
| Source count unit | Уникальные работы/документы; известные зеркала объединены; metamodel и секции 3.18 включены в одну спецификацию |
| Access scope | Релевантные разделы; SLSP — только abstract и metadata |
| Citation coverage | 94% |
| Counter-arguments searched | Yes |
| Research rounds | 2 |
| Runtime conformance testing | Not performed |
| Independent benchmark reproduction | Not performed |
Microsoft. “Language Server Protocol Specification - 3.18.” дата обращения 2026-11-03. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Microsoft. “What is the Language Server Protocol?.” дата обращения 2026-11-03. https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/ ↩︎ ↩︎
Microsoft. “Official page for Language Server Protocol.” дата обращения 2026-11-03. https://microsoft.github.io/language-server-protocol/ ↩︎
Microsoft. “Language Server Protocol Specification - 3.19.” дата обращения 2026-11-03. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.19/specification/ ↩︎
JetBrains. “Language Server Protocol (LSP) | IntelliJ Platform Plugin SDK.” дата обращения 2026-11-03. https://plugins.jetbrains.com/docs/intellij/language-server-protocol.html ↩︎ ↩︎
Microsoft. “Language Server Extension Guide.” дата обращения 2026-11-03. https://code.visualstudio.com/api/language-extensions/language-server-extension-guide ↩︎
JSON-RPC Working Group. “JSON-RPC 2.0 Specification.” дата обращения 2026-11-03. https://www.jsonrpc.org/specification ↩︎ ↩︎ ↩︎ ↩︎
Bray T. (ed.). “The JavaScript Object Notation (JSON) Data Interchange Format.” RFC 8259, December 2017. https://www.rfc-editor.org/rfc/rfc8259.html ↩︎
Microsoft. “Releases · microsoft/vscode-languageserver-node.” release/protocol/3.18.5, 2026-10-22. https://github.com/microsoft/vscode-languageserver-node/releases ↩︎ ↩︎
Microsoft. “Language Server Protocol Specification - 3.17.” дата обращения 2026-11-03. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Neovim project. “neovim/runtime/doc/lsp.txt at v0.11.5 · neovim/neovim · GitHub.” v0.11.5. https://github.com/neovim/neovim/blob/v0.11.5/runtime/doc/lsp.txt ↩︎
Neovim project. “neovim/runtime/lua/vim/lsp/diagnostic.lua at v0.11.5 · neovim/neovim · GitHub.” v0.11.5. https://github.com/neovim/neovim/blob/v0.11.5/runtime/lua/vim/lsp/diagnostic.lua ↩︎ ↩︎
LLVM project. “llvm-project/clang-tools-extra/clangd/Protocol.h at llvmorg-21.1.0 · llvm/llvm-project · GitHub.” llvmorg-21.1.0. https://github.com/llvm/llvm-project/blob/llvmorg-21.1.0/clang-tools-extra/clangd/Protocol.h ↩︎ ↩︎
rust-analyzer project. “rust-analyzer/docs/book/src/contributing/architecture.md at 2025-10-27 · rust-lang/rust-analyzer · GitHub.” 2025-10-27. https://github.com/rust-lang/rust-analyzer/blob/2025-10-27/docs/book/src/contributing/architecture.md ↩︎
LLVM project. “clangd-www/extensions.md at 1473757f9ce34a505f90773023af0a511dd8cb9b · llvm/clangd-www · GitHub.” commit 1473757f9ce34a505f90773023af0a511dd8cb9b. https://github.com/llvm/clangd-www/blob/1473757f9ce34a505f90773023af0a511dd8cb9b/extensions.md ↩︎ ↩︎
Microsoft. “VSCode Language Server - Node.” release/protocol/3.18.5. https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/release/protocol/3.18.5/README.md ↩︎ ↩︎ ↩︎ ↩︎
Zhu H. et al. “LspFuzz: Hunting Bugs in Language Servers.” arXiv:2510.00532v2, 2025-10-02. https://arxiv.org/html/2510.00532v2 ↩︎ ↩︎ ↩︎ ↩︎
Rask J. K. et al. “The Specification Language Server Protocol: A Proposal for Standardised LSP Extensions.” arXiv:2108.02961v1, 2021-08-06; abstract only. https://arxiv.org/abs/2108.02961v1 ↩︎