- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
导读
textDocument/linkedEditingRange是 LSP(Language Server Protocol)自 3.16.0 起引入的一项核心语言特性:当用户在文档中编辑一个符号时,客户端向语言服务器发起请求,服务器返回"与当前位置符号内容相同、可一并编辑"的所有范围(range),从而实现一次修改、多处同步的联动编辑(linked editing)。本文以本仓库 _specifications/lsp/3.19/language/linkedEditingRange.md 为骨架,结合 3.19 版规格中的 general/initialize.md、metaModel/metaModel.json 等源码级证据,完整讲解该请求的客户端/服务端能力声明、参数与响应结构、约束条件及典型应用场景,帮助读者在实现语言服务器或客户端时快速落地这一特性。
一、什么是 linked editing range
联动编辑(linked editing)是编辑器中的常见交互模式:当用户编辑一个符号时,文档中所有与该符号内容相同的其他位置被"关联"起来,同步更新。典型场景包括:
- HTML/XML 标签对:编辑
<div>的开始标签时,</div>结束标签同步改名; - JSX 组件标签:
<MyComponent>与</MyComponent>联动; - Markdown 链接/锚点、LaTeX 环境等成对出现的语法结构。
LSP 通过textDocument/linkedEditingRange请求将这一能力标准化:请求由客户端发送到服务器,用于返回"文档中给定位置处符号的范围,以及所有与该符号内容相同的范围"。可选地,服务器还可以返回一个wordPattern(正则表达式形式的单词模式)来描述合法内容。之后,用户对其中任一范围的修改,只要新内容合法,就会被应用到所有其他范围上。
如果服务器没有返回请求专属的wordPattern,客户端将回退使用客户端语言配置中的 word pattern来校验修改后的内容是否合法。
二、能力协商:客户端与服务端的握手
与所有 LSP 特性一样,linked editing range 需要客户端和服务端在initialize阶段互相声明能力。本仓库 general/initialize.md 中同时收录了这两处声明。
2.1 客户端能力textDocument.linkedEditingRange
客户端能力(可选属性)定义如下:
| 项 | 值 |
|---|---|
| property name(可选) | textDocument.linkedEditingRange |
| property type | LinkedEditingRangeClientCapabilities |
export interface LinkedEditingRangeClientCapabilities { /** * Whether the 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; }dynamicRegistration表示客户端是否支持动态注册:如果为true,则客户端不仅支持服务器在initialize响应中静态声明能力,还支持服务器随后通过client/registerCapability请求动态注册/注销该能力。
在源码中,该能力出现在 initialize.md 第 259–264 行,属于TextDocumentClientCapabilities的一部分:
/** * Capabilities specific to the `textDocument/linkedEditingRange` request. * * @since 3.16.0 */ linkedEditingRange?: LinkedEditingRangeClientCapabilities;它与publishDiagnostics、foldingRange、selectionRange、callHierarchy、semanticTokens等请求能力并列声明。
2.2 服务器能力linkedEditingRangeProvider
服务器能力(可选属性)定义如下:
| 项 | 值 |
|---|---|
| property name(可选) | linkedEditingRangeProvider |
| property type | boolean|LinkedEditingRangeOptions|LinkedEditingRangeRegistrationOptions |
LinkedEditingRangeOptions继承自WorkDoneProgressOptions:
export interface LinkedEditingRangeOptions extends WorkDoneProgressOptions { }也就是说,服务器可以在能力声明中携带workDoneProgress?: boolean,声明该请求是否支持工作进度上报(WorkDoneProgressOptions的完整定义见 types/workDoneProgress.md)。
三种取值方式:
true:简单声明支持该请求,不携带任何选项;LinkedEditingRangeOptions:声明支持,并附带workDoneProgress等选项;LinkedEditingRangeRegistrationOptions:声明支持,同时指定注册选项(文档筛选器、静态注册 id 等),用于动态注册场景。
在源码中,该能力出现在 initialize.md 第 917–923 行:
/** * The server provides linked editing range support. * * @since 3.16.0 */ linkedEditingRangeProvider?: boolean | LinkedEditingRangeOptions | LinkedEditingRangeRegistrationOptions;2.3 注册选项LinkedEditingRangeRegistrationOptions
当客户端支持动态注册时,服务器可以在注册/静态声明中给出完整的注册选项:
export interface LinkedEditingRangeRegistrationOptions extends TextDocumentRegistrationOptions, LinkedEditingRangeOptions, StaticRegistrationOptions { }它同时继承了:
TextDocumentRegistrationOptions:携带documentSelector,声明该能力适用于哪些文档(语言 id、模式、方案等);LinkedEditingRangeOptions:携带workDoneProgress等选项;StaticRegistrationOptions:携带id,用于动态注册时的能力标识。
这正是客户端能力中
dynamicRegistration: true所承诺支持的返回形式——服务器可以返回(TextDocumentRegistrationOptions & StaticRegistrationOptions)的组合。
三、请求定义:方法、参数与响应
3.1 请求方法
| 项 | 值 |
|---|---|
| method | textDocument/linkedEditingRange |
| params | LinkedEditingRangeParams |
| result | LinkedEditingRanges|null |
| error | 处理异常时返回带code和message的错误响应 |
3.2 参数LinkedEditingRangeParams
export interface LinkedEditingRangeParams extends TextDocumentPositionParams, WorkDoneProgressParams { }参数继承自两个基类:
TextDocumentPositionParams(定义见 types/textDocumentPositionParams.md):
interface TextDocumentPositionParams { /** * The text document. */ textDocument: TextDocumentIdentifier; /** * The position inside the text document. */ position: Position; }其中TextDocumentIdentifier携带文档 URI,Position携带零基(zero-based)的行/列坐标(line、character)。
WorkDoneProgressParams(定义见 types/workDoneProgress.md):
export interface WorkDoneProgressParams { /** * An optional token that a server can use to report work done progress. */ workDoneToken?: ProgressToken; }客户端可以在参数中附带workDoneToken,让服务器通过$/progress通知上报该请求的处理进度。注意:进度令牌只在请求尚未返回响应的期间内有效,取消请求即取消进度。
一个完整的 JSON-RPC 请求示例:
{ "jsonrpc": "2.0", "id": 3, "method": "textDocument/linkedEditingRange", "params": { "textDocument": { "uri": "file:///folder/index.html" }, "position": { "line": 2, "character": 5 }, "workDoneToken": "1d546990-40a3-4b77-b134-46622995f6ae" } }3.3 响应结果LinkedEditingRanges
export interface LinkedEditingRanges { /** * A list of ranges that can be renamed together. The ranges must have * identical length and contain identical text content. The ranges cannot * overlap. */ ranges: Range[]; /** * An optional word pattern (regular expression) that describes valid * contents for the given ranges. If no pattern is provided, the client * configuration's word pattern will be used. */ wordPattern?: string; }响应包含两个字段:
ranges: Range[]—— 可一并修改的范围列表,必须满足三条硬性约束:
- 等长(identical length):所有 range 的字符长度必须一致;
- 内容相同(identical text content):所有 range 覆盖的文本内容必须完全一致;
- 不重叠(cannot overlap):range 之间互不重叠。
Range的定义见 types/range.md:以(零基)起始/结束位置表示,end位置是独占的(exclusive),范围相当于编辑器中的一次选区。例如要覆盖第 5 行第 23 个字符到第 6 行行首(含换行符):
{ start: { line: 5, character: 23 }, end : { line: 6, character: 0 } }wordPattern?: string—— 可选的单词模式(正则表达式),用于描述这些 range 的合法内容。如果省略,客户端将回退使用其语言配置中的 word pattern 校验修改结果。
实际开发中的典型做法:服务器返回的
ranges中包含光标所在位置的那个 range(通常排在列表最前),这样客户端可以直观地以当前位置为基准执行联动编辑。
3.4 错误处理
如果textDocument/linkedEditingRange请求处理过程中发生异常,服务器将返回带有code和message的错误响应,而不是LinkedEditingRanges结果。
四、结合源码验证:请求的类型模型与元数据
本仓库的 metaModel/metaModel.json 是 3.19 版协议的机器可读类型模型,其中完整收录了该请求及其类型定义,可作为实现语言服务器时的权威参考:
LinkedEditingRangeParams(第 3426–3440 行):extends引用TextDocumentPositionParams,mixins引用WorkDoneProgressParams——与协议文档定义完全一致;LinkedEditingRanges(第 3442–3467 行):ranges为Range[]数组,wordPattern为可选string,并标注"since": "3.16.0";LinkedEditingRangeRegistrationOptions(第 3469–3487 行):extends引用TextDocumentRegistrationOptions与LinkedEditingRangeOptions,mixins引用StaticRegistrationOptions;- 此外还包含
LinkedEditingRangeRequest请求条目(第 627 行附近)以及LinkedEditingRangeOptions、LinkedEditingRangeClientCapabilities等类型定义。
在 specification.md 第 664 行,该请求文档通过{% include_relative language/linkedEditingRange.md %}被收录进 3.19 版完整规格书,与本仓库其他语言特性(completion、hover、rename 等)一并构成 3.19 的协议全貌。
从源码结构还可以推断:与 linked editing 关系最紧密的既有请求是textDocument/rename——linked editing 负责"找出可联动修改的范围",真正的批量重命名语义仍由 rename 请求承载;两者的能力协商、参数传递与文档筛选机制在initialize.md中采用完全一致的声明模式(如selectionRangeProvider、callHierarchyProvider等均在同一结构体中并列声明)。
五、典型应用场景与实现要点
5.1 语言服务器侧(Server)实现要点
- 能力声明:在
initialize响应中设置capabilities.linkedEditingRangeProvider = true(或携带选项对象);若客户端dynamicRegistration为真,也可通过client/registerCapability动态注册。 - 处理请求:收到
textDocument/linkedEditingRange后,根据position解析出光标所在的符号(如 HTML 标签名、Markdown 锚点文本),再扫描文档找出所有内容相同、等长、互不重叠的范围。 - 返回校验规则:为
ranges提供wordPattern(如 HTML 标签名的[a-zA-Z][a-zA-Z0-9-]*),以覆盖客户端语言配置缺失或不适用的场景。 - 无结果时返回
null:当当前位置不存在可联动的符号时,返回null表示"该位置不支持 linked editing"。
5.2 客户端侧(Client)实现要点
- 能力声明:在
initialize参数中设置capabilities.textDocument.linkedEditingRange = { dynamicRegistration: true }(若支持动态注册)。 - 触发时机:在用户开始编辑(如输入、粘贴或执行改名命令)时,若光标处存在可编辑范围,先请求 linked editing range,再在用户输入过程中实时把新文本同步应用到
ranges中的所有范围。 - 合法性校验:用户输入的新内容需匹配
wordPattern(若服务器返回);否则回退使用客户端语言配置的 word pattern;不合法时停止同步并回滚。 - 处理
null响应:服务器返回null时,退化为普通单点编辑,不做任何联动。
5.3 一个最小化的响应示例
假设用户在index.html第 2 行第 5 个字符处(<div>的div内部)触发 linked editing,服务器可返回:
{ "jsonrpc": "2.0", "id": 3, "result": { "ranges": [ { "start": { "line": 2, "character": 1 }, "end": { "line": 2, "character": 4 } }, { "start": { "line": 2, "character": 10 }, "end": { "line": 2, "character": 13 } } ], "wordPattern": "[a-zA-Z][a-zA-Z0-9-]*" } }此后用户将开始标签的div改为section,客户端会把</div>同步改为</section>,实现 HTML 标签的联动编辑。
六、版本与适用前提
- linked editing range 请求自 LSP 3.16.0 起引入(请求、
LinkedEditingRanges、LinkedEditingRangeClientCapabilities、LinkedEditingRangeOptions、LinkedEditingRangeRegistrationOptions均标注@since 3.16.0); - 本文所述的接口形态以本仓库 3.19 版规格为准(见 specification.md 与 metaModel.json);
- 在 3.17、3.18 版规格中该请求定义与 3.19 保持一致(仓库的
_specifications/lsp/3.17、_specifications/lsp/3.18目录下同样存在language/linkedEditingRange.md); - 使用前请确认客户端与服务端版本均不低于 3.16.0,并完成双方能力协商,否则请求可能被客户端或服务器静默忽略。
- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
相关推荐
LSP Goto Implementation 请求详解:textDocument/implementation 协议规范与语言服务端实现指南
LSP Goto Implementation 请求详解:textDocument/implementation 协议规范与语言服务端实现指南 导读 本文聚焦语
开发工具GraphiQL语言服务器:LSP协议的GraphQL语言服务实现
GraphiQL语言服务器:LSP协议的GraphQL语言服务实现 引言:GraphQL开发者的智能助手 你是否曾经在编写GraphQL查询时遇到过以下痛点?
开发工具后端语言服务器协议(LSP)教程
语言服务器协议(LSP)教程 1. 项目介绍 语言服务器协议(Language Server Protocol, LSP) 是一个开放标准的JSON RPC协议
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考