Uninote
Uninote
用户根目录
brdr
common
programming
docs
tests
后端试题
问题讨论

快捷键设置:MVP

查看与编辑

入口与列表

  • 入口:设置 → 快捷键设置;旁边的「热键设置」直接打开热键配置。
  • 页面通过「快捷键 / 热键」单选框切换,两类列表分别配置,切换时清空搜索与冲突筛选。
  • 快捷键写入 User/keybindings.json,热键写入 User/hotkeybindings.json;JSON 编辑、默认 JSON 和定位文件均跟随当前类型。
  • 两类文件分别监听与保存,损坏配置的修复只影响当前类型;不自动迁移旧混合配置。
  • 文件内不再进行热键/快捷键分类校验;无关命令或移除规则不会阻止其他有效配置加载,JSON 格式、键位和 When 校验仍保留。
  • 默认也可用 Ctrl+K Ctrl+S 打开;macOS 使用 Cmd+K Cmd+S。
  • 页面按绑定逐行显示命令、命令 ID、快捷键、When、Source 和操作;命令 ID 显示在命令名下。
  • 表头保持单行,不自动换行;列内容仍可换行,宽度不足时表格区域可横向滚动。
  • 命令面板每个条目最右侧的设置图标可直接打开本页;也可选中命令后按 Ctrl+Enter(macOS 为 Cmd+Enter),并筛选定位到对应命令的全部快捷键配置。
  • Source 的 system 表示默认规则,user 表示用户规则。

编辑、添加与恢复

  • 同一命令支持多条绑定;全部删除后仍显示「未绑定」,可以再次添加或恢复默认。
  • 「修改」支持同时修改按键和 When。
  • 「添加」为命令增加独立绑定。
  • 「删除」仅删除当前绑定;默认规则通过用户移除记录屏蔽,不修改默认数据。
  • 「恢复默认」清除该命令的全部用户绑定与移除记录。
  • 列表第一条当前显示记录支持操作快捷键,搜索框或其他列表控件获得焦点时均可使用。
    • 修改:Alt+E;删除:Alt+D;添加:Alt+A;恢复默认:Alt+R。
    • Windows、macOS、Linux 均使用 Alt(macOS 键盘上的 Option);第一条记录的按钮提示同步显示这些快捷键。
    • 搜索、冲突筛选、查看平台或配置类型切换后,操作作用于更新后的第一条记录。
    • 列表为空或第一条记录没有对应按钮时不执行操作,不跳到后续记录。
    • 删除、恢复默认或其他配置更新重建列表时,若焦点原本位于列表内,会回到搜索框;可直接连续使用操作快捷键,无需再次点击。
    • 长按重复事件、普通输入法组合输入和额外修饰键不触发操作;macOS Option 产生的重音组合按物理按键识别。
    • 编辑、录制及 JSON 子对话框内不触发列表操作。
    • macOS 打开设置期间,这四组 Alt 按键交给页面,避免被原生菜单截获;其他菜单快捷键和关闭设置后的行为保持原样。
  • 修改立即生效,不需要点击外层「保存设置」。
  • 外层设置的取消按钮不会撤销已经保存的快捷键。
  • 快捷键设置、编辑和 JSON 配置对话框保持固定高度(受窗口高度限制),内容过多时在内部滚动;搜索结果数量和错误提示不会撑高对话框。
  • 关闭快捷键编辑对话框后恢复上一级页面原先聚焦的元素;保存重建列表后恢复到对应快捷键行的同一操作按钮,若该行不再符合筛选条件则聚焦搜索框。
  • 保存失败会显示错误,并保留原来的有效配置。

录制与手动输入

  • 每次添加或修改快捷键默认进入按键录制;可点击「切换为手动输入」直接编辑文本,也可切回录制,两种方式保留已输入的快捷键。
  • 不显示「重新录制」按钮;超时或收满两段后自动等待下一组按键,保留上一组结果供保存,新一组录制完成后替换它。
  • 录制/手动输入模式同时作用于通用键位和 Windows、macOS、Linux 平台覆盖;录制模式下聚焦哪个键位输入框,按键就写入哪个字段,手动模式下四个字段均可直接编辑。
  • 离开当前键位输入框时保留已捕获的第一段并结束录制;When 始终可手动编辑,再次聚焦任一键位输入框会开始录制。
  • Tab 用于离开录制框,需要绑定 Tab 时可切换为手动输入。
  • 每组录制最多两段;第一段后停顿 1.8 秒完成,第二段按下后立即完成,并自动开始下一次录制。
  • 录制时 Esc 取消;Enter 仅在第二段作为快捷键录入,第一段按 Enter 会保存当前配置并关闭编辑对话框(校验或保存失败时保留对话框和草稿)。
  • 超时或收满两段后已自动进入下一组的第一段,此时按 Enter 同样保存当前结果;也可点击「保存」。
  • 「切换为手动输入」旁提供平台转换按钮,录制和手动输入模式均可使用。
    • Windows 将当前 Windows 键位的 Control/Ctrl 转为 Command/Cmd,填入 macOS 平台覆盖。
    • macOS 将当前 macOS 键位的 Command/Cmd 转为 Control/Ctrl,填入 Windows 平台覆盖。
    • 当前平台覆盖留空时读取通用键位;每段快捷键均转换,其他按键和修饰键保留。
    • 转换保留来源字段,覆盖目标平台草稿并取消目标的禁用勾选;点击保存后生效。
    • 当前平台已禁用、来源为空或格式无效时显示错误并保留目标草稿;Linux 不显示此按钮。

平台覆盖

  • key 是通用键位,win、mac、linux 分别覆盖 Windows、macOS、Linux。
  • 省略平台字段表示继承通用键位;JSON 空字符串表示在该平台禁用。
  • 通用键位可以留空,只填写特定平台的快捷键;通用与平台覆盖均为空的平台不绑定。
    • 保存时至少需要一个有效的通用或平台快捷键;全部为空或禁用不能保存。
    • JSON 使用 "key": "" 表示没有通用键位,例如 {"command":"file.save","key":"","mac":"cmd+s"} 只在 macOS 生效。
  • 编辑界面提供三平台输入框及「禁用此平台」复选框;点击复选框或文字均可切换勾选状态。
  • 添加绑定或修改内置默认绑定时,平台覆盖默认留空且不勾选禁用;保存后留空的平台继承通用快捷键。
  • 修改已保存的用户绑定或插件绑定时回显已有平台覆盖和禁用状态;已有平台覆盖需要单独修改,修改通用键位不会覆盖它们。
  • 顶部「查看平台」仅改变列表和冲突预览;实际分发始终使用当前操作系统。
    • 标签与下拉框保持同一行,下拉框按选项内容确定宽度,完整显示平台名称。
  • 平台禁用的绑定仍可在列表中修改、删除或恢复默认。

