ARTICLE DETAIL

资讯详情

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

Univer 文档查找替换:@univerjs/docs-find-replace 插件的实现原理与接入指南

Univer 文档查找替换:@univerjs/docs-find-replace 插件的实现原理与接入指南 Univer 文档查找替换univerjs/docs-find-replace 插件的实现原理与接入指南【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer本文基于 Univer 仓库中univerjs/docs-find-replace包的官方说明及其源码讲解如何在 Univer Docs文字处理实例中接入文档查找与替换功能并深入解析该插件的完整工作链路从插件注册、Provider 能力声明、基于Intl.Segmenter的字面量文本搜索到通过TextX编辑指令完成单点替换与全局替换。读完本文你将掌握该插件的安装注册方式、第一阶段Phase One的功能边界以及搜索/替换在源码层面的关键实现细节。一、插件定位为 Docs 实例提供查找替换能力Univer 的查找替换能力采用「通用框架 业务 Provider」的插件化设计。通用框架位于 find-replace 包中负责状态管理、对话框交互与多 Provider 协调而univerjs/docs-find-replace是为Docs文档实例提供查找替换 Provider 的业务插件。官方 README 对当前阶段的功能边界做了明确说明Phase one searches and replaces literal text in the current document body, including tables. It excludes headers, footers, comments, drawings, regular expressions, formatting search, special characters, result sidebars, presets, and Facade APIs.即第一阶段只在当前文档正文body含表格内文本中做字面量literal文本的查找与替换且范围与能力上有以下明确排除项已支持暂不支持正文文本搜索含表格内文本页眉、页脚中的文本字面量字符串匹配评论comments、绘图drawings区分大小写、全词匹配正则表达式搜索单点替换、全部替换按格式查找formatting search、特殊字符搜索替换后自动重扫、渲染高亮结果侧边栏、预设、Facade API理解这个边界非常重要当你评估该插件是否满足需求时应以「正文内字面量查找替换」为能力上限页眉页脚、批注等内容不在当前搜索范围内。二、安装与注册按照 README 给出的步骤接入分两步1. 安装依赖pnpm add univerjs/docs-find-replace2. 注册插件import { UniverDocsFindReplacePlugin } from univerjs/docs-find-replace; univer.registerPlugin(UniverDocsFindReplacePlugin);从 插件入口 的源码可以看出该插件带有明确的依赖声明与实例类型约束DependentOn(UniverDocsPlugin, UniverRenderEnginePlugin, UniverDocsUIPlugin, UniverFindReplacePlugin) export class UniverDocsFindReplacePlugin extends Plugin { static override type UniverInstanceType.UNIVER_DOC; // ... }这给出了三点使用前提依赖插件UniverDocsPluginuniverjs/docs、UniverDocsUIPluginuniverjs/docs-ui、UniverRenderEnginePluginuniverjs/engine-render、UniverFindReplacePluginuniverjs/find-replace。这些依赖同时列在 package.json 的dependencies中由 pnpm workspace 以workspace:*方式解析实际安装时会一并引入。实例类型type UniverInstanceType.UNIVER_DOC即只面向 Docs 实例生效若你的实例是 Sheets应使用univerjs/sheets-find-replace对应的能力。配置注入构造函数会将传入的PartialIUniverDocsFindReplaceConfig与defaultPluginConfig合并后写入配置键docs-find-replace.configDOCS_FIND_REPLACE_PLUGIN_CONFIG_KEY。当前该配置接口为空接口见 config.ts也就是说现阶段插件没有对外暴露可自定义的运行时参数——功能行为固定无配置项可调。插件生命周期中onStarting()阶段向 Injector 注册DocsFindReplaceProvider与DocsFindReplaceControlleronSteady()阶段主动获取 Controller 以完成各项装配见下文。三、装配链路Controller 完成 Provider 注册与菜单挂载DocsFindReplaceController 的构造函数是整个插件的装配中枢代码只有四行核心逻辑this.disposeWithMe(findReplaceService.registerFindReplaceProvider(provider)); this.disposeWithMe(commandService.registerCommand(DocsReplaceCommand)); menuManagerService.mergeMenu(menuSchema);分别对应三件事把DocsFindReplaceProvider注册进通用查找替换服务IFindReplaceService。通用服务内部通过_syncActiveProvider()根据「当前聚焦的单元focused unit」在所有已注册 Provider 中找到isSupported(unit)为真的那一个作为 active provider见 find-replace.service.ts。文档聚焦时docs Provider 成为活跃方切到其他单元类型时会自动切换若对话框处于打开状态则先terminate()当前会话。注册替换命令DocsReplaceCommanddocs.command.replace供 Provider 在执行替换时调用。合并菜单 schema为文档工具栏挂载查找入口。菜单工厂 生成一个按钮项复用OpenFindDialogOperation的 id并且只有在FOCUSING_DOC上下文为真即文档处于聚焦状态时按钮才可点击——这与插件只在 Docs 实例中生效的设计相互呼应。四、Provider 与能力声明docs-find-replace 如何接入通用框架DocsFindReplaceProvider 实现了通用框架定义的IFindReplaceProvider接口其关键成员是capabilities声明readonly capabilities { caseSensitive: true, matchesTheWholeWord: true, matchesTheWholeCell: false, findDirection: false, findScope: false, findBy: false, };capabilities是一个「能力位」对象通用框架的对话框 UI 会订阅providerCapabilities$并据此显示/隐藏对应选项。从源码结构看Docs 查找替换当前支持区分大小写和全词匹配两个选项「匹配整个单元格」单元格是表格概念、查找方向、查找范围、按值/公式查找等选项不适用于文档正文故声明为false。isSupported()明确只支持 Docs 实例isSupported(unit: UnitModel): boolean { return unit.type UniverInstanceType.UNIVER_DOC; }find()方法的执行路径是terminate()清理上一轮遗留的查找模型保证一次会话只有一个活跃 model通过IUniverInstanceService.getCurrentUnitOfType(DocumentDataModel, UNIVER_DOC)拿到当前文档的数据模型拿不到则返回空数组——这印证了 README 中「current document body」的表述搜索范围锁定在当前活动文档通过IRenderManagerService.getRenderUnitById(doc.getUnitId())?.with(DocSkeletonManagerService)获取文档骨架管理器skeleton这是把字符偏移换算为渲染层几何信息的基础用 Injector 创建DocsFindModel并调用start(query)把结果 model 数组返回给通用服务。五、搜索核心findDocRanges 的字面量匹配与全词过滤真正执行搜索的是 utils.ts 中的findDocRanges()它是「搜索」与「替换」两个场景共用的底层函数export function findDocRanges( body: IDocumentBody, query: IFindQuery, disabled: boolean, resolved: IResolvedDocText { /* 默认用 body.dataStream 自行构造 */ } ): IDocFindRange[] { if (!query.findString) return []; const expression regexp.createLiteralRegExp(query.findString, query.caseSensitive ? gu : giu); const wordRanges query.matchesTheWholeWord ? new Set(Array.from(new Intl.Segmenter(undefined, { granularity: word }).segment(resolved.text)) .filter((item) item.isWordLike) .map((item) ${item.index}:${item.index item.segment.length})) : null; return Array.from(resolved.text.matchAll(expression)) .filter((match) wordRanges?.has(${match.index}:${match.index match[0].length}) ?? true) .flatMap((match) { /* 计算 startOffset/endOffset/replaceable */ }); }实现上有四个值得注意的设计点字面量匹配而非用户自定义正则regexp.createLiteralRegExp会对查找串做转义后构造正则flags 根据caseSensitive选用gu或giu。这就是 README 声明「excludes regular expressions」的直接代码依据——用户输入的.、*等元字符被当作普通字符处理。全词匹配基于Intl.Segmenter当勾选全词匹配时先用Intl.Segmenterword 粒度把解析后的文档文本切成词构造index:length形式的词边界集合再过滤正则命中项。由于Intl.Segmenter是 Unicode 感知的分词器这一实现对中文、日文等无空格语言的「整词」判断也有合理依据而不只是简单的词边界断言。匹配的是「解析后文本」函数接收IResolvedDocText解析后的纯文本 每个字符到 body 偏移的映射而不是直接在原始dataStream其中混有控制字符上匹配。DocsFindModel 在调用时传入this._textResolverService.resolve(this.unitId, body)保证匹配到的是用户可见文本并能把命中区间映射回 body 的字符偏移。replaceable标志每个命中区间带有replaceable布尔值判定条件是「文档未禁用编辑!disabled且该区间不整体覆盖某个wholeEntity的customRanges如整块嵌入式对象且区间内所有字符均可替换」。命中但不可替换的区间仍会被高亮显示只是替换操作会跳过它们——这解释了为什么全部替换可能返回{ success, failure }中failure 0。六、替换核心DocsReplaceCommand 如何用编辑指令完成写入替换不走字符串直接改写而是复用 Univer 的富文本编辑管线。DocsReplaceCommand 的参数与流程如下export interface IDocsReplaceCommandParams { unitId: string; query: IFindQuery; replaceString: string; range?: ITextRange; // 指定 range 时只替换该区间单点替换 }handler 的处理步骤重新调用findDocRanges()计算命中集若带range参数则过滤出与该区间完全一致的命中用于「替换当前项」否则取全部命中过滤出replaceable的候选统计失败数对每个可替换命中用doc.sliceBody(start, end)取出被替换片段的样式信息构造插入片段——若原片段带textRuns字符格式则把第一条格式 run 保留到整个替换串上若带customRanges如链接同样保留第一条并重新对齐偏移。这意味着替换会沿用被替换文本首字符的格式而非插入裸文本用BuildTextUtils.selection.delete生成删除 插入的编辑操作序列TextX再经JSONX.editOp序列化为 ops 路径下的 actions以syncExecuteCommand(RichTextEditingMutation.id, ...)同步执行富文本编辑 mutation让编辑历史undo/redo与数据模型保持一致返回{ success, failure }统计结果IReplaceAllResult类型在通用框架中定义。从源码结构看「单点替换」与「全部替换」共用同一条命令通道DocsFindModel.replace() 传入当前命中的rangereplaceAll()不传range以替换全部可替换命中。七、DocsFindModel会话状态、高亮渲染与自动重扫DocsFindModel 继承自通用框架的抽象类FindModel承载一次查找会话的全部状态职责包括匹配定位与导航。moveToNextMatch/moveToPreviousMatch支持IFindMoveParams中的loop循环查找、stayIfOnMatch选区已在命中上时保持不动、ignoreSelection跨单元导航时忽略选区等参数首次定位优先以当前选区为起点向后找。激活命中项时通过DocSelectionManagerService.replaceDocRanges选中该区间并调用DocBackScrollRenderController.scrollToRange滚动到命中位置随后触发activelyChangingMatch$通用服务据此更新对话框上的「第 N / M 个」计数。渲染层高亮。_refreshHighlights()通过骨架skeleton把字符偏移换算为渲染坐标用主题色yellow.400生成两种样式普通命中 30% 透明度填充、当前命中 65% 透明度填充叠加在选区样式上呈现「普通命中浅黄、当前命中深黄」的视觉区分skeleton 变化如布局、缩放时会重新绘制高亮dispose()时统一销毁避免渲染层残留。内容变更自动重扫。构造函数中订阅了两类信号——本单元执行了RichTextEditingMutation、以及DocTextResolverService.textChanged$文本解析变更——经debounceTime(220)防抖后触发_scan(true)重新计算全部命中。替换操作本身就是一次RichTextEditingMutation因此每次替换后命中列表会自动刷新且会尽量把光标位置保持在原当前命中的附近按原startOffset重新定位这是「替换下一个」连续操作体验平滑的关键。八、端到端时序总览把前面各部分串起来一次完整的「查找 全部替换」流程如下用户聚焦文档实例FOCUSING_DOC上下文变为真文档工具栏上的查找按钮OpenFindDialogOperation可用点击后调用IFindReplaceService.start()服务基于当前 active provider 创建FindReplaceModel会话对象并重置状态输入查找串触发changeInputtingFindString→changeFindString通用服务以 200ms 节流判定「状态更新应触发重搜」调用provider.find(query)DocsFindReplaceProvider.find()创建DocsFindModelstart()内部_scan()调用findDocRanges()计算命中并渲染高亮用户点击「全部替换」时FindReplaceModel.replaceAll()聚合各 FindModel 的结果DocsFindModel发起DocsReplaceCommand不带range命令内重建命中集、生成编辑 ops、同步执行RichTextEditingMutationmutation 触发第 4 步的防抖重扫命中列表与高亮实时收敛对话框计数同步更新。九、适用前提与限制小结结合 package.json 与源码使用该插件需注意包版本当前仓库中该包版本为1.0.0-beta.2处于 beta 阶段运行前提Docs 实例需同时装配univerjs/docs、univerjs/docs-ui、univerjs/engine-render、univerjs/find-replace由插件DependentOn与依赖声明保证react为 peerDependency支持 16.919搜索范围仅当前聚焦文档的正文含表格页眉/页脚、批注、绘图对象不进入搜索匹配能力字面量匹配支持区分大小写与全词匹配不支持正则、格式查找、特殊字符可替换性文档处于只读disabled状态或命中覆盖整体嵌入对象时命中只高亮、不可替换全部替换结果中的failure计数会反映这类情况无运行时配置IUniverDocsFindReplaceConfig目前为空接口注册时无需也不应传入行为参数。该插件的测试覆盖也印证了上述链路findDocRanges的控制器层测试utils.spec.ts、替换命令测试docs-replace.command.spec.ts、Provider 测试docs-find-replace.provider.spec.ts与 FindModel 测试docs-find.model.spec.ts分别覆盖了搜索、替换、会话与高亮各环节可作为理解各模块行为的补充参照。【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表