news 2026/10/7 2:07:22

语言服务器协议(LSP)linked editing range 请求详解:从协议定义到源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
语言服务器协议(LSP)linked editing range 请求详解:从协议定义到源码实现
  • 开发工具

【免费下载链接】language-server-protocol

Defines a common protocol for language servers.

项目地址:https://gitcode.com/gh_mirrors/la/language-server-protocol
点击查看免费下载

导读

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 typeLinkedEditingRangeClientCapabilities
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 typeboolean|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 请求方法

项值
methodtextDocument/linkedEditingRange
paramsLinkedEditingRangeParams
resultLinkedEditingRanges|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[]—— 可一并修改的范围列表,必须满足三条硬性约束:

  1. 等长(identical length):所有 range 的字符长度必须一致;
  2. 内容相同(identical text content):所有 range 覆盖的文本内容必须完全一致;
  3. 不重叠(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)实现要点

  1. 能力声明:在initialize响应中设置capabilities.linkedEditingRangeProvider = true(或携带选项对象);若客户端dynamicRegistration为真,也可通过client/registerCapability动态注册。
  2. 处理请求:收到textDocument/linkedEditingRange后,根据position解析出光标所在的符号(如 HTML 标签名、Markdown 锚点文本),再扫描文档找出所有内容相同、等长、互不重叠的范围。
  3. 返回校验规则:为ranges提供wordPattern(如 HTML 标签名的[a-zA-Z][a-zA-Z0-9-]*),以覆盖客户端语言配置缺失或不适用的场景。
  4. 无结果时返回null:当当前位置不存在可联动的符号时,返回null表示"该位置不支持 linked editing"。

5.2 客户端侧(Client)实现要点

  1. 能力声明:在initialize参数中设置capabilities.textDocument.linkedEditingRange = { dynamicRegistration: true }(若支持动态注册)。
  2. 触发时机:在用户开始编辑(如输入、粘贴或执行改名命令)时,若光标处存在可编辑范围,先请求 linked editing range,再在用户输入过程中实时把新文本同步应用到ranges中的所有范围。
  3. 合法性校验:用户输入的新内容需匹配wordPattern(若服务器返回);否则回退使用客户端语言配置的 word pattern;不合法时停止同步并回滚。
  4. 处理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.

项目地址:https://gitcode.com/gh_mirrors/la/language-server-protocol
点击查看免费下载
上一篇:终极dbt选择器与标签指南:高效管理大型项目的10个关键技巧
下一篇:IronClaw Security Review 技能实战:AI 驱动的代码安全审计方法论与工程化落地

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 2:04:03

AD7177-2 32位ADC驱动实战:寄存器配置、SPI时序与避坑指南

简介&#xff1a;这份资源面向嵌入式驱动开发工程师与ADC应用开发者&#xff0c;提供AD7177-2高精度Σ-Δ型模数转换器的驱动实现参考&#xff0c;帮助解决芯片初始化配置、寄存器读写、数据采集与异常处理等实际问题。压缩包共5个文件&#xff0c;以3个.h头文件与2个.c源文件为…

作者头像 李华
网站建设 2026/10/7 2:03:53

Electron Forge Flatpak Maker 完全指南:构建沙箱化 Linux 应用安装包

开发工具桌面应用前端构建 【免费下载链接】forge :electron: A complete tool for building and publishing Electron applications 项目地址&#xff1a; https://gitcode.com/gh_mirrors/fo/forge 点击查看 免费下载 导读 electron-forge/maker-flatpak 是 Electron Forge…

作者头像 李华