搜索与冲突视图

  • 快捷键设置搜索复用 Quick Open 的命令匹配算法,支持中文原文、无声调全拼、拼音首字母、命令 ID、快捷键、生效条件和来源文本。例如,“新建文档”可用 xinjianwendang 或 xjwd 搜索。
  • 未指定字段时,双引号包裹的内容仅匹配快捷键;例如 "ctrl d" 匹配 Ctrl+D,"ctrl+k enter" 匹配两段快捷键。
  • 高级过滤器支持 @command:、@keybinding:、@when:、@source: 和 @conflict。
  • 多个条件同时匹配;带空格的值用引号,例如 @source:user @keybinding:"ctrl+k enter"。
  • @source:plugin 查看所有插件规则;@source:plugin:demo 查看指定插件。
  • 点击「仅查看冲突」进入冲突视图,可叠加搜索和平台选择;再次点击返回全部绑定。
  • 冲突行列出对方命令 ID、同键或 Chord 前缀关系、When 和来源,可直接修改或删除。
  • 冲突视图沿用下文的保守条件重叠判断,不阻止保存。

默认命令

下表记录本次 MVP 文档收录的默认快捷键命令,并非命令面板的全部命令。命令面板还可显示撤销、重做、缩放及插件命令等;表中的最近标签切换命令不会在命令面板中显示。

下表的主修饰键在 Windows/Linux 为 Ctrl,在 macOS 为 Cmd。

命令 ID 功能 默认快捷键
file.new 新建文档 主修饰键+N
file.open 打开文件 主修饰键+O
file.quickOpen 快速打开 主修饰键+P
workspace.open 打开笔记库 主修饰键+K 主修饰键+O
workspace.openRecent 打开最近的笔记库 Ctrl+R(所有平台)
workspace.openInVSCode 用 VS Code 打开当前仓库 Alt+Shift+V
workspace.refresh 刷新笔记库 主修饰键+K R
workspace.expandAllDirectories 全部展开文件夹 主修饰键+K E
workspace.collapseAllDirectories 全部折叠文件夹 主修饰键+K C
workspace.toggleFileTimeFilter 切换文件修改时间过滤 主修饰键+K T
workspace.packFiles 打包文件 无(可自定义)
workspace.toggleFileTreeSettings 切换文件树设置 主修饰键+K ,
view.fileTreeDifference 打开文件树差异 主修饰键+Alt+D
workspace.previousFile 遍历到文件树中的上一个文件 Alt+PageUp
workspace.nextFile 遍历到文件树中的下一个文件 Alt+PageDown
workspace.rename 重命名文件树中所选文件或文件夹 Windows/Linux:F2;macOS:Enter
workspace.cut 剪切文件树中所选文件或文件夹 主修饰键+X
workspace.copy 复制文件树中所选文件或文件夹 主修饰键+C
workspace.paste 将已剪切或复制的文件树条目粘贴到所选目标 主修饰键+V
workspace.revealInFileExplorer 在文件管理器中显示 Shift+Alt+R
file.save 保存文档 主修饰键+S
file.saveAs 另存为 主修饰键+Shift+S
file.close 关闭文档 主修饰键+W
file.exit 退出应用 主修饰键+Q
workspace.copyPath 复制文件或文件夹路径 Shift+Alt+C
workspace.copyRelativePath 复制文件或文件夹相对路径 主修饰键+K 主修饰键+Shift+C
workspace.copyRelativePathWithOutline 复制引用(大纲) 主修饰键+Alt+C
workspace.trash 将文件树中所选文件或文件夹移入回收站 Windows/Linux:Delete;macOS:Cmd+Backspace(也支持 Delete)
workspace.copyReference 复制引用 未绑定
workspace.findFileReferences 查找文件引用 未绑定
workspace.findHeadingReferences 查找当前标题引用(无标题时查文件) Shift+F12
editor.insertImage 插入本地图片 主修饰键+Shift+I
editor.pasteImage 插入剪贴板图片 未绑定
editor.gotoLine 跳转到行 Ctrl+G(所有平台)
editor.selectionToList 切换列表 Alt+Shift+L
editor.promoteHeadingLevels 整体提升大纲级别 未绑定
editor.demoteHeadingLevels 整体降低大纲级别 未绑定
editor.nextDifference 定位到下一处差异 Alt+N
editor.previousDifference 定位到上一处差异 Alt+P
navigation.openLink 打开编辑器光标处的 Markdown 链接 Alt+E
sync.open 打开同步 Alt+S
sync.run 立即同步当前仓库 Alt+A Alt+S
outline.focus 聚焦大纲 Shift+Alt+O
outline.collapseAll 全部折叠大纲 未绑定
outline.expandAll 全部展开大纲 未绑定
outline.collapseBranch 递归折叠当前大纲节点 未绑定
outline.expandBranch 递归展开当前大纲节点 未绑定
outline.focusFilter 聚焦大纲过滤框 未绑定
outline.toggleFilterMode 切换大纲过滤模式 未绑定
outline.copyMarkdown 复制可见大纲为 Markdown 未绑定
outline.copyText 复制可见大纲为纯文本 未绑定
editor.focus 聚焦编辑器(隐藏时显示,保留预览) Shift+Alt+E
search.open 页内查找;大纲获得焦点时聚焦大纲过滤框 主修饰键+F
search.replace 替换当前文件 Ctrl+H;macOS:Cmd+Option+F
search.findInFiles 在文件中查找 主修饰键+Shift+F
search.replaceInFiles 在文件中替换 主修饰键+Shift+R
search.next 下一个匹配 F3
search.previous 上一个匹配 Shift+F3
tabs.mruNext 最近使用标签 Ctrl+Tab
tabs.mruPrevious 反向最近使用标签 Ctrl+Shift+Tab
tabs.previous 上一个标签 Ctrl+PageUp;macOS:Cmd+Shift+[
tabs.next 下一个标签 Ctrl+PageDown;macOS:Cmd+Shift+]
tabs.activate1–tabs.activate9 切换到第 1–9 个标签(1-based) Alt+1–Alt+9
view.sidebar 切换侧边栏 主修饰键+B
view.editor 仅显示编辑器 未绑定
view.split 同时显示编辑器和预览 未绑定
view.preview 仅显示预览 未绑定
view.cycle 循环切换编辑、并排和预览视图 主修饰键+Shift+V
view.outline 切换大纲 主修饰键+Shift+O
settings.open 打开设置 主修饰键+Shift+,
settings.keyboard 打开快捷键设置 主修饰键+K 主修饰键+S
  • Windows 执行 Alt+PageUp / Alt+PageDown 文件遍历或使用小键盘触发 Alt+1–Alt+9 标签切换时,会拦截小键盘按键产生的延迟 Alt 数字码字符,避免导航后向编辑器插入文本;下一次真实文本按键仍按正常输入处理。
  • 最近标签选择器保留原有控件操作:方向键选择、Enter 确认、Esc 取消、松开 Ctrl 确认。
  • Windows Alt 数字码保护持续到松开 Alt 后,长按超过一秒也不会提前失效;切换标签后立即保护目标编辑器,下一次真实按键恢复正常输入。
  • 编辑区(含只读搜索页)聚焦时,Ctrl+↑ / Ctrl+↓ 每次向上 / 向下滚动一个行高,长按连续滚动;光标位置、选区及其方向保持不变,到达顶部或底部时也不移动光标。
  • 此滚动操作属于编辑区局部按键行为,各平台均使用 Ctrl;带 Shift、Alt、Cmd 的组合及输入法组合输入不触发该操作,现有用户自定义快捷键仍优先处理。
  • 大纲菜单点击入口保留;快捷键由渲染进程统一处理,以便修改和删除默认绑定生效。
  • 原生 File、Edit、View 菜单覆盖顶部工具栏的对应操作;菜单显示 VS Code 惯用的默认键位,但不自行注册按键,点击菜单与键盘操作均复用渲染进程命令。
  • Electron 原生菜单无法显示两段式 Chord;“打开笔记库”的主修饰键+K 主修饰键+O 仍会在工具栏提示、命令面板和快捷键设置中显示并生效。
  • 查找栏的 Enter/Esc、Alt+C/Alt+W、当前文件全部替换(Windows/Linux 为 Ctrl+Alt+Enter,macOS 为 Cmd+Enter),以及文件树、右键菜单等局部控件导航沿用原有实现,不属于本次可配置命令集合。

