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

一套可以直接交给开发人员实现的「快捷键配置产品设计方案」,类似 VS Code 截图里的 Keyboard Shortcuts 页面,包含数据模型、交互、冲突处理、上下文条件、用户配置和实现边界。

下面我按产品规格的方式来设计。

快捷键配置功能产品方案

1. 产品目标

提供一套统一的快捷键管理系统,让用户可以:

  • 查看系统内所有命令及其快捷键
  • 搜索命令、快捷键和上下文条件
  • 修改已有快捷键
  • 为命令增加多个快捷键
  • 删除/恢复快捷键
  • 查看快捷键来源
  • 检测快捷键冲突
  • 基于上下文 when 条件复用同一个快捷键
  • 支持系统默认、插件默认、用户自定义三级覆盖
  • 支持 JSON / UI 两种配置方式
  • 支持 Windows / macOS / Linux 不同按键映射

核心设计原则:

快捷键不是“按键 → 功能”的简单映射,而应该是 按键 + 上下文条件 → 命令


2. 页面结构

整体参考 VS Code。

建议页面由 4 个区域组成:

┌────────────────────────────────────────────────────┐
│ 搜索框                                  操作菜单    │
├──────────────┬─────────────┬────────────┬──────────┤
│ Command      │ Keybinding  │ When       │ Source   │
├──────────────┼─────────────┼────────────┼──────────┤
│ 保存         │ Ctrl + S    │ editorFocus│ System   │
│ 打开搜索     │ Ctrl + F    │ editorFocus│ System   │
│ 打开搜索结果 │ Enter       │ searchFocus│ System   │
│ ...          │             │            │          │
└──────────────┴─────────────┴────────────┴──────────┘

四列建议固定为:

字段 用途
Command 命令显示名称
Keybinding 快捷键
When 生效条件
Source 来源

建议允许额外开启:

  • Command ID
  • Category
  • Conflict
  • Platform

但默认隐藏,避免页面过重。


3. Command 数据模型

每个可执行功能都必须注册成 Command。

建议定义:

interface Command {
  id: string
  title: string
  category?: string

  description?: string

  icon?: string

  enablement?: string

  source: 'system' | 'extension'
}

例如:

{
  id: 'editor.action.save',
  title: 'Save',
  category: 'File',
  source: 'system'
}

Markdown 产品可以是:

{
  id: 'markdown.openPreview',
  title: 'Open Preview',
  category: 'Markdown',
  source: 'system'
}

Command ID 设计要求

必须:

  • 全局唯一
  • 稳定
  • 不随 UI 文案改变

推荐格式:

domain.action
domain.object.action

例如:

file.save
file.open

editor.copy
editor.insertLineAfter

search.open
search.openResult

outline.reveal
outline.refresh

markdown.openPreview
markdown.insertTable

不要使用:

saveButtonClicked
openSearchPanel2

这种和 UI 实现耦合的命名。


4. Keybinding 数据模型

这是整个系统的核心。

建议:

interface Keybinding {
  id: string

  command: string

  key: string

  when?: string

  args?: unknown

  source:
    | 'system'
    | 'extension'
    | 'user'

  platform?: {
    windows?: string
    mac?: string
    linux?: string
  }
}

例如:

{
  id: 'system.save',
  command: 'file.save',
  key: 'ctrl+s',
  when: 'editorFocus',
  source: 'system'
}

5. 支持 Chord 快捷键

必须支持 VS Code 式的二段快捷键:

Ctrl+K Ctrl+S

内部不要把它当作一个普通按键字符串处理。

建议模型:

type KeyStroke = {
  ctrl?: boolean
  shift?: boolean
  alt?: boolean
  meta?: boolean
  key: string
}

interface KeyboardShortcut {
  strokes: KeyStroke[]
}

例如:

{
  strokes: [
    {
      ctrl: true,
      key: 'k'
    },
    {
      ctrl: true,
      key: 's'
    }
  ]
}

第一阶段建议最多支持:

2 strokes

也就是:

Ctrl+K Ctrl+S

不要一开始支持无限 chord。


6. When Context 系统

这个模块建议直接借鉴 VS Code 思路。

一个快捷键是否触发,取决于:

Key Match
+
When Expression Match

例如:

{
  key: 'enter',
  command: 'search.openResult',
  when: 'searchResultFocus'
}

