- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
导读
workspace/didChangeConfiguration是 LSP(Language Server Protocol)中由客户端(编辑器/IDE)主动向语言服务器发送的通知(notification),用于告知服务器用户的配置设置已经发生变化。本文基于 language-server-protocol 仓库中 3.17 版规范 的权威定义,系统讲解该通知的协议格式、客户端能力声明、参数结构,并与 3.6.0 引入的workspace/configuration拉取模型(pull model)进行对比。读完本文,你将掌握如何在客户端正确触发配置变更通知、如何在服务器端注册与响应,以及如何用拉取模型替代传统的推送模型来获取最新配置。
一、通知概览:客户端到服务器的单向配置信号
workspace/didChangeConfiguration是一条从客户端发往服务器的通知(notification),方向为client → server(规范中用标记),其作用正如官方文档所述:
A notification sent from the client to the server to signal the change of configuration settings.
即:当用户在客户端修改了设置(settings)后,客户端通过该通知把变更后的完整配置内容推送给服务器,服务器收到后可以据此更新自己的行为(例如调整格式化选项、补全偏好、诊断开关等)。
与请求(request)不同,通知不需要服务器返回响应,因此它是一条单向、无应答的消息。在 specification.md 中,该通知与workspace/configuration请求、workspace/didChangeWatchedFiles等一起归属于 workspace 功能域。
二、消息格式:方法名与参数结构
2.1 方法名(method)
该通知使用的方法名为:
workspace/didChangeConfiguration2.2 参数类型:DidChangeConfigurationParams
通知的参数类型为DidChangeConfigurationParams,在 didChangeConfiguration.md 中定义如下:
interface DidChangeConfigurationParams { /** * The actual changed settings */ settings: LSPAny; }settings:实际发生变更后的完整配置内容,类型为LSPAny。LSPAny是 LSP 3.17 引入的通用类型,表示"任意 LSP 数据类型",可以容纳对象(object)、数组、字符串、数字、布尔值甚至null。由于它是整个协议中通用的"任意值"载体(在 metaModel.json 中大量用于data?: LSPAny等字段,例如代码补全、CodeLens、悬停等请求的透传数据),因此settings字段对配置的具体结构不做任何限制——完全由客户端与服务端双方自行约定。
一个典型的 JSON-RPC 消息体形如:
{ "jsonrpc": "2.0", "method": "workspace/didChangeConfiguration", "params": { "settings": { "editor": { "tabSize": 4, "insertSpaces": true }, "languageServer": { "lint": { "enabled": true, "severity": "warning" } } } } }注意:settings携带的是变更后的完整配置快照,而不是"变更了哪些字段"的增量信息。协议本身不保证配置的具体 schema,服务器需要具备对任意结构配置的容错能力。
三、客户端能力声明:DidChangeConfigurationClientCapabilities
与所有 LSP 功能一样,客户端是否支持该通知,需要在initialize阶段的ClientCapabilities中显式声明。规范中该能力的属性路径为:
workspace.didChangeConfiguration其属性类型为DidChangeConfigurationClientCapabilities:
export interface DidChangeConfigurationClientCapabilities { /** * Did change configuration notification supports dynamic registration. * * @since 3.6.0 to support the new pull model. */ dynamicRegistration?: boolean; }能力字段说明:
| 字段 | 类型 | 必选 | 含义 |
|---|---|---|---|
dynamicRegistration | boolean | 否 | 该通知是否支持动态注册(dynamic registration),即服务器是否可以在运行时通过client/registerCapability请求动态注册/注销对该通知的兴趣。该字段自3.6.0起引入,目的是配合新的配置拉取模型(详见下文第五节)。 |
在 metaModel.json 的结构化元数据中,DidChangeConfigurationClientCapabilities同样被建模为仅包含可选布尔字段dynamicRegistration的类型,与 Markdown 规范保持完全一致。
一个完整的初始化握手示例(客户端声明其支持该通知):
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "capabilities": { "workspace": { "didChangeConfiguration": { "dynamicRegistration": true } } } } }四、动态注册:DidChangeConfigurationRegistrationOptions
在dynamicRegistration为true的前提下,服务器可以通过client/registerCapability在运行时动态注册对该通知的监听。从 metaModel.json 可以看到注册选项类型DidChangeConfigurationRegistrationOptions:
{ "name": "DidChangeConfigurationRegistrationOptions", "properties": [ { "name": "section", "type": { "kind": "or", "items": [ { "kind": "base", "name": "string" }, { "kind": "array", "element": { "kind": "base", "name": "string" } } ] }, "optional": true } ] }即注册选项包含一个可选的section字段,类型为string | string[],用于指定服务器关心的配置节(section)。这允许服务器只对特定配置节的变化做出反应,而不是盲目接收全部配置。
五、推送模型 vs 拉取模型:与workspace/configuration的关系
理解workspace/didChangeConfiguration的关键,在于它隶属于 LSP 配置机制中的推送模型(push model),而 3.6.0 之后协议又提供了**拉取模型(pull model)**作为替代。规范在 configuration.md 中对此有明确说明:
This pull model replaces the old push model were the client signaled configuration change via an event.
两者的对比如下:
| 维度 | 推送模型(workspace/didChangeConfiguration) | 拉取模型(workspace/configuration) |
|---|---|---|
| 方向 | 客户端 → 服务器(通知,无响应) | 服务器 → 客户端(请求,有响应) |
| 触发时机 | 客户端配置变化时主动推送 | 服务器按需批量获取 |
| 携带内容 | settings: LSPAny完整配置快照 | ConfigurationItem[]列表,可一次请求多项 |
| 引入版本 | 1.0 起(推送模型的传统机制) | 3.6.0 |
在拉取模型中,服务器通过workspace/configuration请求向客户端批量获取配置,响应结果为LSPAny[],返回顺序与请求中的ConfigurationItem顺序一一对应。每个ConfigurationItem包含:
export interface ConfigurationItem { /** * The scope to get the configuration section for. */ scopeUri?: URI; /** * The configuration section asked for. */ section?: string; }section:要获取的配置节(如cpp.formatterOptions),由服务器自行定义,不必与客户端内部的配置存储结构一致——例如服务器请求cpp.formatterOptions,而客户端可能以 XML 或其他布局存储配置,转换工作由客户端负责。scopeUri:配置的作用域资源 URI。若提供,客户端应返回限定到该资源的作用域配置;若客户端无法为给定作用域提供配置,则响应数组中对应位置必须为null。
为什么推送模型仍然需要保留?
规范明确指出:拉取模型取代了旧的推送模型。但服务器如果在拉取模型下缓存了workspace/configuration的结果,仍然需要一种方式获知"配置变了、缓存已失效"。为此,规范给出的做法是——仍然注册一个空(empty)的配置变更通知:
connection.client.register(DidChangeConfigurationNotification.type, undefined);这段代码(出自 configuration.md)的含义是:
- 服务器以空注册选项注册
workspace/didChangeConfiguration通知(undefined表示不关心具体section); - 客户端在配置发生任何变化时都会发送该通知;
- 服务器收到通知后,虽然不使用通知中的
settings数据(因为拉取模型下应再次发起workspace/configuration请求获取权威配置),但借此获知"配置已变化",从而失效本地缓存并重新拉取。
这正是dynamicRegistration字段注释中 "since 3.6.0 to support the new pull model" 的用意:动态注册机制使得拉取模型下的服务器可以按需订阅配置变化事件,而不必在初始化时静态声明。
六、服务器端典型处理流程
综合推送与拉取两种模型,一个现代 LSP 服务器处理配置变更的推荐流程如下:
- 初始化阶段:读取客户端在
initialize中声明的workspace.didChangeConfiguration能力。 - 注册阶段:若能力可用,服务器调用
client/registerCapability,以空选项(或指定section)注册DidChangeConfigurationNotification。 - 通知处理阶段:收到
workspace/didChangeConfiguration时:- 在纯推送模型下:直接解析
params.settings,更新服务器内部状态; - 在拉取模型下:忽略
settings内容,将其仅视为缓存失效信号,随后发起workspace/configuration请求,按需重新拉取所需配置节。
- 在纯推送模型下:直接解析
- 作用域处理:结合
scopeUri语义,对不同资源返回不同配置(例如按文件路径返回 EditorConfig 等作用域配置),未提供的项以null占位。
七、版本演进与元数据佐证
- 该通知自协议早期版本即存在,属配置推送机制的基础消息;
dynamicRegistration字段自3.6.0起为支持拉取模型而引入;- 在 3.18 版本的 didChangeConfiguration.md 中,
DidChangeConfigurationParams.settings的注释更新为 "The actual changed settings."(3.17 版为 "The actual changed settings"),协议内容保持一致; - 结构化元数据 metaModel.json 中完整建模了
DidChangeConfigurationParams(含settings: LSPAny)、DidChangeConfigurationClientCapabilities(含可选dynamicRegistration)以及DidChangeConfigurationRegistrationOptions(含可选section: string | string[])三类类型,是机器可读的规范等价物,可用于代码生成、协议校验与工具链开发。
八、实战要点小结
- 方向不可逆:
workspace/didChangeConfiguration只允许客户端发往服务器,服务器不可反向发送。 - settings 是快照而非增量:客户端发送的是变更后的完整配置,服务器不应假设其与上一次发送内容存在可比较的增量关系。
- LSPAny 意味着零约束:
settings的具体 schema 完全由客户端与服务端协商,服务器应对未知字段保持宽容。 - 推送与拉取可共存:即使使用拉取模型获取配置,也建议保留一个空注册的
didChangeConfiguration通知作为"配置已变化"的失效信号,避免本地缓存过期。 - 能力声明必不可少:客户端必须在
initialize的workspace.didChangeConfiguration中声明能力,服务器才能合法地依赖该通知;支持动态注册的客户端应同时上报dynamicRegistration: true。
- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
相关推荐
深入解析 Biome 的 Markdown 格式化:setext 标题与跨行 HTML 的边界处理(example-59 用例剖析)
深入解析 Biome 的 Markdown 格式化:setext 标题与跨行 HTML 的边界处理(example 59 用例剖析) 导读 :本文以 Biome
开发工具Language Server Protocol 文件删除事件:workspace/didDeleteFiles 通知详解
Language Server Protocol 文件删除事件:workspace/didDeleteFiles 通知详解 导读 workspace/didDe
开发工具Language Server Protocol 文件创建事件通知:workspace/didCreateFiles 消息规范与实现解析
Language Server Protocol 文件创建事件通知:workspace/didCreateFiles 消息规范与实现解析 导读 workspac
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考