When 与分发规则

  • 支持 !、&&、||、==、!= 和括号。
  • 比较右侧可使用裸文本或引号包围的文本,例如 editorLanguage == markdown。
  • 空条件视为 true,未知 Context Key 的布尔值视为 false。
  • 不使用 eval,无效表达式不能保存。
  • 当前提供的 Context Keys:
    • editorFocus、editorTextFocus、editorHasSelection、editorReadonly、editorLanguage。
    • inputFocus、documentOpen、markdownDocument、workspaceEntrySelected、workspaceFileSelected。
    • pathCopyAvailable、relativePathCopyAvailable、referenceCopyAvailable。
    • searchVisible、searchInputFocus。
    • outlineFocus、outlineVisible、sidebarFocus、sidebarVisible。
    • modalOpen、dialogOpen、platform。
  • platform 的值为 windows、mac 或 linux。
  • 大部分默认规则含 !modalOpen,保存等操作还要求已打开文档。
  • 弹窗打开时,仅显式声明弹窗上下文且条件成立的规则可执行。
  • 快捷键设置和录制弹窗暂停普通命令分发;指定搜索框中的拼音开关保留专用入口。
  • 普通输入控件保留字符、删除、光标移动及 Ctrl/Cmd+A/C/V/X/Z/Y 等编辑行为。
  • 需要在输入控件中覆盖这些按键时,规则必须显式指定 editorFocus、editorTextFocus、inputFocus 或 searchInputFocus。
  • 普通状态下忽略输入法组合输入、纯修饰键和 AltGraph;优先使用物理 KeyboardEvent.code 规范化字母、数字和标点。
  • Chord 第一段已接受后,输入法组合事件也仅按物理 KeyboardEvent.code 识别第二段;
    • Windows 在等待第二段的 1.8 秒内临时向系统注册当前上下文可用的第二段,仅当应用保持聚焦时转交现有分发器,避开 Chromium 将按键交给输入法后只返回 Process 的路径;
    • 注册失败时仍回退到 Electron before-input-event。
  • 所有被消费的第二段(不依赖浏览器是否标记 isComposing 或 keyCode 229)都会阻止该物理键随后产生的 beforeinput,并由 CodeMirror 事务过滤器拒绝遗漏的输入法提交,兼容 macOS 及 Windows 的不同输入事件顺序;若命令打开对话框或聚焦其他控件,延迟的输入法提交不能把焦点抢回编辑器。
  • 用户绑定优先于插件绑定,插件绑定优先于系统绑定;同一优先级按条件中的词项数量估计具体程度,再按最后注册者优先。
  • 此具体程度规则是确定性启发式,不做完整逻辑蕴含判断。
  • 同键或一段键与 Chord 前缀重叠时显示可能冲突,允许保存并保留两者。
  • 简单合取条件中的互斥值可判定为无冲突;其余条件保守提示。
  • 有效 Chord 的前缀优先等待第二段,不立即执行同前缀的单段绑定。
  • 等待超过 1.8 秒、Esc、窗口失焦或控件焦点切换会取消 Chord。
  • 第二段按下时重新检查上下文;下一次可识别的非修饰键由 Chord 独占,不匹配或上下文失效时结束等待并阻止该按键进入原控件或输入法。
  • 操作系统及 Electron 原生菜单保留的按键仍可能被外层拦截,页面对常见组合显示提醒。

存储与实现

  • 快捷键配置存于 Electron app.getPath('userData')/User/keybindings.json,热键配置存于同目录的 hotkeybindings.json;目录和文件不存在时自动创建,初始内容为 []。
  • Windows 默认路径为 %APPDATA%\mynote-client\User\keybindings.json,例如 C:\Users\Administrator\AppData\Roaming\mynote-client\User\keybindings.json。
  • 实际路径由应用的用户数据目录决定,在快捷键列表和 JSON 编辑窗口中显示;不写入 VS Code 的 Code\User 目录。
  • 不再读取或写入快捷键 localStorage;按本次约定不迁移旧配置。
  • 配置属于本机应用配置,所有笔记库共用,不参与笔记库同步。
  • 文件只包含公开 JSON 格式的用户绑定与默认移除规则;重启后重新叠加到系统默认规则。
  • 写入采用 UTF-8、LF、两空格缩进;先写同目录临时文件,再替换目标文件。
  • 主进程只允许访问固定配置文件,大小上限为 1 MiB;渲染进程不能指定其他路径。
  • 启动时无法读取或解析配置会提示错误并暂用默认值,不覆盖原文件。
  • 主进程每 500 毫秒检测文件变化,窗口重新获得焦点和打开设置时也会重新读取;有效外部编辑自动更新列表和分发。
  • 运行期间外部编辑无效时保留最后有效快捷键;可打开 JSON 编辑器查看原文件并修复。
  • 保存时比较最近读取的文件内容,发现外部变更则拒绝覆盖;重新打开编辑器后再修改。
  • src/main/keybinding-file.ts:固定路径文件读写、外部变更通知与 IPC;设置读写使用有大小限制的同步本地文件操作,以保持保存成功后才切换有效规则的行为。
  • src/main/chord-input.ts:仅在 Windows 的 Chord 等待期间,临时注册候选第二段系统快捷键,并以 Electron 原始键盘事件作为回退;窗口失焦、第二段到达、取消或超时都会解除注册。
  • src/renderer/keyboard/service.mts:命令注册、覆盖存储、冲突检查和 Chord 分发。
  • src/renderer/keyboard/when.mts:条件解析与求值。
  • src/renderer/keyboard/keys.mts:按键规范化与显示。
  • src/renderer/keyboard/page.mts:按需加载的设置及录制界面。
  • src/renderer/runtime/keyboard.mts:应用命令与默认绑定注册。
  • src/renderer/runtime/keyboard-dispatch.mts:实际上下文、键盘监听、输入保护及服务接入。
  • P2 的 Profiles、云同步和导入导出未纳入本次。