同时:

{
  key: 'enter',
  command: 'dialog.confirm',
  when: 'dialogFocus'
}

完全可以共存。


7. Context Key 设计

应用内部维护一个 Context Store。

例如:

{
  editorFocus: true,
  editorHasSelection: false,

  searchVisible: false,
  searchInputFocus: false,

  outlineVisible: true,
  outlineFocus: false,

  modalOpen: false,

  platform: 'windows'
}

每一个状态称为:

Context Key

建议常用 Context Keys:

Editor

editorFocus
editorTextFocus
editorHasSelection
editorReadonly
editorLanguage
editorDirty

Search

searchVisible
searchFocus
searchInputFocus
searchResultFocus
searchHasResults

Outline

outlineVisible
outlineFocus
outlineItemFocus

UI

sidebarVisible
sidebarFocus

panelVisible
panelFocus

dialogOpen
modalOpen

inputFocus

Document

documentOpen
documentDirty

markdownDocument
textDocument

8. When 表达式语法

建议第一版支持:

&&
||
!
==
!=

例如:

editorFocus && !editorReadonly

或者:

searchVisible && searchResultFocus

或者:

editorLanguage == markdown

暂时不建议第一版支持:

regex
in
not in
复杂函数调用

否则解析器和调试复杂度会明显提高。


9. 快捷键触发算法

建议实现流程:

keydown
   ↓
Normalize KeyboardEvent
   ↓
匹配 key
   ↓
查找所有候选 Keybinding
   ↓
检查 when
   ↓
按照优先级排序
   ↓
执行最高优先级 command

候选项:

const candidates =
  keybindings.filter(binding =>
    matchesKey(binding, event)
  )

然后:

const valid = candidates.filter(binding =>
  evaluateWhen(binding.when, context)
)

最后选择:

User
>
Extension
>
System

10. 优先级规则

建议明确规定:

User Keybinding
    ↓
Extension Keybinding
    ↓
System Keybinding

用户配置永远最高。

如果同一来源内部有重复:

更具体的 when
>
更宽泛的 when

例如:

Ctrl+Enter
when: searchResultFocus

优先于:

Ctrl+Enter
when: editorFocus

如果仍然无法区分:

最后注册者优先

但这种情况 UI 中应该提示冲突。


11. Source 设计

截图中的 Source 很重要。

建议来源分为:

System
User
Extension

Extension 再显示:

Git
Markdown
Your Extension Name

例如:

System
Markdown Tools
GitLens
User

UI 可以表现:

Markdown Preview         Ctrl+K V     markdownEditor     System

Git: Commit              Ctrl+Enter   gitCommitFocus      Git

12. 搜索设计

顶部搜索框不能只搜索 Command。

必须同时支持:

命令名称
Command ID
快捷键
When
Source

例如搜索:

ctrl+k

返回所有以:

Ctrl+K

开头的快捷键。

搜索:

@source:user

只查看用户快捷键。


13. 建议支持 Search Filter Syntax

可以参考 VS Code,但第一版不用过多。

建议支持:

@keybinding:
@command:
@when:
@source:

例如:

@keybinding:ctrl+k
@source:user
@command:search

也可以支持:

@conflict

快速查看冲突快捷键。


14. 编辑快捷键交互

用户点击快捷键单元格:

Ctrl + S

进入录制模式:

┌─────────────────────┐
│ Press desired keys  │
│                     │
│ Ctrl + Shift + S    │
└─────────────────────┘

实现上:

keydown => preventDefault()

然后记录。

Esc

取消录制

Enter

确认快捷键

但注意:

如果正在录制:

Enter

本身也可能是用户想绑定的键。

因此建议:

快捷键捕获完成后自动确认

而不是:

Enter = 保存

否则用户无法配置:

Enter

15. 行操作

鼠标悬停到某一行:

点击后菜单:

Change Keybinding
Add Keybinding
Remove Keybinding
Change When Expression
Reset Keybinding
Copy Command ID
Copy as JSON

建议对应中文:

更改快捷键
添加快捷键
删除快捷键
修改生效条件
恢复默认
复制命令 ID
复制为 JSON

16. 一个 Command 支持多个 Keybinding

例如:

Copy

Ctrl+C
Ctrl+Insert

不要把它建模为:

command.keys = []

更推荐:

一条 Binding 一行

