- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
导读
本文基于本仓库_specifications/lsp/3.19/types/partialResults.md规范文档,系统讲解 LSP 3.15 起引入的 Partial Result Progress(部分结果进度)机制:服务器如何在客户端允许的前提下,通过通用的$/progress通知将workspace/symbol、textDocument/reference等请求的超大结果集分批增量返回,以及错误发生时客户端应如何正确处理已收到的部分结果。读完本文,你将掌握partialResultToken的传递方式、$/progress通知的载荷结构、结果追加与最终响应置空的协议约定,并能据此在语言服务器与客户端中实现可流式返回的结果通道。
一、Partial Result 机制的由来与定位
1.1 版本与协议地位
Partial Result Progress 是 Language Server Protocol 自3.15.0版本起正式引入的能力。它在协议中归属于请求参数层面的通用基础设施(与 Work Done Progress 并列),由以下规范文档共同定义:
- Partial Result Progress 说明:本文核心,定义机制与错误处理规则;
- PartialResultParams 类型定义:承载
partialResultToken的参数字面量接口; - Work Done Progress 类型定义:同源于
$/progress通知的兄弟机制。
注意:虽然本节仓库同时维护了 3.17、3.18、3.19 三个版本目录,但 partialResults.md 在 3.18 与 3.19 中的内容完全一致,均标注 Since 3.15.0。Partial Result 语义自 3.15 定型后保持稳定,本文以 3.19 版本为准展开。
1.2 为什么需要 Partial Result
在未引入该机制之前,像workspace/symbol(工作区符号检索)、textDocument/reference(查找引用)这类请求,服务器必须等全部结果收集完毕后再一次性返回完整数组。当结果集规模巨大(例如全仓引用、全局符号索引)时:
- 客户端 UI 长时间空白无反馈;
- 网络传输单个超大 JSON 响应,首屏渲染延迟高;
- 服务器内存被一次性的大结果集占用。
Partial Result 解决的是"结果本身"的分批投递问题:服务器边收集边推送,客户端边接收边展示,配合 Work Done Progress 的进度提示,形成完整的"流式结果 + 进度反馈"体验。
二、核心机制:$/progress通知 +partialResultToken
2.1 通用进度通知$/progress
Partial Result 与 Work Done Progress 共享同一个通用的$/progress通知通道。从本仓库的 3.19 metaModel.json 可见其在元模型中的定义:
{ "method": "$/progress", "typeName": "ProgressNotification", "messageDirection": "both", "params": { "kind": "reference", "name": "ProgressParams" } }要点:$/progress的messageDirection为both,即客户端与服务器双向都可发送;其参数结构ProgressParams形如:
{ "token": "<ProgressToken>", "value": { ... } }其中token用于把通知关联到某一次具体的请求,value则根据通知类型携带不同的载荷(Work Done 的 begin/report/end 或 Partial Result 的结果数组)。
2.2 什么请求支持 Partial Result
并非所有请求都支持部分结果。从 3.19 metaModel.json 中可以看到,大量请求结构的mixins同时注入了WorkDoneProgressParams与PartialResultParams,例如ImplementationParams(metaModel.json 第 2413-2430 行):
{ "name": "ImplementationParams", "extends": [{ "kind": "reference", "name": "TextDocumentPositionParams" }], "mixins": [ { "kind": "reference", "name": "WorkDoneProgressParams" }, { "kind": "reference", "name": "PartialResultParams" } ] }同理,textDocument/reference、workspace/symbol、textDocument/documentSymbol、workspace/executeCommand等请求在元模型中均以PartialResultParams作为 mixin(全文检索可见PartialResultParams在 metaModel.json 中出现 26 处,覆盖了语言特性类与工作区类的主要查询请求)。
PartialResultParams本身的完整定义见 partialResultParams.md:
export interface PartialResultParams { /** * An optional token that a server can use to report partial results (e.g. * streaming) to the client. */ partialResultToken?: ProgressToken; }ProgressToken类型(metaModel.json 第 15863 行)为integer | string的联合类型,即令牌既可以是整数编号,也可以是字符串 UUID。
2.3 一次完整的 Partial Result 交互
规范文档给出了一个同时支持 Work Done 与 Partial Result 的textDocument/reference请求示例:
{ "textDocument": { "uri": "file:///folder/file.ts" }, "position": { "line": 9, "character": 5 }, "context": { "includeDeclaration": true }, // The token used to report work done progress. "workDoneToken": "1d546990-40a3-4b77-b134-46622995f6ae", // The token used to report partial result progress. "partialResultToken": "5f6f349e-4f81-4a3b-afff-ee04bff96804" }字段解读:
| 字段 | 含义 |
|---|---|
textDocument.uri | 目标文档的file://URI |
position.line/position.character | 引用符号所在位置(0 基) |
context.includeDeclaration | 是否包含声明位置本身 |
workDoneToken | 供服务器发送进度 begin/report/end 通知的令牌 |
partialResultToken | 供服务器发送部分结果通知的令牌 |
服务器收到请求后,用partialResultToken作为$/progress通知的token字段,逐批推送结果。例如查找引用返回前两批结果时:
{ "token": "5f6f349e-4f81-4a3b-afff-ee04bff96804", "value": [ { "uri": "file:///folder/file.ts", "range": { "start": { "line": 12, "character": 8 }, "end": { "line": 12, "character": 16 } } } ] }(后续批次继续以同一 token 追加发送。)
三、服务器端的协议约定(发送方规则)
规范对服务器发送 Partial Result 提出了两条刚性约定,这是实现时最容易出错、也最需要严格遵守的部分:
3.1 全部结果必须走$/progress,最终响应结果必须为空
If a server reports partial result via a corresponding
$/progress, the whole result must be reported using$/progressnotifications, each of which appends items to the result. The final response has to be empty in terms of result values.
即:一旦服务器决定用$/progress上报部分结果,就必须把所有结果都通过$/progress通知发出,每一条通知向结果集追加一批条目;最终请求响应中的结果值必须为空。
例如textDocument/reference的正常响应类型为Location[],走 Partial Result 时最终响应应为:
{ "jsonrpc": "2.0", "id": 1, "result": [] }(即空数组 / null 结果值。)
之所以这样设计,规范给出的理由是"避免混淆最终结果应如何解读"——例如收到完整结果时,客户端无法判断它到底是又一个部分结果、还是一个替换性的最终结果。通过"响应结果为空 + 所有数据走通知"的明确约定,客户端可以无歧义地合并所有$/progress载荷。
3.2 载荷结构与最终结果类型一致
The value payload of a partial result progress notification is in most cases the same as the final result.
部分结果的载荷类型与最终结果类型基本一致。例如workspace/symbol的最终结果是SymbolInformation[] | WorkspaceSymbol[],那么每条$/progress的value也是SymbolInformation[] | WorkspaceSymbol[]——即每条通知携带一段同类型的数组切片,而不是单个条目。
3.3 一个 token 的一次性使用
与 Work Done Progress 的服务器主动进度(window/workDoneProgress/create创建的 token 仅能使用一次)不同,Partial Result 的 token 由客户端随请求下发,其生命周期绑定本次请求:token 仅在"请求发出后、响应返回前"这段时间内有效。服务器应保证在响应返回前完成所有部分结果通知的发送。
四、客户端处理规则(接收方与错误语义)
4.1 正常路径:增量合并
客户端收到以partialResultToken为 token 的$/progress通知后,应将通知value中的条目追加到该请求的结果集合中。多个$/progress通知之间是有序追加关系(append),客户端按收到顺序合并即可,无需去重或排序。
4.2 错误路径:按错误码区分处理
规范明确规定,若请求最终响应报错,已收到的部分结果按以下规则处理:
响应错误code | 客户端处理方式 |
|---|---|
RequestCancelled(即-32800,请求被取消) | 可以使用已收到的结果,但必须向用户明确该请求已被取消、结果可能不完整 |
| 其他所有错误码 | 丢弃已收到的部分结果,不得使用 |
用伪代码表述客户端逻辑:
onRequestError(error, collectedPartialResults) { if (error.code === RequestCancelled) { // 可用,但需标注"请求已取消,结果可能不完整" ui.showResults(collectedPartialResults, { incomplete: true }); } else { // 其他错误:丢弃 discard(collectedPartialResults); } }4.3 客户端能力信号:按请求实例逐次声明
值得注意:协议中没有"客户端是否支持 Partial Result"的静态能力声明(如clientCapabilities中的固定开关)。原因正如 Work Done Progress 章节所解释的——对多数客户端而言这不是静态属性,同一个请求类型在不同实例上可能不同。因此客户端是否接受部分结果,通过每个请求参数中是否存在partialResultToken字段来逐次声明。服务器不应假设客户端总是支持,也不应要求客户端预先声明全局能力。
(服务器侧对应的"支持情况"通过具体请求的能力项声明,例如referencesProvider中可带workDoneProgress选项,但 Partial Result 本身同样不设独立的静态 capability,全部依赖请求实例上的 token 存在性。)
五、从元模型看 Partial Result 在 3.19 的覆盖范围
为了验证哪些请求真正接入了 Partial Result,可在 3.19 metaModel.json 中检索"name": "PartialResultParams"出现的 26 处位置,它们分别出现在以下请求/注册结构的mixins中(节选):
ImplementationParams(第 2413-2430 行)TypeDefinitionParams、DeclarationParams、ReferencesParams(同构结构)DocumentSymbolParams、WorkspaceSymbolParams、CodeLensParams、DocumentLinkParams、DocumentColorParams、FoldingRangeParams、SelectionRangeParams、InlayHintParams、CallHierarchy*Params、MonikerParams等
据此可以总结出三类典型场景:
- 引用/定义类查询:
textDocument/reference、textDocument/declaration、textDocument/typeDefinition、textDocument/implementation——结果是一批Location,天然适合流式返回; - 文档/工作区符号:
textDocument/documentSymbol、workspace/symbol——全仓或大文件符号集合可能很大; - 文档内散点数据:
textDocument/codeLens、textDocument/documentLink、textDocument/foldingRange、textDocument/documentColor、textDocument/inlayHint等——文档级请求虽然结果相对可控,但协议同样允许流式返回以降低单次响应体积。
以上请求名单为从元模型 mixins 中可推断的实现事实;具体到某个语言服务器是否真的对某个请求启用 Partial Result,取决于该服务器实现,客户端仍需按"token 存在即支持"的规则适配。
六、实现要点与最佳实践
综合规范与元模型证据,落地 Partial Result 时建议遵循以下要点:
- 客户端侧:仅在确实需要流式展示(如引用面板渐进填充)时,才在请求参数中附加
partialResultToken;收到$/progress通知时按 token 路由到对应请求,并持续追加合并;收到最终响应后,若result为空数组而此前已有部分结果,则直接用合并结果,若响应报错则按第 4.2 节规则处理。 - 服务器侧:当请求携带
partialResultToken时,不要等全部结果收集完再一次性返回;而是分批构造$/progress通知(token = partialResultToken,value = 结果切片),全部发完后返回空结果;最终响应结果置空是强制性约定,切不可"通知发一半、响应再带部分数据",否则客户端将无法区分追加与替换语义。 - 取消语义:若请求被取消,响应应带
RequestCancelled错误码,此时客户端可保留已收到的部分结果并标注不完整;其他错误则必须丢弃部分结果,避免把错误场景的残缺数据当作有效结果展示。 - 与 Work Done Progress 协同:两者共享
$/progress通道但语义不同(进度 vs 数据)。实现时可同时下发workDoneToken与partialResultToken,进度与数据并行推进,但务必区分通知载荷类型,避免混淆。
七、小结
Partial Result Progress 是 LSP 3.15 引入、在 3.19 中保持稳定的通用流式结果机制:客户端通过请求参数中的partialResultToken逐实例声明支持,服务器通过$/progress通知以"追加切片"的方式推送与最终结果同类型的数据,最终响应结果置空;错误时按RequestCancelled与其他错误码分别决定"可保留但标注不完整"或"整体丢弃"。
该机制与 Work Done Progress 共同构成了 LSP 面向长耗时、大结果量请求的完整反馈方案,是workspace/symbol、textDocument/reference等高成本查询在现代语言服务器与编辑器集成中实现"边算边出"体验的基础设施。
- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
相关推荐
Language Server Protocol 部分结果进度(Partial Result Progress)机制详解:基于 `$/progress` 的流式结果报告
Language Server Protocol 部分结果进度(Partial Result Progress)机制详解:基于 $/progress 的流式结果
开发工具如何写出自己的KittenBlock扩展:以ps2-controller为例,从Blockly积木到Python代码生成全解
如何写出自己的KittenBlock扩展:以ps2 controller为例,从Blockly积木到Python代码生成全解 本文以 ps2 controlle
开发工具opencodex 中的 Cursor 用量上报修复方案:基于累计上下文与错误路径发射的 WP1 计划全解析
opencodex 中的 Cursor 用量上报修复方案:基于累计上下文与错误路径发射的 WP1 计划全解析 导读 本文以 opencodex 仓库中 devl
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考