快捷键注册与响应技术流程

本节描述当前实现。应用内快捷键由渲染进程匹配,全局热键由 Electron 主进程注册,控件内部导航由控件自己处理。

模块与初始化顺序

模块 职责
src/renderer.mts 显式导入键盘模块,依次调用 runtime/keyboard.mts 和 runtime/keyboard-dispatch.mts 的初始化函数
src/renderer/runtime/keyboard.mts 注册应用命令、默认绑定及全局热键展示项
src/renderer/runtime/keyboard-dispatch.mts 创建服务、采集上下文、处理键盘事件、保护输入法输入、更新提示
src/renderer/keyboard/service.mts 命令表、有效绑定合成、Chord 状态机、冲突检查、插件生命周期
src/renderer/keyboard/keys.mts 配置键位规范化、物理按键识别、显示文本
src/renderer/keyboard/when.mts When 解析、求值、保守重叠判断
src/preload.ts 固定文件读写 IPC 和第二段捕获接口
src/main/keybinding-file.ts 文件快照、原子替换、并发覆盖检查、变化通知
src/main/chord-input.ts Windows 第二段临时捕获、原始修饰键及原生菜单协调
src/main/window-hotkey.ts 长期全局热键注册与实际执行
  • 先注册命令和默认绑定,再实例化 KeybindingService,最后接入键盘监听。
  • 快捷键服务与热键服务共享渲染命令表,但各自持有 defaults、用户规则、文件快照和监听器。
  • 设置页和命令面板按需加载,常规匹配不依赖其界面初始化。

命令注册与有效绑定合成

命令与绑定分离

  • CommandRegistry.commands 是以稳定命令 ID 为键的 Map;register() 拒绝空 ID 和重复 ID,默认来源为 system。
  • 命令包含 id、title、execute、when;无默认快捷键的命令也能注册。
  • CommandRegistry.execute(id) 仅调用回调,不检查 When,不捕获异常。
  • registerKeyboardCommand(id, title, execute, keys, when, options) 同时注册命令和默认绑定。
    • 默认 When 为 !modalOpen;每个键位生成 system.<命令 ID>.<序号> 绑定 ID。
    • 命令 When 与绑定 When 分别存储,一条命令可有多条绑定。
    • 默认生成 Windows/Linux 的 Ctrl 和 macOS 的 Meta 覆盖;portableModifiers: false 保留原修饰键。
    • MRU 标签命令保留 Control,前后标签切换使用单独的平台映射。

合成步骤

  1. 构造服务时校验默认绑定,规范化键位,将 When 解析为表达式树。
  2. reload() 读取用户文件,完整解析成功后才替换内存快照。
  3. allDefaults() 按系统默认、插件默认的顺序组合贡献。
  4. bindings(platform) 从默认项排除用户移除 ID 和未解析的移除选择器,再追加用户绑定。
  5. 排除命令表中不存在的命令,按 entry[platform] ?? entry.key 选择键位。
  6. 默认排除空键位;设置界面可用 includeDisabled 保留禁用项。
  • 平台空字符串表示禁用,不能用逻辑或回退通用键位;baseKey 保留通用键位,结果中的 key 是当前平台键位。
  • 用户内部 ID 为 user.<UUID>;公开 JSON 不保存 ID、来源和表达式树。
  • -<command> 按通用键位、可选 When、可选平台字段匹配默认项。
  • 插件尚未加载时保留未解析移除选择器,之后注册插件即可继续应用。

插件生命周期

  • window.mynoteKeyboard.registerPlugin(id, contributions) 委托给快捷键服务,插件须为受信任的本地模块;JSON 不能注入函数。
  • 注册前整体校验命令 ID 唯一性、插件 ID 前缀、标题和执行函数,绑定须引用本次贡献的命令。
  • 全部校验通过才发布,来源为 plugin:<id>,绑定 ID 为 plugin.<id>.<序号>。
  • 返回卸载函数只卸载本次注册,旧卸载函数不能误删之后同名插件。
  • 注册、卸载、保存、成功重载均取消待处理 Chord 并通知监听器。

按键事件与响应流程

flowchart TD
  A[window keydown 捕获阶段] --> B[界面专属入口过滤]
  B --> C[KeybindingService.handle]
  C --> D[物理键规范化和实时上下文]
  D --> E[过滤平台键位、When、弹窗和输入控件规则]
  E --> F{存在更长前缀?}
  F -->|是| G[消费按键并等待第二段 1800ms]
  G --> C
  F -->|否| H{存在精确匹配?}
  H -->|是| I[按优先级选择并执行回调]
  H -->|否且已有前缀| J[取消等待并消费第二段]
  H -->|否且无前缀| K[交还控件或浏览器]

入口与按键规范化

  • window.addEventListener('keydown', ..., true) 在捕获阶段调用共享匹配器;处理成功后 stopImmediatePropagation(),避免编辑器再次处理。
  • 命令面板、快捷键列表、绑定编辑弹窗打开时提前返回,由控件处理导航、编辑和录制。
    • 指定搜索框具有 pinyinSearchInputFocus 时,只允许 search.togglePinyin 进入匹配器。
  • handle() 收到已经 defaultPrevented 的事件会取消等待并返回 false。
  • eventStroke() 优先根据 code 识别物理字母、数字、常见标点和导航键,必要时回退小写 key。
  • 修饰键固定顺序为 ctrl+shift+alt+meta;cmd/command 转成 meta,control 转成 ctrl,option 转成 alt。
  • normalizeKey() 只接受一段或两段,拒绝重复修饰键、纯修饰键和不支持的键名;支持 F1–F24、ContextMenu、numadd、numsub。
  • 纯修饰键、AltGraph、无法识别的按键不匹配;普通输入法组合输入不启动快捷键。
  • macOS 显式 Option 组合可按物理键识别;已有前缀时允许识别组合输入的物理第二段。
  • 主进程合成事件可直接提供 bindingStroke,仍复用同一匹配器。

