ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LSP WorkspaceEdit 深度解析:Language Server Protocol 中跨文件编辑与资源操作协议详解

LSP WorkspaceEdit 深度解析:Language Server Protocol 中跨文件编辑与资源操作协议详解 开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载导读WorkspaceEdit工作区编辑是 Language Server ProtocolLSP中用于描述一次性修改多个资源的核心数据结构是重命名Rename、代码重构Code Action、批量格式化等能力向编辑器提交修改的载体。本文以 LSP 3.17 规范的 workspaceEdit.md 为主体完整梳理WorkspaceEdit的类型定义、changes与documentChanges两种承载方式、3.13 起引入的文件/文件夹资源操作创建、重命名、删除、3.16 起引入的 Change Annotation 机制以及配套的客户端能力协商WorkspaceEditClientCapabilities、失败处理策略FailureHandlingKind和workspace/applyEdit请求闭环。读完本文你将能准确理解 LSP 工作区编辑的完整数据契约并能为自己的语言服务端正确实现多文件批量编辑。一、什么是 WorkspaceEdit一个编辑多个资源1.1 核心定义根据 workspaceEdit.md 的规范原文A workspace edit represents changes to many resources managed in the workspace.一个WorkspaceEdit代表对工作区中多个资源的变更集合。它之所以重要是因为语言服务端Language Server的很多操作天然是跨文件的一次重命名符号可能同时需要修改 5 个文件中的 20 处引用一次提取方法重构可能既要改原文件又要新建一个新文件。这些改动应当作为一个整体编辑交给客户端统一应用并可统一撤销而不是逐条发送多个零散请求。1.2 两条编辑通道changes与documentChangesWorkspaceEdit提供了两条承载编辑内容的通道规范明确要求二选一changes一个以文档 URI 为键、以TextEdit[]为值的映射用于描述对现有资源的文本修改documentChanges一个更精细的数组既可以只包含TextDocumentEdit面向特定文档版本的编辑也可以在客户端支持时混入CreateFile/RenameFile/DeleteFile资源操作。规范对两者优先级的表述是If the client can handle versioned document edits and ifdocumentChangesare present, the latter are preferred overchanges.即当客户端具备处理版本化文档编辑的能力workspace.workspaceEdit.documentChanges能力且documentChanges存在时客户端优先使用documentChanges。1.3 完整接口定义LSP 3.17export interface WorkspaceEdit { /** * Holds changes to existing resources. */ changes?: { [uri: DocumentUri]: TextEdit[]; }; /** * Depending on the client capability * workspace.workspaceEdit.resourceOperations document changes are either * an array of TextDocumentEdits to express changes to n different text * documents where each text document edit addresses a specific version of * a text document. Or it can contain above TextDocumentEdits mixed with * create, rename and delete file / folder operations. * * Whether a client supports versioned document edits is expressed via * workspace.workspaceEdit.documentChanges client capability. * * If a client neither supports documentChanges nor * workspace.workspaceEdit.resourceOperations then only plain TextEdits * using the changes property are supported. */ documentChanges?: ( TextDocumentEdit[] | (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[] ); /** * A map of change annotations that can be referenced in * AnnotatedTextEdits or create, rename and delete file / folder * operations. * * Whether clients honor this property depends on the client capability * workspace.changeAnnotationSupport. * * since 3.16.0 */ changeAnnotations?: { [id: string /* ChangeAnnotationIdentifier */]: ChangeAnnotation; }; }三个字段的职责可以概括为一张表字段类型职责依赖的客户端能力changes{ [uri: DocumentUri]: TextEdit[] }对现有资源的纯文本编辑按文档 URI 分组最基础能力任何客户端都需支持documentChangesTextDocumentEdit[]或(TextDocumentEdit \| CreateFile \| RenameFile \| DeleteFile)[]版本化文档编辑可混入文件资源操作workspace.workspaceEdit.documentChanges、workspace.workspaceEdit.resourceOperationschangeAnnotations{ [id: string]: ChangeAnnotation }变更注解表供编辑与资源操作按 ID 引用3.16workspace.changeAnnotationSupport需要特别强调的是文档中指出的最保守的兼容情形If a client neither supportsdocumentChangesnorworkspace.workspaceEdit.resourceOperationsthen only plainTextEdits using thechangesproperty are supported.也就是说当客户端既不支持版本化文档编辑、也不支持资源操作时服务端只能退回到changes属性发送纯TextEdit。二、文件资源操作create / rename / delete3.132.1 操作顺序与失败语义自3.13.0起WorkspaceEdit可以包含资源操作创建、删除或重命名文件/文件夹。规范给出了两个重要的行为约束顺序即语义客户端必须按照服务端提供的顺序执行资源操作前后依赖示例一个编辑可以合法地由创建文件 a.txt和向 a.txt 插入文本的文档编辑组成而像删除 a.txt再向 a.txt 插入文本这样非法的序列会导致操作失败。关于失败后的恢复方式规范明确由客户端能力workspace.workspaceEdit.failureHandling描述详见本文第四节。2.2 三种资源操作字面量文件资源操作的完整定义位于 resourceChanges.md。规范特别提示命名虽然写的是file但这些操作同样适用于文件夹与 LSP 中文件监视器能同时监视文件与文件夹的命名习惯一致。CreateFile创建export interface CreateFileOptions { /** * Overwrite existing file. Overwrite wins over ignoreIfExists */ overwrite?: boolean; /** * Ignore if exists. */ ignoreIfExists?: boolean; } export interface CreateFile { kind: create; uri: DocumentUri; options?: CreateFileOptions; annotationId?: ChangeAnnotationIdentifier; // since 3.16.0 }RenameFile重命名export interface RenameFileOptions { /** * Overwrite target if existing. Overwrite wins over ignoreIfExists */ overwrite?: boolean; /** * Ignores if target exists. */ ignoreIfExists?: boolean; } export interface RenameFile { kind: rename; oldUri: DocumentUri; newUri: DocumentUri; options?: RenameFileOptions; annotationId?: ChangeAnnotationIdentifier; // since 3.16.0 }DeleteFile删除export interface DeleteFileOptions { /** * Delete the content recursively if a folder is denoted. */ recursive?: boolean; /** * Ignore the operation if the file doesnt exist. */ ignoreIfNotExists?: boolean; } export interface DeleteFile { kind: delete; uri: DocumentUri; options?: DeleteFileOptions; annotationId?: ChangeAnnotationIdentifier; // since 3.16.0 }三种操作的选项参数语义归纳操作关键选项语义createoverwrite/ignoreIfExists目标已存在时是否覆盖overwrite优先于ignoreIfExists/ 已存在时是否忽略renameoverwrite/ignoreIfExists新位置已存在时是否覆盖 / 已存在时是否忽略deleterecursive/ignoreIfNotExists目标为文件夹时是否递归删除 / 文件不存在时是否忽略每个资源操作字面量都带一个判别字段kindcreate | rename | delete这正是ResourceOperationKind联合类型的取值见本文第三节也是 JSON-RPC 消息中判别联合类型discriminated union的标准写法。三、ResourceOperationKind 与 FailureHandlingKind3.1 ResourceOperationKind客户端支持哪些资源操作/** * The kind of resource operations supported by the client. */ export type ResourceOperationKind create | rename | delete; export namespace ResourceOperationKind { /** * Supports creating new files and folders. */ export const Create: ResourceOperationKind create; /** * Supports renaming existing files and folders. */ export const Rename: ResourceOperationKind rename; /** * Supports deleting existing files and folders. */ export const Delete: ResourceOperationKind delete; }ResourceOperationKind只有三种取值create、rename、delete。规范对客户端的建议是Clients should at least support create, rename and delete files and folders.即客户端至少应支持创建、重命名和删除文件与文件夹三种操作。3.2 FailureHandlingKind应用失败时的四种处理策略export type FailureHandlingKind abort | transactional | undo | textOnlyTransactional; export namespace FailureHandlingKind { /** * Applying the workspace change is simply aborted if one of the changes * provided fails. All operations executed before the failing operation * stay executed. */ export const Abort: FailureHandlingKind abort; /** * All operations are executed transactional. That means they either all * succeed or no changes at all are applied to the workspace. */ export const Transactional: FailureHandlingKind transactional; /** * If the workspace edit contains only textual file changes they are * executed transactional. If resource changes (create, rename or delete * file) are part of the change the failure handling strategy is abort. */ export const TextOnlyTransactional: FailureHandlingKind textOnlyTransactional; /** * The client tries to undo the operations already executed. But there is no * guarantee that this is succeeding. */ export const Undo: FailureHandlingKind undo; }四种失败处理策略对比如下取值策略描述适用场景abort某个变更失败则整体中止失败之前已执行的操作保留轻量客户端无事务保障transactional所有操作按事务执行要么全部成功要么一个都不应用强一致性要求的客户端textOnlyTransactional仅包含文本编辑时按事务执行一旦混入资源操作则退化为 abort能保证文本事务、但无法回滚文件系统操作的客户端undo尽力回滚已执行的操作不保证一定成功提供撤销栈的客户端这四种策略是服务端判断一次跨文件编辑提交后能获得什么保证的关键依据例如重命名符号时若混入文件重命名遇到textOnlyTransactional客户端就必须意识到资源操作部分并不具备事务保证。四、WorkspaceEditClientCapabilities能力协商4.1 能力字段的演化规范指出工作区编辑的能力随着时间不断演化客户端通过workspace.workspaceEdit属性路径上报自身支持程度。3.13 版本新增了ResourceOperationKind、FailureHandlingKind以及resourceOperations、failureHandling能力3.16 又新增了normalizesLineEndings与changeAnnotationSupport。export interface WorkspaceEditClientCapabilities { /** * The client supports versioned document changes in WorkspaceEdits */ documentChanges?: boolean; /** * The resource operations the client supports. Clients should at least * support create, rename and delete files and folders. * * since 3.13.0 */ resourceOperations?: ResourceOperationKind[]; /** * The failure handling strategy of a client if applying the workspace edit * fails. * * since 3.13.0 */ failureHandling?: FailureHandlingKind; /** * Whether the client normalizes line endings to the client specific * setting. * If set to true the client will normalize line ending characters * in a workspace edit to the client specific new line character(s). * * since 3.16.0 */ normalizesLineEndings?: boolean; /** * Whether the client in general supports change annotations on text edits, * create file, rename file and delete file changes. * * since 3.16.0 */ changeAnnotationSupport?: { /** * Whether the client groups edits with equal labels into tree nodes, * for instance all edits labelled with Changes in Strings would * be a tree node. */ groupsOnLabel?: boolean; }; }各字段含义汇总能力字段类型含义documentChangesboolean是否支持WorkspaceEdit中的版本化文档变更resourceOperationsResourceOperationKind[]支持的资源操作列表create/rename/deletefailureHandlingFailureHandlingKind应用编辑失败时采用的恢复策略normalizesLineEndingsboolean为true时客户端会把编辑中的换行符归一化为客户端特有的换行字符changeAnnotationSupport{ groupsOnLabel?: boolean }是否支持变更注解groupsOnLabel表示是否将相同标签的编辑分组为树节点例如所有标记为 Changes in Strings 的编辑聚合成一个树节点这些能力定义在 workspaceEdit.md并作为Workspace能力的一部分在初始化握手阶段由客户端上报参见 initialize.md 中WorkspaceClientCapabilities的workspaceEdit?: WorkspaceEditClientCapabilities字段。服务端应依据这些能力裁剪自己的行为未上报documentChanges时不要发送版本化文档编辑resourceOperations为空时不要发送文件资源操作未上报changeAnnotationSupport时不要发送AnnotatedTextEdit字面量见 textDocumentEdit.md 的明确要求。4.2 端到端应用workspace/applyEdit 请求WorkspaceEdit本身只是数据结构真正动手改文件发生在服务端向客户端发送workspace/applyEdit请求时定义见 applyEdit.md方法名workspace/applyEdit服务端 → 客户端箭头符号:arrow_right_hook:表示由服务端发起的请求能力字段workspace.applyEditboolean请求参数ApplyWorkspaceEditParamsexport interface ApplyWorkspaceEditParams { /** * An optional label of the workspace edit. This label is * presented in the user interface for example on an undo * stack to undo the workspace edit. */ label?: string; /** * The edits to apply. */ edit: WorkspaceEdit; }响应结果ApplyWorkspaceEditResultexport interface ApplyWorkspaceEditResult { /** * Indicates whether the edit was applied or not. */ applied: boolean; /** * An optional textual description for why the edit was not applied. * This may be used by the server for diagnostic logging or to provide * a suitable error for a request that triggered the edit. */ failureReason?: string; /** * Depending on the clients failure handling strategy failedChange * might contain the index of the change that failed. This property is * only available if the client signals a failureHandling strategy * in its client capabilities. */ failedChange?: uinteger; }请求中的label会展示在用户界面中例如撤销栈上的名称这是给用户这条编辑做了什么的可读标识响应中的applied是编辑是否被应用的硬性结果failureReason供服务端记录诊断日志而failedChange仅在客户端上报了failureHandling能力时可用给出失败变更的索引服务端可据此精确定位是哪一步出了问题——这与第四节failureHandling的语义直接挂钩。在 LSP 3.17 仓库中WorkspaceEdit及相关类型还被 rename.md、executeCommand.md、codeAction.md 以及willCreateFiles/willRenameFiles/willDeleteFiles见 workspace/willCreateFiles.md等文档反复引用可见它是 LSP 中横跨重构、命令执行、文件事件等模块的通用修改载体。五、TextDocumentEdit 与版本化文档编辑5.1 为什么需要版本化changes通道以 URI 为键但不知道文档版本。若文档在服务端计算编辑之后、客户端应用之前又被用户改动直接套用旧文本位置可能产生冲突。为此documentChanges通道引入TextDocumentEdit——它通过OptionalVersionedTextDocumentIdentifier携带文档版本信息让客户端在应用编辑前可以校验版本是否匹配。定义见 textDocumentEdit.mdexport interface TextDocumentEdit { /** * The text document to change. */ textDocument: OptionalVersionedTextDocumentIdentifier; /** * The edits to be applied. * * since 3.16.0 - support for AnnotatedTextEdit. This is guarded by the * client capability workspace.workspaceEdit.changeAnnotationSupport */ edits: (TextEdit | AnnotatedTextEdit)[]; }5.2 OptionalVersionedTextDocumentIdentifier 的版本语义OptionalVersionedTextDocumentIdentifier定义于 versionedTextDocumentIdentifier.md其version字段类型为integer | null当服务端 → 客户端传递该标识符、而目标文件并未在编辑器中打开服务端未收到 open 通知时服务端可发送null表示版本已知、磁盘内容为准依据文档内容所有权规则版本号在每次变更含 undo/redo后递增不必连续。5.3 无需排序但不得重叠TextDocumentEdit有一个独特的便利性它描述了对某个文档从版本 Si 到 Si1 的所有变更因此the creator of aTextDocumentEditdoesnt need to sort the array of edits or do any kind of ordering. However the edits must be non overlapping.即服务端无需对编辑数组排序客户端自会按版本推进应用但编辑区间之间不得重叠——这是服务端实现时必须自己保证的不变量。六、Change Annotation3.16 起的变更分组与说明机制6.1 动机给编辑贴上可读标签跨文件重构往往产生几十个编辑用户希望知道每个编辑属于哪类改动、能否安全应用。LSP 3.16 引入了Change Annotation变更注解机制让编辑可以被标注人类可读的描述 是否需要确认。相关定义位于 textEdit.md/** * Additional information that describes document changes. * * since 3.16.0 */ export interface ChangeAnnotation { /** * A human-readable string describing the actual change. The string * is rendered prominent in the user interface. */ label: string; /** * A flag which indicates that user confirmation is needed * before applying the change. */ needsConfirmation?: boolean; /** * A human-readable string which is rendered less prominent in * the user interface. */ description?: string; }label是必须的且会在 UI 中醒目渲染needsConfirmation标记该变更应用前需要用户确认description是次要渲染的可选补充说明。6.2 为什么用标识符而不是内联对象协议设计上编辑或资源操作引用的是注解标识符ChangeAnnotationIdentifier即string而不是注解字面量本身This allows servers to use the identical annotation across multiple edits or resource operations which then allows clients to group the operations under that change annotation.好处显而易见同一个注解可被多个编辑/资源操作共享引用客户端从而能把这些操作按注解分组例如把标记为 Changes in Strings 的所有编辑聚合为一个树节点展示对应能力groupsOnLabel。export type ChangeAnnotationIdentifier string; /** * A special text edit with an additional change annotation. * * since 3.16.0. */ export interface AnnotatedTextEdit extends TextEdit { /** * The actual annotation identifier. */ annotationId: ChangeAnnotationIdentifier; }6.3 注解的存放位置与守卫条件注解本体存放在WorkspaceEdit.changeAnnotations映射中见第一节接口编辑与资源操作只通过annotationId引用。两个关键守卫条件能力守卫ChangeAnnotation、AnnotatedTextEdit以及CreateFile/RenameFile/DeleteFile上的annotationId都受workspace.workspaceEdit.changeAnnotationSupport能力保护——客户端未上报该能力时服务端不应发送AnnotatedTextEdit字面量textDocumentEdit.md 对此有明确说明版本边界该机制自 3.16.0 引入3.15 及更早版本的客户端无法理解服务端必须结合初始化握手时协商的协议版本与能力做降级处理。七、实践建议服务端如何构造一个正确的 WorkspaceEdit综合以上规范要点服务端构造WorkspaceEdit时可遵循以下检查清单先看能力再选通道读初始化时客户端上报的workspace.workspaceEdit。支持documentChanges就优先用TextDocumentEdit[]支持resourceOperations才能混入CreateFile/RenameFile/DeleteFile两者皆不支持时退回到纯changes映射。版本化编辑要带上版本TextDocumentEdit.textDocument使用OptionalVersionedTextDocumentIdentifier文档未打开时可传version: null编辑区间不得重叠无需排序。资源操作讲究顺序混入文件操作时按先创建、后引用、再删除的依赖顺序排列例如先create新文件再TextDocumentEdit向新文件写入内容删除后再写同一文件属于非法序列。善用标签与注解需要让用户看清改动来源时使用labelchangeAnnotationsannotationId受changeAnnotationSupport守卫对需要确认的破坏性改动设置needsConfirmation: true。关注失败反馈通过workspace/applyEdit的响应检查applied结合客户端failureHandling策略理解failedChange的语义并利用failureReason做诊断日志。这些类型与请求在 LSP 3.17 规范仓库中形成了完整的闭环定义侧在 types/workspaceEdit.md 与 types/resourceChanges.md、types/textDocumentEdit.md、types/textEdit.md应用侧在 workspace/applyEdit.md能力协商入口在 general/initialize.md并被 language/rename.md 等请求文档作为结果类型引用。服务端开发者按这一链路实现即可交付可靠、可回退、可分组展示的跨文件编辑体验。赞分享开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载相关推荐微服务的事件驱动数据管理从分布式数据一致性问题到事件源架构doocs/advanced-java微服务的事件驱动数据管理从分布式数据一致性问题到事件源架构doocs/advanced java 本篇技术指南围绕微服务架构下分布式数据管理这一核心痛开发工具Jira Python 库测试策略单元测试与集成测试最佳实践Jira Python 库测试策略单元测试与集成测试最佳实践 Jira Python 库是一个功能强大的工具为开发者提供了与 Jira 系统交互的便捷接口。后端Language Server Protocol (LSP) 开源项目教程Language Server Protocol LSP 开源项目教程 项目的目录结构及介绍 Language Server Protocol LSP 项目的目开发工具上一篇WPF UI完整指南打造现代化Fluent风格桌面应用的终极解决方案下一篇Earthly 与 AWS ECR 集成实战推送与拉取镜像的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表