例如:

Copy    Ctrl+C
Copy    Ctrl+Insert

原因是每一个 Binding 可以拥有自己的:

when
source
platform

例如:

Copy   Ctrl+C       editorFocus
Copy   Ctrl+C       treeFocus

这是两条不同规则。


17. 冲突检测

冲突不能简单定义成:

key 相同 = conflict

例如:

Enter → search.openResult
when searchResultFocus

以及:

Enter → dialog.confirm
when dialogOpen

通常不冲突。

真正冲突是:

相同快捷键
+
when 条件可能同时成立

第一版不需要构建完整逻辑 SAT 求解器。

可以使用一个保守算法:

明确无冲突

例如:

editorFocus

vs

searchFocus

如果它们被定义为 mutually exclusive context。

可能冲突

否则:

Potential Conflict

UI 显示:

而不是阻止用户保存。


18. 禁止硬性阻止快捷键冲突

这是一个比较重要的产品决策。

不要:

此快捷键已经使用,无法保存

应该:

Ctrl+Enter is already used by:
Search: Open Result

[Replace]
[Keep Both]
[Cancel]

因为:

不同 when 条件

本来就允许相同快捷键。


19. 删除快捷键的模型

不要修改 System 默认数据。

例如默认存在:

{
  command: 'file.save',
  key: 'ctrl+s',
  source: 'system'
}

用户删除它时:

不要 delete system row。

应该新增一条:

{
  command: '-file.save',
  key: 'ctrl+s',
  source: 'user'
}

或者内部:

{
  type: 'remove',
  command: 'file.save',
  key: 'ctrl+s'
}

这样:

Reset

时只需要删除用户 override。

系统默认立刻恢复。


20. 用户配置格式

建议暴露一个:

keybindings.json

例如:

[
  {
    "key": "ctrl+k ctrl+o",
    "command": "outline.focus"
  },
  {
    "key": "ctrl+enter",
    "command": "search.openResult",
    "when": "searchResultFocus"
  }
]

删除默认快捷键:

{
  "key": "ctrl+s",
  "command": "-file.save"
}

这个设计和 VS Code 用户认知非常接近。


21. UI 和 JSON 必须双向同步

用户在 UI 中修改:

Ctrl+K O

立即写入:

keybindings.json

用户直接编辑:

keybindings.json

UI 也实时刷新。

不要维护两套数据。

应该:

UI
   ↓
Keybinding Service
   ↓
User Keybinding Config

JSON Editor
   ↓
Keybinding Service
   ↓
UI

22. 跨平台键位

内部建议统一保存:

ctrl
shift
alt
meta

显示层:

Windows:

Ctrl+Shift+P

macOS:

⇧⌘P

或者:

Cmd+Shift+P

不要在底层保存:

Command
Option

应该 Normalize 为:

meta
alt

23. 推荐支持 Platform Override

例如:

{
  "command": "file.save",
  "key": "ctrl+s",
  "mac": "cmd+s"
}

或者程序内部:

{
  command: 'file.save',

  win: 'ctrl+s',
  linux: 'ctrl+s',
  mac: 'meta+s'
}

24. KeyboardEvent Normalization

这是开发时最容易出问题的地方之一。

不要直接依赖:

event.key

建议产生内部标准:

{
  ctrl: true,
  shift: false,
  alt: false,
  meta: false,

  code: 'KeyK'
}

推荐优先:

KeyboardEvent.code

表达物理键

同时根据产品需要决定:

layout dependent

还是:

layout independent

这一点建议早期明确,否则中文键盘、德语键盘、法语键盘后面会很麻烦。


25. Chord 状态机

例如:

Ctrl+K

可能既是:

一个完整快捷键

也可能是:

Ctrl+K Ctrl+S

的前缀。

建议规定:

只要某个 shortcut 是 chord prefix,就不要允许它同时作为完整 command。

这样可以避免:

Ctrl+K

到底立即执行还是等待第二键的问题。

状态机:

Idle
 ↓ Ctrl+K
ChordWaiting
 ↓ Ctrl+S
Execute

等待时间:

1500–2000ms

超时:

Cancel chord

页面底部可以提示:

Ctrl+K was pressed. Waiting for second key...

26. 和浏览器快捷键冲突

如果你的产品运行在 Web 环境,这部分必须单独设计。

例如浏览器保留:

