ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

理解 LSP 3.19 的 textDocument/didOpen:文档打开通知协议详解与客户端/服务端实现要点

理解 LSP 3.19 的 textDocument/didOpen:文档打开通知协议详解与客户端/服务端实现要点 开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载导读textDocument/didOpen是 Language Server ProtocolLSP中客户端向语言服务器宣告新文本文档已被打开的核心通知它把文档内容的所有权从磁盘移交到客户端内存并触发服务器初始化该文档的同步状态。本文以 LSP 3.19 规范specification.md中的 didOpen.md 为骨架结合 didChange.md、didClose.md 等配套文档讲透 didOpen 的协议语义、DidOpenTextDocumentParams与TextDocumentItem的字段细节、语言标识符规范、同步类型Full/Incremental以及客户端与服务端的实现约束。读完本文你将能正确实现或调试 didOpen 的收发逻辑并理解它与 didChange/didClose 之间的配对与版本管理规则。1. didOpen 的协议语义什么是打开一份文档1.1 核心定义LSP 规范对textDocument/didOpen的定义如下原文见 didOpen.md文档打开通知由客户端发送给服务器用于通知新打开的文本文档。文档内容从此由客户端管理服务器不得再尝试通过该文档的 URI 去读取其内容。这里有两个极易被误解的语义要点打开不等于在编辑器中显示协议层面打开仅意味着文档内容由客户端托管例如已加载进编辑器缓冲区即使文档并未在编辑器视图中呈现只要客户端声明对其内容负责就应发送 didOpen。内容来源以客户端为准服务器在收到 didOpen 后必须使用通知参数里携带的text字段内容而不是去磁盘或远端按 URI 重新读取。这一点在规范中被称为 The documents content is now managed by the client and the server must not try to read the documents content using the documents Uri。1.2 与关闭通知的配对约束规范同时强调了两条硬性规则开/关必须配对在没有先发送对应的textDocument/didClose之前不得对同一文档重复发送 didOpenAn open notification must not be sent more than once without a corresponding close notification send before。最大打开数为一任意特定textDocument在同一时刻的打开计数最多为 1open and close notification must be balanced and the max open count for a particular textDocument is one。换言之didOpen → (didChange)* → didClose构成一个严格的生命周期闭环客户端必须维护这一平衡关系否则会造成服务器端状态混乱重复打开可能触发状态重置或内存泄漏。1.3 服务器能力与文档开闭无关规范特别注明服务器的能力如诊断、补全、跳转定义与文档处于打开还是关闭状态无关a servers ability to fulfill requests is independent of whether a text document is open or closed。也就是说服务器不应因文档关闭而失忆不过实际语言服务通常会基于内部缓存策略决定是否保留关闭文档的模型这属于实现细节。2. 消息结构DidOpenTextDocumentParams 与 TextDocumentItem2.1 通知定义textDocument/didOpen是客户端 → 服务器方向的通知Notification没有响应method:textDocument/didOpenparams:DidOpenTextDocumentParams其类型定义如下源自 didOpen.mdinterface DidOpenTextDocumentParams { /** * The document that was opened. */ textDocument: TextDocumentItem; }整个参数仅含一个textDocument字段类型为TextDocumentItem。它是一个完整的文档快照与didChange携带的增量补丁、didClose携带的最小标识仅 URI形成鲜明对比。2.2 TextDocumentItem 的四要素TextDocumentItem的完整定义见 textDocumentItem.mdinterface TextDocumentItem { /** * The text documents URI. */ uri: DocumentUri; /** * The text documents language identifier. */ languageId: string; /** * The version number of this document (it will increase after each * change, including undo/redo). */ version: integer; /** * The content of the opened text document. */ text: string; }字段类型必填说明uriDocumentUri是文档唯一标识协议层以字符串形式传递 URI参见 textDocumentIdentifier.mdlanguageIdstring是与文档关联的语言标识符见第 3 节versioninteger是文档版本号每次变更含撤销/重做后递增textstring是打开时文档的完整内容uri即DocumentUri在协议层就是字符串 URI。此后服务器将用该 URI 作为该文档在所有请求与通知如textDocument/completion、textDocument/didChange中的统一主键。规范中 URI 的类型定义可参见 types/uri.md 对应的文档说明仓库中 3.19 的通用 URI 定义见 _includes/types/uri.md。version整数版本号。它并非只对 didOpen 有意义——它是整个同步机制的基石服务器依赖版本号判断didChange到达顺序是否正确、内容是否过期。规范明确it will increase after each change, including undo/redo即撤销/重做也属于一次变更版本必须递增。text打开瞬间的完整内容。服务器应将其作为文档同步状态的初始值后续内容更新一律来自didChange。3. languageId文档的语言标识符约定languageId用于在服务器同时处理多种语言时避免重新解析文件扩展名to identify a document on the server side when it handles more than one language to avoid re-interpreting the file extension。规范在 textDocumentItem.md 中给出了推荐标识符表客户端应优先使用这些 ID语言标识符语言标识符ABAPabapLualuaWindows BatbatMakefilemakefileBibTeXbibtexMarkdownmarkdownClojureclojureObjective-Cobjective-cCoffeeScriptcoffeescriptObjective-Cobjective-cppCcPascalpascalsince 3.18.0CcppPerlperlC#csharpPerl 6perl6CSScssPHPphpDdsince 3.18.0PlaintextplaintextDelphipascalsince 3.18.0PowerShellpowershellDiffdiffPugjadeDartdartPythonpythonDockerfiledockerfileRrElixirelixirRazor (cshtml)razorErlangerlangRubyrubyF#fsharpRustrustGitgit-commit、git-rebaseSCSSscss花括号语法、sass缩进语法GogoScalascalaGroovygroovyShaderLabshaderlabHandlebarshandlebarsShell Script (Bash)shellscriptHaskellhaskellSQLsqlHTMLhtmlSwiftswiftIniiniTypeScripttypescriptJavajavaTypeScript ReacttypescriptreactJavaScriptjavascriptTeXtexJavaScript ReactjavascriptreactText (plain)plaintextJSONjsonVisual BasicvbLaTeXlatexXMLxmlLesslessXSLxslYAMLyaml使用要点当文档的语言发生变化时例如从 TypeScript 切换为 JavaScript客户端需要按规范先发送textDocument/didClose再以新的languageId发送textDocument/didOpen前提是服务器同样支持新语言 ID——见 didOpen.md 的说明。pascal同时映射 Delphi 与 Pascalsince 3.18.0D语言标识符也是 3.18.0 起新增。4. 完整的 JSON-RPC 示例结合上述定义一次典型的 didOpen 通知在 wire 上形如基于 LSP 3.19 规范与 JSON-RPC 2.0 消息格式{ jsonrpc: 2.0, method: textDocument/didOpen, params: { textDocument: { uri: file:///home/user/src/main.py, languageId: python, version: 1, text: import sys\n\nprint(sys.version)\n } } }注意通知Notification不携带id字段服务器无需也不应回复参考 specification.md 中关于请求/通知对象的基础协议描述。version从 1 开始是常见实践首次打开即为 v1但协议本身未强制起始值仅要求每次内容变更后递增。消息通过 LSP 基础协议传输头部携带Content-Length字节数与可选的Content-Type随后是\r\n\r\n分隔的 UTF-8 编码内容体。5. 与同步机制的关系openClose、TextDocumentSyncKind 与能力声明5.1 文档同步的三个通知必须成组规范在 specification.md 的 Text Document Synchronization 一节明确Client support fortextDocument/didOpen,textDocument/didChangeandtextDocument/didClosenotifications is mandatory in the protocol and clients can not opt out supporting them.即协议强制客户端支持 didOpen、didChange、didClose 三者不可选择性退出且服务器必须要么全部实现这三个通知要么一个都不实现。只有在客户端展示的文档都是只读的情况下才允许关闭文档同步——否则服务器可能收到针对内容由客户端托管的文档请求却拿不到内容。5.2 客户端与服务端能力声明客户端能力specification.mdexport interface TextDocumentSyncClientCapabilities { /** * Whether text document synchronization supports dynamic registration. */ dynamicRegistration?: boolean; /** * The client supports sending will save notifications. */ willSave?: boolean; /** * The client supports sending a will save request and * waits for a response providing text edits which will * be applied to the document before it is saved. */ willSaveWaitUntil?: boolean; /** * The client supports did save notifications. */ didSave?: boolean; }服务器能力textDocumentSync属性其值可为TextDocumentSyncKind或TextDocumentSyncOptions。其中TextDocumentSyncOptions的完整定义如下specification.mdexport interface TextDocumentSyncOptions { /** * Open and close notifications are sent to the server. If omitted open * close notification should not be sent. */ openClose?: boolean; /** * Change notifications are sent to the server. See * TextDocumentSyncKind.None, TextDocumentSyncKind.Full and * TextDocumentSyncKind.Incremental. If omitted it defaults to * TextDocumentSyncKind.None. */ change?: TextDocumentSyncKind; /** * If present will save notifications are sent to the server. If omitted * the notification should not be sent. */ willSave?: boolean; /** * If present will save wait until requests are sent to the server. If * omitted the request should not be sent. */ willSaveWaitUntil?: boolean; /** * If present save notifications are sent to the server. If omitted the * notification should not be sent. */ save?: boolean | SaveOptions; }didOpen 直接受openClose开关控制若openClose为true或服务器以TextDocumentSyncKind简写形式声明同步客户端才会发送 didOpen/didClose若省略该字段客户端不应发送开/关通知。注意这里的默认语义与change不同——change省略时默认为TextDocumentSyncKind.None0而openClose省略时意味着不发送开/关通知。5.3 TextDocumentSyncKind 三种取值/** * Defines how the host (editor) should sync document changes to the language * server. */ export namespace TextDocumentSyncKind { /** * Documents should not be synced at all. */ export const None 0; /** * Documents are synced by always sending the full content * of the document. */ export const Full 1; /** * Documents are synced by sending the full content on open. * After that only incremental updates to the document are * sent. */ export const Incremental 2; } export type TextDocumentSyncKind 0 | 1 | 2;取值名称含义0None不同步文档内容1Full每次变更都发送文档完整内容2Incremental打开时发送完整内容即 didOpen 的text此后仅发送增量变更从同步生命周期看didOpen 是 Incremental 模式的起点——服务器以 didOpen 携带的完整text建立基线后续靠didChange的增量补丁range text持续演进文档状态。因此 didOpen 的正确实现直接决定了增量同步的基线质量若初始内容不完整或版本号不准确后续所有增量都会产生累积偏差。5.4 注册选项didOpen 的注册选项为TextDocumentRegistrationOptions见 didOpen.md该选项贯穿几乎所有基于文本文档的功能hover、completion 等用于声明该功能适用的文档选择器document selector。结合仓库中 documentFilter.md 等文档TextDocumentRegistrationOptions的形态大致如下export interface TextDocumentRegistrationOptions { /** * A document selector to identify the scope of the registration. */ documentSelector: DocumentSelector | null; }documentSelector为null表示不作为服务端静态注册目标常配合动态注册。当服务器通过client/registerCapability动态注册 didOpen 处理时会用该选项限定只接收匹配选择器的文档的 didOpen 通知。6. didOpen 在文档生命周期中的位置6.1 从打开到关闭的完整流程结合 LSP 3.19 的同步章节specification.md一份文档在客户端中的生命周期如下阶段客户端行为消息携带内容用户打开文档声明内容所有权textDocument/didOpen完整TextDocumentItemuri、languageId、version、text用户编辑文档同步内容变更textDocument/didChange0 次或多次VersionedTextDocumentIdentifier 增量/全量变更用户保存文档通知保存textDocument/didSave可选TextDocumentIdentifier 可选 text用户关闭文档释放内容所有权textDocument/didClose仅TextDocumentIdentifier仓库中的时序图 language-server-sequence.png 直观展示了这一流程客户端在用户打开文档时发送textDocument/didOpen随后在编辑时发送textDocument/didChange服务器回发textDocument/publishDiagnostics诊断用户执行跳转定义时走textDocument/definition请求/响应最后文档关闭时发送textDocument/didClose。其中 didOpen 正是整个同步链条的第一环。6.2 为什么必须先用 didOpen 再发 didChangedidChange.md 明确规定Before a client can change a text document it must claim ownership of its content using thetextDocument/didOpennotification.即客户端必须先通过 didOpen 声明对文档内容的所有权才能发送 didChange。这是内容所有权模型的核心约束didOpen 把权威内容从磁盘迁移到客户端didChange 才有合法的变更依据否则服务器无从得知变更前的基线内容也无法正确应用增量。6.3 版本号与同步时序示例didChange.md 给出了一个连续输入场景下的同步示例假设用户输入触发了textDocument/completion文档版本用户输入客户端行为请求5文档变更一将文档 v5 同步给服务器textDocument/didChange5-基于文档 v5 向服务器发起请求textDocument/completion6文档变更二将文档 v6 同步给服务器textDocument/didChange该表的要点在向服务器发起语言功能请求之前客户端必须确保文档状态已同步到服务器the client must ensure that the documents state is synchronized with the server to guarantee reliable results。didOpen 为这个版本递增链条提供了初始的 version 基线——首个版本通常在打开时确立如 v1此后每次 didChange 携带VersionedTextDocumentIdentifier递增版本号。开发语言功能如补全、签名帮助时应遵循先同步、后请求的时序保证服务器计算所用的文档状态与客户端一致。7. 实现要点与常见陷阱7.1 客户端实现清单配对维护为每个 URI 维护打开状态未关闭前禁止重复发送 didOpen关闭后禁止再次发送 didChange。快照完整性didOpen 必须携带打开瞬间的完整text不能发送空内容或截断内容Incremental 模式的基线依赖它。语言切换languageId 变化时先 didClose 再 didOpen且确认服务器支持新语言 ID。版本递增version 在每次变更含撤销/重做后 1didChange 中的版本号指向变更应用后的版本见 didChange.md 中VersionedTextDocumentIdentifier的注释。能力协商仅当服务器在initialize响应中声明textDocumentSync且 openClose 开启时才发送 didOpen若服务器声明TextDocumentSyncKind.None则不做任何同步。7.2 服务器实现清单不得按 URI 回读didOpen 到达后以参数中的text为权威内容建立文档模型禁止自行访问磁盘。三合一实现要么同时处理 didOpen/didChange/didClose要么全部不处理规范强制。版本校验记录每份文档的版本检测乱序/重复的 didChange如遇缺失的 didOpen先收到 didChange应视为协议违规并做防御处理。动态注册若支持dynamicRegistration可通过client/registerCapability注册 didOpen并用TextDocumentRegistrationOptions.documentSelector限定文档范围。7.3 常见陷阱重复打开不配对同一 URI 未关闭又发 didOpen违反max open count is one会导致服务器状态被意外重置。把 didOpen 当请求处理didOpen 是通知不应带id服务器不应回复响应消息。忽略 languageId 变更直接把新 languageId 悄悄改掉而不走 didClose/didOpen服务器端语言上下文不会更新。省略 versiondidOpen 的version不是可选字段缺失或恒为常量的版本号会让增量同步的乱序检测失效。8. 相关文档与进一步阅读本通知的权威定义didOpen.md变更与版本同步规则didChange.md关闭配对通知didClose.md保存通知可选环节didSave.md文档快照类型与语言标识符表textDocumentItem.md文档最小标识textDocumentIdentifier.md同步总览与能力定义TextDocumentSyncKind、TextDocumentSyncOptions、TextDocumentSyncClientCapabilitiesspecification.md 的 Text Document Synchronization 一节注册选项使用的文档过滤器documentFilter.md完整客户端/服务器时序language-server-sequence.png结语textDocument/didOpen看似简单一个通知、一个参数对象却是 LSP 文档同步模型的地基它确立内容所有权、建立增量同步基线、声明语言与版本信息并与 didChange/didClose 构成严格配对的完整生命周期。理解并正确实现它是构建可靠语言服务器尤其是依赖增量同步、需要精确诊断与补全的服务的前提。以本文梳理的协议语义、类型细节与实现清单为准结合上述规范原文逐项核对即可避免绝大多数同步类集成问题。赞分享开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载相关推荐LSP Type Definition RequesttextDocument/typeDefinition协议详解从客户端能力到服务端实现LSP Type Definition RequesttextDocument/typeDefinition协议详解从客户端能力到服务端实现 本文基于 l开发工具深入解析 LSP 3.18 的 textDocument/didOpen文档打开通知与文本同步机制深入解析 LSP 3.18 的 textDocument/didOpen文档打开通知与文本同步机制 导读 textDocument/didOpen 是 Lan开发工具LSP 3.19 中的 Go to Implementation 请求textDocument/implementation协议详解与实现指南LSP 3.19 中的 Go to Implementation 请求textDocument/implementation协议详解与实现指南 导读 本文开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表