ARTICLE DETAIL

资讯详情

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

Language Server Protocol 3.18 拉取式诊断(Pull Diagnostics)协议详解:从推送模型到按需计算的完整实现指南

Language Server Protocol 3.18 拉取式诊断(Pull Diagnostics)协议详解:从推送模型到按需计算的完整实现指南 开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载导读本文深入剖析 language-server-protocol 与 metaModel 元模型 的底层证据链。为什么需要拉取式诊断推送模型的三个痛点在 3.17 之前的 LSP 版本中诊断由服务端通过textDocument/publishDiagnostics通知主动推送给客户端详见 publishDiagnostics.md。该模型的特点是诊断的所有权在服务端客户端只能被动接收。规范指出这一模型存在三个明显缺陷服务端无法感知客户端 UI 状态虽然理论上服务端可以自行选择计算时机但由于它不知道用户当前正在编辑哪个文件、哪些文件可见于编辑器也就无法为用户正在输入的文件或编辑器可见文件优先安排诊断计算。从同步通知推断 UI 状态不可靠通过textDocument/didOpen和textDocument/didChange通知来推断客户端 UI 状态会带来误报false positives——因为这两类通知本质上是所有权转移通知ownership transfer notifications并不等价于文件在 UI 中的可见状态。工作区级诊断的资源浪费为工作区中所有文件统一计算诊断往往包含大量用户当前并不关心的文件造成不必要的计算开销。因此3.17 规范引入了**诊断拉取请求diagnostic pull requests**的概念把为哪些文档计算诊断、在什么时间点计算的控制权交还给客户端。3.18 在此基础上继续演进例如为诊断消息增加了MarkupContent支持。能力协商客户端与服务端的双向声明拉取式诊断的正确工作依赖握手阶段initialize请求的能力协商。服务端必须同时检查客户端能力并在InitializeResult.capabilities中声明自身的diagnosticProvider否则拉取请求不会发生。客户端能力textDocument.diagnostic客户端在InitializeParams.capabilities.textDocument.diagnostic中声明对文档级拉取式诊断的支持对应ServerCapabilities中的字段定义见 initialize.md 的TextDocumentClientCapabilities与ClientCapabilities。其类型DiagnosticClientCapabilities定义如下export type ClientDiagnosticsTagOptions { /** * The tags supported by the client. */ valueSet: DiagnosticTag[]; }; /** * Client capabilities specific to diagnostic pull requests. * * since 3.17.0 */ export interface DiagnosticClientCapabilities { /** * Whether implementation supports dynamic registration. If this is set to * true, the client supports the new * (TextDocumentRegistrationOptions StaticRegistrationOptions) * return value for the corresponding server capability as well. */ dynamicRegistration?: boolean; /** * Whether the clients supports related documents for document diagnostic * pulls. */ relatedDocumentSupport?: boolean; /** * Whether the clients accepts diagnostics with related information. */ relatedInformation?: boolean; /** * Client supports the tag property to provide meta data about a diagnostic. * Clients supporting tags have to handle unknown tags gracefully. */ tagSupport?: ClientDiagnosticsTagOptions; /** * Client supports a codeDescription property */ codeDescriptionSupport?: boolean; /** * Whether the client supports MarkupContent in diagnostic messages. * * since 3.18.0 */ markupMessageSupport?: boolean; /** * Whether code action supports the data property which is * preserved between a textDocument/publishDiagnostics and * textDocument/codeAction request. */ dataSupport?: boolean; }各字段的职责如下表所示字段语义关键说明dynamicRegistration是否支持动态注册若为true服务端对应能力可返回TextDocumentRegistrationOptions StaticRegistrationOptions联合类型relatedDocumentSupport是否支持关联文档诊断决定服务端能否在报告中携带relatedDocuments字段relatedInformation是否接受带关联信息的诊断对应Diagnostic.relatedInformation属性tagSupport是否支持诊断标签元数据valueSet列出客户端支持的DiagnosticTag集合如 Unnecessary1、Deprecated2客户端必须优雅处理未知标签codeDescriptionSupport是否支持codeDescription属性对应CodeDescription.href用于展示错误码说明链接markupMessageSupport是否支持 MarkupContent 诊断消息3.18 新增未声明时服务端不应发送MarkupContent类型消息dataSupport是否支持data属性该数据需在publishDiagnostics与codeAction请求之间保持透传服务端能力diagnosticProvider服务端在InitializeResult.capabilities.diagnosticProvider中声明支持拉取式诊断metaModel 中对应serverCapability: diagnosticProvider见 metaModel.json。其类型DiagnosticOptions定义如下/** * Diagnostic options. * * since 3.17.0 */ export interface DiagnosticOptions extends WorkDoneProgressOptions { /** * An optional identifier under which the diagnostics are * managed by the client. */ identifier?: string; /** * Whether the language has inter file dependencies, meaning that * editing code in one file can result in a different diagnostic * set in another file. Inter file dependencies are common for * most programming languages and typically uncommon for linters. */ interFileDependencies: boolean; /** * The server provides support for workspace diagnostics as well. */ workspaceDiagnostics: boolean; }identifier可选的字符串标识符用于在客户端区分多套诊断管理空间。后续的DocumentDiagnosticParams.identifier、WorkspaceDiagnosticParams.identifier会回传该值便于同一客户端内并存多个诊断来源。interFileDependencies必填声明语言是否存在跨文件依赖——即编辑文件 A 可能导致文件 B 的诊断集变化。大多数编程语言都有跨文件依赖而典型的 linter 通常没有。该值直接影响客户端的拉取策略见下文实现要点。workspaceDiagnostics必填声明服务端是否同时提供工作区级诊断能力只有为true时客户端才会发起workspace/diagnostic请求。DiagnosticOptions继承自WorkDoneProgressOptions这意味着服务端可以在能力声明中带workDoneProgress以表明支持进度上报。注册选项DiagnosticRegistrationOptions当客户端启用动态注册时服务端通过client/registerCapability注册诊断能力注册参数类型为/** * Diagnostic registration options. * * since 3.17.0 */ export interface DiagnosticRegistrationOptions extends TextDocumentRegistrationOptions, DiagnosticOptions, StaticRegistrationOptions { }它是三个基础接口的组合TextDocumentRegistrationOptions提供文档选择器documentSelector、StaticRegistrationOptions提供静态注册的id、DiagnosticOptions提供上述诊断配置。metaModel 元模型中 textDocument/diagnostic 与 workspace/diagnostic 的registrationOptions均指向该类型。文档级诊断拉取textDocument/diagnostic请求参数客户端向服务端发起textDocument/diagnostic请求方向 clientToServer请求服务端为当前已同步版本的文档计算诊断。参数类型DocumentDiagnosticParams/** * Parameters of the document diagnostic request. * * since 3.17.0 */ export interface DocumentDiagnosticParams extends WorkDoneProgressParams, PartialResultParams { /** * The text document. */ textDocument: TextDocumentIdentifier; /** * The additional identifier provided during registration. */ identifier?: string; /** * The result ID of a previous response, if provided. */ previousResultId?: string; }三个字段含义textDocument目标文档的标识URI必填identifier与注册/能力声明时的identifier对应可选previousResultId上一次响应的结果 ID可选。这是增量去重的关键——若客户端持有上一次的resultId服务端可以返回unchanged报告从而跳过全量计算。WorkDoneProgressParams允许携带workDoneToken汇报进度PartialResultParams允许携带partialResultToken流式返回部分结果。响应类型Full 与 Unchanged 双模式报告响应的根类型为DocumentDiagnosticReport/** * The result of a document diagnostic pull request. A report can * either be a full report, containing all diagnostics for the * requested document, or an unchanged report, indicating that nothing * has changed in terms of diagnostics in comparison to the last * pull request. * * since 3.17.0 */ export type DocumentDiagnosticReport RelatedFullDocumentDiagnosticReport | RelatedUnchangedDocumentDiagnosticReport;报告种类由kind判别字段区分/** * The document diagnostic report kinds. * * since 3.17.0 */ export namespace DocumentDiagnosticReportKind { /** * A diagnostic report with a full * set of problems. */ export const Full full; /** * A report indicating that the last * returned report is still accurate. */ export const Unchanged unchanged; } export type DocumentDiagnosticReportKind full | unchanged;全量报告Full/** * A diagnostic report with a full set of problems. * * since 3.17.0 */ export interface FullDocumentDiagnosticReport { /** * A full document diagnostic report. */ kind: DocumentDiagnosticReportKind.Full; /** * An optional result ID. If provided, it will * be sent on the next diagnostic request for the * same document. */ resultId?: string; /** * The actual items. */ items: Diagnostic[]; }未变更报告Unchanged/** * A diagnostic report indicating that the last returned * report is still accurate. * * since 3.17.0 */ export interface UnchangedDocumentDiagnosticReport { /** * A document diagnostic report indicating * no changes to the last result. A server can * only return unchanged if result IDs are * provided. */ kind: DocumentDiagnosticReportKind.Unchanged; /** * A result ID which will be sent on the next * diagnostic request for the same document. */ resultId: string; }使用约束服务端只有在客户端提供了previousResultId且确认诊断集未变化时才能返回unchanged否则必须返回full。resultId的取值与含义完全由服务端自定义如内容哈希或版本号客户端仅做不透明的透传与回传。关联文档报告解决跨文件诊断针对 C/C 这类存在宏展开等跨文件传播的语言拉取式诊断支持**关联文档related documents**机制。两类基础报告分别扩展出关联文档版本/** * A full diagnostic report with a set of related documents. * * since 3.17.0 */ export interface RelatedFullDocumentDiagnosticReport extends FullDocumentDiagnosticReport { /** * Diagnostics of related documents. This information is useful * in programming languages where code in a file A can generate * diagnostics in a file B which A depends on. An example of * such a language is C/C, where macro definitions in a file * a.cpp can result in errors in a header file b.hpp. * * since 3.17.0 */ relatedDocuments?: { [uri: string /** DocumentUri */]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; }/** * An unchanged diagnostic report with a set of related documents. * * since 3.17.0 */ export interface RelatedUnchangedDocumentDiagnosticReport extends UnchangedDocumentDiagnosticReport { /** * Diagnostics of related documents. This information is useful * in programming languages where code in a file A can generate * diagnostics in a file B which A depends on. An example of * such a language is C/C, where macro definitions in a file * a.cpp can result in errors in a header file b.hpp. * * since 3.17.0 */ relatedDocuments?: { [uri: string /** DocumentUri */]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; }relatedDocuments是一个以DocumentUri为键、以全量或未变更报告为值的映射。典型场景a.cpp中的宏定义可能导致头文件b.hpp出现错误——客户端请求a.cpp的诊断时服务端可在同一响应中附带b.hpp的诊断报告。规范建议客户端在didOpen/didChange后主动拉取关联文档具体策略见实现要点。部分结果与取消语义部分结果协议允许以1 个首字面量 n 个部分结果字面量的形式流式返回。首个字面量必须是DocumentDiagnosticReport随后是 n 个DocumentDiagnosticReportPartialResult/** * A partial result for a document diagnostic report. * * since 3.17.0 */ export interface DocumentDiagnosticReportPartialResult { relatedDocuments: { [uri: string /** DocumentUri */]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; }错误与取消请求期间若发生异常按 LSP 通用错误规范返回 code 与 message。服务端还可以返回ServerCancelled错误码表示当前无法计算并附带取消数据/** * Cancellation data returned from a diagnostic request. * * since 3.17.0 */ export interface DiagnosticServerCancellationData { retriggerRequest: boolean; }若未附带该数据默认值为{ retriggerRequest: true }——即客户端默认应重新触发请求。工作区级诊断拉取workspace/diagnosticworkspace/diagnostic请求clientToServer用于替代过去服务端主动推送的工作区级诊断客户端可一次性拉取整个工作区的诊断结果。与文档级请求不同可以长时间运行不受特定文档或文档状态的绑定若客户端支持流式返回服务端可以对同一文档 URI 多次上报诊断报告后上报者覆盖先前上报者。冲突裁决规则客户端可能同时对同一文档发起文档级拉取与工作区级拉取此时必须裁决展示哪份诊断。规范给出的通用规则是文档版本更高者胜出注意文档版本号是单调递增的higher version代表更新状态文档级拉取的诊断优先于工作区级拉取的诊断。请求参数/** * Parameters of the workspace diagnostic request. * * since 3.17.0 */ export interface WorkspaceDiagnosticParams extends WorkDoneProgressParams, PartialResultParams { /** * The additional identifier provided during registration. */ identifier?: string; /** * The currently known diagnostic reports with their * previous result IDs. */ previousResultIds: PreviousResultId[]; }previousResultIds是客户端已持有的各文档报告及其结果 ID 的列表必填数组可为空/** * A previous result ID in a workspace pull request. * * since 3.17.0 */ export interface PreviousResultId { /** * The URI for which the client knows a * result ID. */ uri: DocumentUri; /** * The value of the previous result ID. */ value: string; }响应结构工作区级响应根类型/** * A workspace diagnostic report. * * since 3.17.0 */ export interface WorkspaceDiagnosticReport { items: WorkspaceDocumentDiagnosticReport[]; }其中的条目WorkspaceDocumentDiagnosticReport是全量与未变更报告的工作区变体联合类型在基础报告之上额外增加了文档 URI 与版本号/** * A full document diagnostic report for a workspace diagnostic result. * * since 3.17.0 */ export interface WorkspaceFullDocumentDiagnosticReport extends FullDocumentDiagnosticReport { /** * The URI for which diagnostic information is reported. */ uri: DocumentUri; /** * The version number for which the diagnostics are reported. * If the document is not marked as open, null can be provided. */ version: integer | null; }/** * An unchanged document diagnostic report for a workspace diagnostic result. * * since 3.17.0 */ export interface WorkspaceUnchangedDocumentDiagnosticReport extends UnchangedDocumentDiagnosticReport { /** * The URI for which diagnostic information is reported. */ uri: DocumentUri; /** * The version number for which the diagnostics are reported. * If the document is not marked as open, null can be provided. */ version: integer | null; };/** * A workspace diagnostic document report. * * since 3.17.0 */ export type WorkspaceDocumentDiagnosticReport WorkspaceFullDocumentDiagnosticReport | WorkspaceUnchangedDocumentDiagnosticReport;version字段是客户端执行冲突裁决版本高者胜的数据基础对未打开未同步的文档服务端可将其设为null。部分结果与错误部分结果的首字面量必须是WorkspaceDiagnosticReport随后是 n 个WorkspaceDiagnosticReportPartialResult/** * A partial result for a workspace diagnostic report. * * since 3.17.0 */ export interface WorkspaceDiagnosticReportPartialResult { items: WorkspaceDocumentDiagnosticReport[]; }错误与取消语义同文档级请求可返回ServerCancelled并附带DiagnosticServerCancellationData缺省retriggerRequest: true。服务端主动刷新workspace/diagnostic/refresh当服务端检测到项目级配置变更、需要重算全部诊断时可主动向客户端发起workspace/diagnostic/refresh请求serverToClient要求客户端刷新所有文档级与工作区级诊断。方法名workspace/diagnostic/refresh参数无响应voidresult 为 null出错时返回标准错误 code 与 message。metaModel 元模型 workspace/diagnostic/refresh 中记录了该请求的完整定义clientCapability 为workspace.diagnostics.refreshSupport。对应的客户端能力DiagnosticWorkspaceClientCapabilities位于workspace.diagnostics/** * Workspace client capabilities specific to diagnostic pull requests. * * since 3.17.0 */ export interface DiagnosticWorkspaceClientCapabilities { /** * Whether the client implementation supports a refresh request sent from * the server to the client. * * Note that this event is global and will force the client to refresh all * pulled diagnostics currently shown. It should be used with absolute care * and is useful for situation where a server, for example, detects a project * wide change that requires such a calculation. */ refreshSupport?: boolean; }规范特别强调该事件的全局性——一次刷新会强制客户端重拉当前展示的所有诊断属于高成本操作务必谨慎使用仅在确实需要全量重算如项目级配置变化时触发。客户端实现要点与最佳实践规范明确LSP 本身不强制任何特定客户端实现因为具体策略取决于客户端 UI 行为但针对文档级与工作区级两级诊断给出了三条明确的实践建议对用户正在输入的文件做主动高频拉取——这是拉取模型相对推送模型的核心收益诊断计算优先服务于用户的实时编辑体验。若服务端声明了interFileDependencies客户端应额外对可见文档进行拉取确保跨文件诊断准确但拉取频率应低于正在编辑的文件以平衡计算开销。若服务端声明了workspaceDiagnostics客户端应同时拉取工作区诊断并且强烈建议为工作区拉取实现部分结果进度partial result progress使服务端可以长时间保持请求开启、持续流式上报若服务端关闭了工作区诊断拉取请求客户端应重新触发该请求与DiagnosticServerCancellationData默认的retriggerRequest: true语义一致。从推送模型迁移的实践建议对于正在实现语言服务端的开发者将诊断从推送模型迁移到拉取模型可遵循如下步骤能力声明在initialize响应的capabilities中设置diagnosticProvider: { interFileDependencies: bool, workspaceDiagnostics: bool }如需动态注册则返回DiagnosticRegistrationOptions。文档级响应实现textDocument/diagnostic处理器返回{ kind: full, items: [...] }在客户端回传previousResultId且结果未变时返回{ kind: unchanged, resultId }。注意Diagnostic类型的message字段在 3.18 中可以是string | MarkupContent但仅在客户端声明textDocument.diagnostic.markupMessageSupport时才应发送 MarkupContent 消息详见 diagnostic.md。工作区级响应实现workspace/diagnostic处理器结合previousResultIds只返回有变化的文档充分利用Unchanged报告降低负载。配置变更刷新项目配置变化时调用workspace/diagnostic/refresh通知客户端重拉。测试验证可将 metaModel.json 与 metaModel.ts 作为类型契约基准确保请求/响应结构与 3.18 元模型一致。总结拉取式诊断是 LSP 诊断机制从服务端单向推送走向客户端按需拉取的关键架构升级textDocument/diagnostic让客户端精确控制文档级计算时机workspace/diagnostic配合流式部分结果支撑长时运行的工作区级计算workspace/diagnostic/refresh则为项目级配置变更提供了全量重算的通道。三者共同解决了推送模型下无法感知 UI 状态、计算无法按需优先级排序的结构性问题。相关协议细节可以继续查阅 3.18 规范全文、初始化能力定义 以及 Diagnostic 类型定义。赞分享开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载相关推荐LSP Pull Diagnostics 诊断拉取模型全解析从推送式到拉取式的协议设计与实现指南3.19LSP Pull Diagnostics 诊断拉取模型全解析从推送式到拉取式的协议设计与实现指南3.19 本指南以 Language Server Pro开发工具Language Server Protocol 3.18 workspace/didChangeConfiguration 通知解析从配置变更推送到拉取模型Language Server Protocol 3.18 workspace/didChangeConfiguration 通知解析从配置变更推送到拉取模型开发工具LSP 3.17 Pull Diagnostics 诊断拉取机制详解从服务器推送通知到客户端驱动的诊断计算LSP 3.17 Pull Diagnostics 诊断拉取机制详解从服务器推送通知到客户端驱动的诊断计算 本文基于当前仓库 Language Server开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表