实时上下文与候选过滤

  • 每次匹配重新调用 context(),第二段使用当时的焦点、文档和选择状态。
  • 上文基础 Context Keys 之外,当前还提供:
    • 文档:documentTextSelection、activeEditorIsPreview、activeEditorIsPinned、activeEditorIsDirty、editorGroupHasPreview、voiceRecordingAvailable。
    • 搜索:pinyinSearchInputFocus、quickOpenInputFocus、commandPaletteInputFocus。
    • 文件树:fileTreeFocus、workspaceOpen、workspaceSingleEntrySelected、workspaceFileComparisonAvailable。
    • 大纲:outlineFavoriteAvailable、outlineFavoriteOrganizerAvailable。
    • 同步:syncFileHistoryAvailable、syncConflictResolutionAvailable、syncConflictToolAvailable。
  • 绑定平台字段使用 win/mac/linux,When 的 platform 值使用 windows/mac/linux。
  • When 优先级为括号及 !、&&、||;比较右值是字面量,不读取另一个上下文键。
  • 未知布尔键为 false,比较时用 String() 转换上下文值,空条件为 true。
  • 候选须通过绑定 When;全局热键命令排除在应用内匹配之外,避免重复执行。
  • 弹窗打开时,绑定文本还须显式含 modalOpen 或 dialogOpen。
  • 输入控件中覆盖原生编辑按键时,文本须显式含 inputFocus、editorFocus、editorTextFocus 或 searchInputFocus。
    • 这层是正则文本检查,不是对表达式含义的逻辑推导。
    • 等待第二段时按第一段判断原生编辑保护,第二段字母和 Enter 归 Chord 所有。
  • 拼音开关在指定搜索框中有例外:允许忽略弹窗保护,并尝试用关闭弹窗的上下文求值绑定。

Chord 状态与胜出规则

  • 状态由 pending、定时器及底部 role=status 提示维护。
  • 存在更长前缀时优先等待,即使同键有单段精确匹配;超时仅取消,不补执行单段命令。
  • 接受第一段后消费事件,设置 1800ms 定时器,并调用 pendingChanged(true, secondStrokes)。
  • 等待时 Esc 消费并取消;重复事件消费但不执行第二段;无法识别按键保留等待。
  • 第二段组成 <pending> <stroke> 后重新匹配;无匹配也消费该键,不再次作为单段尝试。
  • blur、focusin、配置或插件变化取消等待。
  • 精确匹配分数为来源基分加 When 标识符词项数:用户 20000、插件 10000、系统 0。
    • 升序稳定排序后取最后一项,同分取有效列表中靠后的项;用户配置后项可覆盖同分前项。
    • 词项数量是确定性启发式,不进行逻辑蕴含求解。
  • 冲突预览检查同键、前缀及 When 可能重叠;允许保存,不等同于运行时胜出结果。

Windows 第二段捕获与输入法保护

  1. 渲染进程通过同步 IPC keyboard:chord-capture 发送 pending 和候选第二段,避免快速按键早于捕获启动。
  2. 主进程仅在 Windows 启用捕获,并临时 globalShortcut.register() 注册候选第二段。
  3. 系统回调检查窗口仍聚焦,解除注册,通过 keyboard:chord-input 发回 bindingStroke。
  4. 注册失败保留 before-input-event 回退,阻止原始事件后转发 code、按键、重复及修饰键等字段。
  5. 渲染进程 onChordInput 调用同一个 handle(),重新检查当前规则。
  • 临时注册只负责输入路由,实际命令仍在渲染进程执行;取消、超时、第二段到达或失焦均解除注册。
  • DOM 路径消费第二段后,捕获 beforeinput 并调用编辑器适配器抑制接口,阻止延迟组合提交与焦点回抢。
  • macOS 单段 Option 字母/数字也启用保护,防止死键提交文本。
  • Windows Alt+PageUp/PageDown 和 Alt+小键盘标签切换跟随新编辑器,阻止延迟 Alt 数字码输入。
  • 下一次真实物理按键、鼠标按下、粘贴、拖入或失焦清理抑制;通常有 1 秒兜底,Windows Alt 导航从 Alt 松开后计时。

键盘、菜单与命令面板执行差别

入口 执行前检查 最终调用
应用内快捷键 绑定 When、焦点保护、冲突优先级 handle() → 注册表
主菜单应用命令 IPC application:command-requested → runtime/shell.mts,检查命令 When KeybindingService.execute() → 注册表
命令面板 命令 When 决定可用性;关闭面板恢复焦点后再次检查命令存在及 When 注册表
全局热键 主进程解析热键文件并注册系统键位,无渲染上下文 主进程独立注册表
  • handle() 不额外求值命令自身 When,自定义绑定 When 与命令可用性是两个独立层次;新增绑定须明确操作前提。
  • KeybindingService.execute() 返回是否接受执行,不等待异步完成;与 handle() 一样捕获同步异常和 Promise 拒绝并交给 onError。
  • 精确匹配执行失败也不会把已消费按键交还控件。
  • 命令面板与绑定数量无关,未绑定命令仍可执行;全局热键、面板自身入口、历史运行项和 MRU 切换器不列入普通结果。
  • 可配置菜单项随有效绑定更新提示;两段键和 macOS 键位放在标签内,相关项使用 registerAccelerator: false 保留渲染进程按键所有权。

保存、重载与全局热键注册

  • 保存链路:save/remove/reset/applyJSON → commit() → preload 同步 IPC → 主进程写入 → 更新内存规则 → 通知监听器。
  • 完整校验后比较 expected 与真实文件内容,有外部变更则拒绝覆盖。
  • 主进程限制固定路径及 1 MiB 大小,写同目录临时文件后 rename 替换;写入失败不切换内存规则。
  • 每 500ms 监听两类文件并广播各自 changed;窗口 focus、打开设置也 reload。
  • 相同文本不重复加载;无效外部编辑保留最后有效规则,启动读取失败保留默认规则供修复。
  • 监听器同步刷新文件树、视图切换、选择器历史和菜单提示。
  • createWindowHotkeyController().prepare(text, enabled) 复用解析服务合成全局热键平台规则。
    • 全局热键要求单段、When 留空、不支持 ContextMenu,同一配置不能有两个热键占用同键位。
    • 先尝试注册新增键位;保存失败 rollback 释放本次新增注册,commit 才释放不再需要的旧键位。
    • 系统占用导致注册失败时记录警告并剔除失败项,不阻止保存;后续读取可以重试。
    • 总开关关闭保留 Quick Cmd Form 热键例外,无效草稿也允许关闭其他热键。
    • 渲染全局命令回调只是展示占位,实际窗口切换、鼠标操作由主进程执行。