Ctrl+L
Ctrl+T
Ctrl+N
Ctrl+W
Ctrl+Shift+T
F5
Ctrl+R

其中部分可以:

preventDefault()

部分浏览器不允许覆盖。

因此每个按键建议有:

reserved?: boolean

UI 中提示:

⚠ May be intercepted by browser

不要保证:

一定可以触发

27. 输入框场景必须保护

这是编辑器产品非常重要的一条。

例如:

Delete
Backspace
Ctrl+A
Ctrl+C
Ctrl+V

在:

<input>
<textarea>
contenteditable

中应该优先保持原生行为。

可以定义:

inputFocus

系统默认规则:

global shortcuts
when: !inputFocus

例如:

{
  key: 'backspace',
  command: 'navigation.goBack',
  when: '!inputFocus'
}

而不是全局拦截。


28. 命令执行层必须和快捷键解耦

架构上应该是:

Keyboard
   ↓
Keybinding Service
   ↓
Command Service
   ↓
Command Handler

而不是:

if (ctrl && key === 's') {
  saveFile()
}

正确:

commandService.executeCommand('file.save')

快捷键只是其中一个入口。

其他入口:

菜单
右键菜单
命令面板
按钮
快捷键

全部调用同一个:

Command ID

29. 推荐整体架构

                  ┌───────────────────┐
Keyboard Event ──▶│ KeybindingService │
                  └─────────┬─────────┘
                            │
                            ▼
                   ┌─────────────────┐
                   │ ContextKeyService│
                   └─────────────────┘
                            │
                            ▼
                   ┌─────────────────┐
                   │ CommandService  │
                   └────────┬────────┘
                            │
                            ▼
                     Command Handler

配置:

System Keybindings ─┐
                    │
Extension Keybindings ──▶ Keybinding Registry
                    │
User Keybindings ───┘

30. 推荐模块拆分

开发可以直接按下面拆:

keyboard/
├── command-registry.ts
├── command-service.ts
│
├── keybinding-registry.ts
├── keybinding-service.ts
├── keybinding-parser.ts
├── keybinding-resolver.ts
├── keybinding-normalizer.ts
│
├── context-key-service.ts
├── context-expression-parser.ts
│
├── chord-state-machine.ts
│
├── conflict-detector.ts
│
└── user-keybinding-storage.ts

UI:

keyboard-shortcuts/
├── KeyboardShortcutsPage
├── KeyboardShortcutTable
├── ShortcutRecorder
├── ConflictDialog
├── WhenEditor
└── ShortcutSearch

31. MVP 范围

我建议第一版不要一次做成完整 VS Code。

P0

必须:

  • Command Registry
  • 系统默认快捷键
  • 用户修改快捷键
  • 添加/删除快捷键
  • 单键组合
  • 双段 Chord
  • when
  • 搜索
  • Source
  • Reset
  • 冲突提示
  • 本地持久化

P1

增加:

  • JSON 配置
  • Command ID 搜索
  • 高级过滤器
  • macOS / Windows / Linux 独立配置
  • 插件快捷键
  • 冲突页面

P2

再考虑:

  • Keybinding Profiles
  • 云同步
  • 导入/导出
  • Vim / Emacs preset
  • Shortcut Cheatsheet
  • 用户统计和推荐

32. 最终用户视角

用户进入:

Settings
→ Keyboard Shortcuts

看到:

Command                  Keybinding             When                 Source

Save                     Ctrl+S                 editorFocus           System
Open File                Ctrl+O                                       System
Quick Open               Ctrl+P                                       System
Command Palette          Ctrl+Shift+P                                 System

Outline: Focus           Ctrl+K Ctrl+O          documentOpen          User

Search: Open Result      Enter                  searchResultFocus     System

右键某一项:

Change Keybinding
Add Keybinding
Remove Keybinding
Change When Expression
Reset
Copy Command ID

这基本就是一套可以真正开发,而不是只停留在快捷键推荐层面的产品方案

如果你这个需求是准备实际落地,我建议开发时把核心抽象固定成这四个:

Command
Keybinding
ContextKey
KeybindingResolver

只要这四层设计正确,后面无论加菜单、命令面板、插件、用户自定义、同步,基本都不用推翻快捷键系统。

MVP P0 实施记录

phase-5-hosted-sync-beta

sync-flow

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