排查与验证入口

  • 未响应时依次检查命令注册、配置类别、平台键位、绑定 When、焦点和弹窗保护、Chord 前缀、冲突胜出项、系统占用。
  • 菜单或命令面板可执行而按键不执行时检查绑定层;反向差异检查命令自身 When。
  • 第二段插入文本时检查 pendingChanged、同步 IPC、临时注册、onChordInput 和编辑器输入抑制链路。
  • 服务单测:test/keybinding-service.test.mts、test/keybinding-p1.test.mts、test/keyboard-shortcuts.test.mts、test/keyboard-category.test.mts。
  • 主进程单测:test/keybinding-file.test.ts、test/chord-input.test.ts、test/window-hotkey.test.ts、test/hotkey-settings.test.ts。
  • 入口单测:test/menu.test.ts、test/command-palette.test.mts。
  • 实机验证入口:npm run test:smoke:keyboard;跨平台输入法和系统占用需在对应系统验证。

JSON 配置

  • 「编辑 JSON」和「默认 JSON」仅显示当前类别;保存 JSON 仅写入当前类别的独立文件,另一类别的配置保持原样。
  • 点击「编辑 JSON」查看和修改磁盘文件;快捷键列表与 JSON 共用 User/keybindings.json,热键列表与 JSON 共用 User/hotkeybindings.json,不维护额外副本。
  • 若 JSON 文件语法损坏,编辑器显示完整原文件并提示修复范围;修复保存只会更新当前类型的配置。
  • 「编辑 JSON」旁提供「默认 JSON」按钮,弹出只读窗口展示当前类型的全部内置及当前已加载插件的默认绑定 JSON,包括通用键位、平台覆盖和生效条件,不受用户修改或删除规则影响。
  • 每条默认配置附带 description 命令描述,使用快捷键列表中的中文命令名称(插件使用其注册名称,缺失时回退为命令 ID),查看及复制均包含该字段;该字段仅用于默认配置参考,不属于用户配置格式。
  • 默认 JSON 支持选择文本复制及点击「复制 JSON」复制全部内容;复制成功或失败会在窗口内提示,查看和复制不会修改用户配置。
  • 复制接口允许最多 1 Mi 个字符,支持完整默认配置等长文本,并保留输入类型、非空及长度校验。
  • 每次打开默认 JSON 都读取最新默认规则;窗口内容可滚动,点击「关闭」或按 Esc 退出。
  • JSON 配置弹窗的文件路径旁提供「定位文件」链接,点击后在系统文件管理器中打开所在目录并选中当前类型的 keybindings.json 或 hotkeybindings.json;不保存或关闭当前草稿,定位失败时在弹窗内提示。
  • 列表修改后重新打开 JSON 即可看到更新;JSON 保存成功后列表立即刷新。
  • JSON 草稿需点击「保存 JSON」才生效;取消不会修改配置。
  • 保存前完整校验数组、字段、键位和 When;解析或持久化失败会保留草稿及原有效配置。
  • 草稿打开期间配置发生变化时拒绝覆盖,需取消后重新打开。
  • 普通规则使用 command、key、可选 when、win、mac、linux 字段。
  • command 前缀 - 表示移除匹配的默认绑定;按通用 key 匹配,可用 when 和平台覆盖进一步限定。
  • 插件未加载时,已有默认移除记录可用 {"remove":"绑定 ID"} 保留;文件中的负命令移除规则也会保留,插件注册后参与默认绑定过滤。
  • 未注册命令的用户规则会保留在 JSON 中,但不显示在列表,也不参与分发或冲突检查;命令注册后自动生效。
  • 未知字段会报错,避免拼写错误被静默忽略。
[
  { "command": "-file.save", "key": "ctrl+s" },
  {
    "command": "file.save",
    "key": "ctrl+shift+s",
    "mac": "cmd+shift+s",
    "linux": "",
    "when": "documentOpen && !modalOpen"
  }
]

插件快捷键接口

  • 为受信任的本地渲染模块提供 window.mynoteKeyboard.registerPlugin(id, contributions)。
  • 此接口负责快捷键贡献注册,不提供插件下载、安装或执行 JSON 代码的能力。
  • 命令 ID 必须以 插件ID. 为前缀,且全局唯一;命令需提供标题与执行函数。
  • 插件绑定只能引用本插件命令,支持与用户 JSON 相同的键位和平台覆盖。
  • 注册会先整体校验,失败时不留下部分命令。
  • 来源显示为 plugin:插件ID,用户可修改、删除并恢复插件默认绑定。
  • 返回的卸载函数移除命令和默认绑定、取消等待中的 Chord,并刷新页面;用户覆盖和移除记录保留。
  • 默认绑定 ID 按 plugin.插件ID.数组下标 生成;插件更新需保持绑定顺序稳定,以维持用户移除记录的对应关系。
const dispose = window.mynoteKeyboard.registerPlugin('demo', {
  commands: [{ id: 'demo.run', title: '运行示例', execute: () => console.log('demo') }],
  bindings: [{ command: 'demo.run', key: 'ctrl+alt+j', mac: 'meta+alt+j', when: '!modalOpen' }]
});
// 插件卸载时调用;可重复调用,不影响后续重新注册的插件。
dispose();
  • src/renderer/keyboard/config.mts:公开 JSON 格式转换和整体校验。
  • src/renderer/keyboard/filter.mts:高级搜索过滤。
  • src/renderer/keyboard/json-editor.mts:JSON 草稿编辑界面,随快捷键设置按需加载。
  • src/renderer/keyboard/default-json.mts:默认快捷键 JSON 只读查看和复制界面,首次点击按钮时创建弹窗。

全局热键配置

  • 热键类别在「查看平台」旁单独显示「启用热键」复选框,默认勾选。
    • 取消勾选后立即注销其他全局热键,保留 Quick Cmd Form 启动热键 quickCmdForm.toggleWindow,以便再次打开并输入 h 恢复。
    • Quick Cmd Form 启动热键仍遵守自定义键位、平台覆盖和显式移除配置。
    • 配置草稿无效时,关闭开关保留当前已注册的 Quick Cmd Form 键位;重新勾选后按有效的保存配置恢复其他热键。
  • 热键录制一段后立即完成,可继续重新录制,Enter 保存。
  • 开关仅保存在本机 workspace-state.json 的 localSettings.hotkeysEnabled,由设置层统一保存和广播,通过 applySettings 更新 runtime store,不参与云同步,缺省启用,重启后保留;关闭期间修改快捷键配置不会重新注册热键,已有绑定和应用内快捷键保持可用。
  • 重新启用时若发生系统占用或配置无效,开关保持关闭并显示错误;开关文件保存失败时保持此前状态。
  • 在「设置 → 快捷键设置」搜索「全局热键」或 window.toggleVisibility,可修改「显示/隐藏或最小化窗口(全局热键)」。
  • 默认仍为 Alt+Shift+S:窗口已聚焦时在 macOS 像 Cmd+H 一样隐藏应用的所有窗口、在 Windows/Linux 最小化主窗口;其他情况下恢复、显示并聚焦窗口。
  • 可从 Window 菜单或命令面板将当前笔记窗口设为主窗口;未设置时仍选择最近聚焦的笔记窗口,固定窗口关闭后恢复默认选择算法。
  • 支持添加多个热键、删除、恢复默认、JSON 编辑和 Windows / macOS / Linux 平台覆盖;平台值为空字符串表示禁用。
  • 全局热键在其他应用获得焦点时也能触发,仅支持一段组合键,When 必须留空,不支持 ContextMenu 键。
  • 热键配置保存在本机 User/hotkeybindings.json,保存后立即重新注册;下次启动自动读取,外部文件修改也会重载。
  • 新热键被系统或其他应用占用、或注册抛出异常时,配置仍可保存;失败原因写入应用日志,失败热键暂不生效,其他热键继续逐个注册。
  • 列表页的删除、恢复默认、热键总开关,以及修改页和 JSON 编辑器的保存均遵循此规则;读取或重新加载配置时会重试未注册成功的热键。
  • 文件写入失败时撤销新注册;外部配置无效时保留上次有效热键,JSON 编辑器仍可打开文件修复。
  • 首次运行包含此功能的新代码需要重启应用;此后修改热键无需重启。
  • 全局热键由 Electron 主进程独立处理,渲染进程不重复分发。

验证

  • 下列通用入口按当前构建运行器执行;带日期的小节保留当时的验证结果、失败及命令,不表示这些问题现在仍存在或本次重新运行过。

  • 命令面板独立回归验证:node scripts/run.mts electron test/electron-smoke.js --command-palette-only,覆盖输入框焦点、光标、选区方向和各关闭路径。

  • node scripts/run.mts node --test test/hotkey-settings.test.js:总开关注销多个热键、关闭期间读写、重启持久化、冲突不阻止重新启用与列表和修改保存、写入失败回滚和无效配置下关闭。

  • node scripts/run.mts node --test test/window-hotkey.test.js test/keybinding-file.test.js:全局热键注册、替换、注册失败或异常后的继续注册与重试、平台禁用、恢复默认、写入失败回滚和无效配置修复。

  • 键盘绑定单元测试:磁盘读写、引擎、JSON、平台、插件、过滤器与菜单。

    • node scripts/run.mts node --test test/keybinding-file.test.js test/keybinding-p1.test.mjs test/keybinding-service.test.mjs test/keyboard-shortcuts.test.mjs test/menu.test.js
  • npm run test:smoke:keyboard:真实 Electron 设置入口、录制 Enter Chord、修改、删除、恢复默认、旧查找快捷键停用、磁盘持久化、外部修改、无效 JSON 修复和渲染进程重新加载。

  • npm run test:smoke:完整界面回归。

  • npm run lint:文件长度检查及逐模块 ESLint。

  • Markdown 产品方案保留完整内容,不参与 check:file-length。

列表首条操作快捷键验证(2026-10-02)

  • 相关键位、配置和原生输入控制的 20 项单元测试通过;test/chord-input.test.ts 覆盖 macOS 菜单让行范围、失焦与关闭后恢复。
  • Electron 回归模拟 Windows、macOS 与 Linux 的 Alt 键位,覆盖四个首条操作、筛选结果、缺失按钮、空列表、额外修饰键、组合输入、macOS Option 重音键、长按和录制窗口隔离。
    • 标准回归仍在后续既有命令面板文件历史断言失败;临时隔离该步骤后,完整快捷键及磁盘配置回归通过。
    • Windows 主机未验证真实 macOS 原生菜单,菜单行为由单元测试验证。
  • 修改文件未新增 ESLint 问题;chord-input.ts 仍有原来的正则转义错误。
  • 连续操作回归从真实聚焦元素发送按键,覆盖取消修改/添加后继续操作,以及多次删除与恢复默认;避免直接向搜索框发事件掩盖焦点丢失。
  • 调整为 Alt 键位后,12 项键位与原生输入控制单元测试、隔离既有命令面板问题后的 Electron 快捷键回归通过;修改未新增 ESLint 问题。

平台独立绑定与转换验证(2026-10-02)

  • 35 项相关单元测试通过,覆盖单平台分发、空通用键位持久化、空绑定拒绝和平台独立插件绑定的删除记录。
  • Electron 快捷键设置回归通过,覆盖 Windows/macOS 双向转换、别名与双段键位、通用键位回退、错误输入保护、仅平台绑定保存与重开,以及 Linux 隐藏转换按钮。
    • 标准快捷键回归在后续命令面板文件历史断言失败;临时隔离该未修改步骤后,磁盘持久化、外部修改、JSON 修复与重载验证通过。
  • 本次修改的快捷键模块 ESLint 通过。
    • 全量 lint 仍被既有超长文件阻断。
    • 独立渲染脚本检查仍报告 resetFavoriteSortMode 未使用与 exportWorkspace 未定义;全量 ESLint 仍报告 chord-input.ts 的转义与 link-mirror.ts 的正则错误。

命令面板冒烟阻断修复(2026-10-02)

  • 文件历史冒烟按操作标签验证“恢复”和“另存”,不再假定“恢复”是最后一个按钮。
    • 同时验证删除版本不显示恢复或另存按钮。
  • 不可用命令使用未设置的测试专用上下文条件,避免之前的测试留下打开文档后使该命令变为可用。
  • 命令面板专项、标准快捷键专项、面板布局专项 Electron 冒烟和改动脚本 ESLint 均通过。
    • 完整 node scripts/run.mts electron test/electron-smoke.js 本次达到 60 秒超时,不能视为完整综合回归通过。
  • 本次仅更新测试与文档,无需重启应用或服务。

综合冒烟超时与夹具修复(2026-10-02)

  • 综合 Electron 冒烟超时从 60 秒延长为 5 分钟;运行日志和超时错误标明当前渲染脚本。
  • 隐藏测试窗口禁用后台节流,布局断言仍等待真实动画帧。
  • 清理最后一个窗口时保留进程,由测试显式设置退出码;Windows 清理临时目录使用有限重试,清理失败也以失败码退出。
  • MRU 方向键从编辑器派发,遵循真实输入的捕获与冒泡路径。
  • 缩放检查等待实际缩放值,不再假定 IPC 与重排一定在 100ms 内结束。
  • 综合入口补齐打包与工作区会话夹具,以及路径复制检查需要的参数。
    • 文件树差异测试开始前建立独立 Git 基线,并清理当前库会话测试留下的草稿和筛选。
    • 筛选恢复测试验证切换目标库及恢复条件,不假定前序会话测试使用过的第二库没有标签。
  • 工作区会话测试补上双击前的选中点击,使用 CodeMirror 的正文焦点节点和当前控件的 aria-checked 属性。
    • 开始检查前明确设置目标控件状态,避免依赖前序测试的默认状态。
  • 库详情按当前夹具的文件数和同步身份验证显示,不再固定为单文件、未建立身份的场景。
  • 文件树断言限定在文件树容器内,避免命中最近文件列表;路径比较统一 Windows 分隔符,快捷键按当前平台派发。
    • 文件树滚动检查等待布局稳定,并确保测试视口至少可容纳一行。
    • 删除检查后恢复改名夹具,收藏检查明确设定目标状态。
    • 大纲检查确保面板打开,并等待当前夹具标题生成,避免前序流程的面板状态影响验证。
  • 模拟外部删除库前,以及综合和各专项退出清理时,等待索引任务结束,避免 Windows 文件句柄竞态。
  • 粘贴撤销冒烟超时延长为 60 秒;等待编辑后的光标滚动布局结束再建立视图恢复夹具,并确保失败退出码有效。
    • 原生键盘输入、撤销和重做等待实际文本结果后才切换标签,防止异步输入落入另一个编辑器。
  • 片段窗口冒烟按实际项目产物检查配置与伴随的 .ignore 文件。
  • 缓存的文件打包窗口重新打开时刷新共享预设列表,保留当前筛选和输出选项。
    • 此应用修复需要重启桌面应用后生效。
  • 超过 600 行的冒烟入口、初始编辑器流程和文件树标题流程安排独立、集中模块抽取,保留原有验证与注释。
    • 片段或搜索改动后,test/snippet-window-smoke.ts 为 607 行,纳入后续独立的集中抽取:统一整理窗口夹具、集合操作与搜索/接力/导出场景,本次不单独拆出新增测试。
    • FEATURES.md 的长文档整理也另行处理,保留完整行为说明。
  • 验证记录:527 项单元测试通过,修改的 JavaScript / MJS 文件通过显式 eslint --no-ignore 检查。
    • 标准冒烟链中的编辑器、布局、粘贴、发布、片段窗口、文件树多选专项均通过。
    • 最终修复后运行 node scripts/run.mts electron test/electron-smoke.js,完整综合冒烟通过并以 0 退出,覆盖版本差异、筛选恢复、大纲和库清理。
    • 全量 lint 仍受未修改文件的行数门禁及既有 ESLint 问题阻断:渲染合并检查中的 resetFavoriteSortMode、exportWorkspace,以及 chord-input.ts、link-mirror.ts 的正则检查。

冒烟异步操作与退出清理修复(2026-10-02)

  • 片段窗口的打开测试等待导入 IPC 完成及查询结果就绪,避免只检查模拟文件对话框参数就开始下一步。
    • 模拟打开对话框保留 150 毫秒延迟,回归验证操作完成等待。
    • 新建标签完成首次读取与查询后才执行关闭,防止旧查询访问已关闭的集合。
  • 片段窗口退出时先销毁发送窗口,等待在途 IPC 完成,再关闭后续读取创建的监测器并删除临时目录。
    • 防止尚未完成的 snippets.json 原子写入与测试目录删除竞争,产生 rename ENOENT。
    • 渲染执行失败会打印具体执行语句与原始错误;断言失败以退出码 1 结束。
    • 专项总超时调整为 60 秒,超时时打印测试名称。
  • 粘贴撤销测试注册真实命令历史读写服务,消除缺少 command-history:read/update 处理器的错误。
    • 删除临时目录前等待命令历史写入和工作区索引结束。
  • 文件树多选等待条件使用 15 秒期限,并在撤销和重做后等待历史操作完成。
    • Windows 回收站逐项恢复可能超过原来的 2.5 秒;超时时输出未满足的条件。
  • 本次仅修改测试夹具,无需重启桌面应用。
  • 验证:完整 npm run test:smoke 通过,改动脚本的显式 ESLint 和 git diff --check 通过。
    • 额外注入 snippets.json 状态替换延迟 150 毫秒:旧版失败并产生临时目录 watch ENOENT,修复版通过。
    • 导入未完成时注入渲染执行失败:退出码为 1,保留执行语句和原始错误,清理阶段没有 ENOENT。
    • 负向场景仍保留未授权路径、目标重名等预期错误日志;以断言和进程退出码判断回归结果。

P1 验证结果(2026-09-05)

  • 16 项相关单元测试通过。
  • 快捷键 Electron 回归通过,覆盖原 P0 操作、JSON 无效草稿/保存、双向同步、高级过滤、Chord 冲突详情和跨平台预览。
  • 渲染脚本检查和全量 ESLint 通过。
  • 修复新增绑定时误清除既有默认移除记录的问题,并增加回归验证。

磁盘配置验证结果(2026-09-05)

  • 21 项相关单元测试通过,包括磁盘创建、UTF-8/LF、写失败保护、过期保存保护、外部配置重载和延迟加载插件移除规则。
  • Electron 快捷键回归通过,覆盖真实文件写入、外部变更通知、错误文件修复和重新加载后的持久化。
  • 粘贴撤销回归第一次在原生重做断言失败,未改动代码重跑后通过。
  • 渲染脚本检查和全量 ESLint 通过。
  • 原始产品方案文件的 600 行限制问题仍保留。

P0 验证记录(2026-09-05)

  • 快捷键专项 Electron 测试、11 项相关单元测试、ESLint 和 Windows 目录打包通过。
  • 全量单元测试为 195/196;既有 macOS BCompare 路径测试在 Windows 下返回反斜杠,与预期正斜杠不一致。
  • 完整 Electron 回归的文件树滚动可见性检查未通过;在修改前的 9ee4507 上使用隔离存储与相同等待时限复现相同失败。
  • 完整 lint 被原始方案文档的 600 行限制阻断;渲染脚本检查与 ESLint 单独执行均通过。

CodeMirror 正文编辑接入(2026-09-13)

  • 应用快捷键的捕获阶段优先于 CodeMirror 键位;未处理的正文导航交给编辑器。
  • 保留两空格 Tab、块缩进、列表命令及 Ctrl/Cmd 撤销行为,不启用 Markdown 自动续列表。
  • 输入法组合事件保留给输入法,不能由缩进或方向键命令拦截。
  • 主菜单撤销/重做走每页 transaction 历史;复制/剪切复用既有整行规则,空文档不清空剪贴板。
  • 命令面板、Quick Open 和应用内提示/确认框保存所属编辑器的选区方向及双轴滚动位置。
  • 快捷键 JSON 设置编辑器仍保持原实现。
  • 实施与验收记录见 CodeMirror 迁移。

热键与快捷键分离后的整合任务

  • 后续单独整合 test/electron-smoke.ts 与 test/paste-undo-smoke.ts 的公共初始化、生命周期和测试场景。
  • 本次仅更新这两个现有大文件的异步配置服务初始化调用,不按单次功能拆出零散模块;统一提取时保留原有场景、注释与行为。

html-paste

module-extraction

点赞(0) 阅读(32) 举报
目录
标题