mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
Fixes #23470 Co-authored-by: fruit <200041037+guozi-lab@users.noreply.github.com>
470 lines
17 KiB
TypeScript
470 lines
17 KiB
TypeScript
// Per-platform default bindings and action metadata in the registry.
|
|
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
agentTabActionId,
|
|
getKeybindingDefinition,
|
|
findKeybindingConflicts,
|
|
formatKeybindingList,
|
|
getEffectiveKeybindingsForAction,
|
|
keybindingFromInput,
|
|
LEGACY_TAB_SWITCH_BINDINGS,
|
|
keybindingMatchesAction
|
|
} from './keybindings'
|
|
import type { KeybindingActionId } from './keybindings'
|
|
import { ALL_TUI_AGENTS } from './tui-agent-display-names'
|
|
|
|
describe('keybindings', () => {
|
|
it('defines a default shortcut for opening markdown notes', () => {
|
|
expect(getEffectiveKeybindingsForAction('tab.openMarkdown', 'darwin')).toEqual(['Mod+Shift+O'])
|
|
expect(formatKeybindingList(['Mod+Shift+O'], 'darwin')).toBe('⌘⇧O')
|
|
})
|
|
|
|
it.each(['darwin', 'linux', 'win32'] as const)(
|
|
'binds editor word wrap to Alt+Z on %s',
|
|
(platform) => {
|
|
expect(getEffectiveKeybindingsForAction('editor.toggleWordWrap', platform)).toEqual(['Alt+Z'])
|
|
}
|
|
)
|
|
|
|
it('matches macOS Option+Z through its composed key', () => {
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'editor.toggleWordWrap',
|
|
{
|
|
key: 'Ω',
|
|
code: 'KeyZ',
|
|
meta: false,
|
|
control: false,
|
|
alt: true,
|
|
shift: false
|
|
},
|
|
'darwin'
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('defines a default shortcut for adding an editor review note', () => {
|
|
expect(getEffectiveKeybindingsForAction('editor.addReviewNote', 'darwin')).toEqual([
|
|
'Mod+Shift+A'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('editor.addReviewNote', 'linux')).toEqual([
|
|
'Mod+Shift+A'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('editor.addReviewNote', 'win32')).toEqual([
|
|
'Mod+Shift+A'
|
|
])
|
|
expect(formatKeybindingList(['Mod+Shift+A'], 'darwin')).toBe('⌘⇧A')
|
|
expect(formatKeybindingList(['Mod+Shift+A'], 'linux')).toBe('Ctrl+Shift+A')
|
|
|
|
const macChord = {
|
|
key: 'a',
|
|
code: 'KeyA',
|
|
meta: true,
|
|
control: false,
|
|
alt: false,
|
|
shift: true
|
|
}
|
|
const ctrlChord = { ...macChord, meta: false, control: true }
|
|
expect(keybindingMatchesAction('editor.addReviewNote', macChord, 'darwin')).toBe(true)
|
|
expect(keybindingMatchesAction('editor.addReviewNote', ctrlChord, 'linux')).toBe(true)
|
|
expect(keybindingMatchesAction('editor.addReviewNote', ctrlChord, 'win32')).toBe(true)
|
|
|
|
const oldCtrlAltChord = {
|
|
key: 'n',
|
|
code: 'KeyN',
|
|
meta: false,
|
|
control: true,
|
|
alt: true,
|
|
shift: false
|
|
}
|
|
expect(keybindingMatchesAction('editor.addReviewNote', oldCtrlAltChord, 'linux')).toBe(false)
|
|
expect(keybindingMatchesAction('editor.addReviewNote', oldCtrlAltChord, 'win32')).toBe(false)
|
|
})
|
|
|
|
it('maps browser Find to Command on macOS and Control elsewhere', () => {
|
|
const commandF = {
|
|
key: 'f',
|
|
code: 'KeyF',
|
|
meta: true,
|
|
control: false,
|
|
alt: false,
|
|
shift: false
|
|
}
|
|
const controlF = { ...commandF, meta: false, control: true }
|
|
|
|
expect(keybindingMatchesAction('browser.find', commandF, 'darwin')).toBe(true)
|
|
expect(keybindingMatchesAction('browser.find', controlF, 'darwin')).toBe(false)
|
|
expect(keybindingMatchesAction('browser.find', controlF, 'linux')).toBe(true)
|
|
expect(keybindingMatchesAction('browser.find', controlF, 'win32')).toBe(true)
|
|
expect(keybindingMatchesAction('browser.find', commandF, 'linux')).toBe(false)
|
|
expect(keybindingMatchesAction('browser.find', commandF, 'win32')).toBe(false)
|
|
})
|
|
|
|
it('defines platform-native replace-in-editor shortcuts', () => {
|
|
expect(getEffectiveKeybindingsForAction('editor.replace', 'darwin')).toEqual(['Mod+Alt+F'])
|
|
expect(getEffectiveKeybindingsForAction('editor.replace', 'linux')).toEqual(['Mod+H'])
|
|
expect(getEffectiveKeybindingsForAction('editor.replace', 'win32')).toEqual(['Mod+H'])
|
|
expect(formatKeybindingList(['Mod+Alt+F'], 'darwin')).toBe('⌘⌥F')
|
|
expect(formatKeybindingList(['Mod+H'], 'linux')).toBe('Ctrl+H')
|
|
})
|
|
|
|
it('defines macOS-only rename shortcuts that stay conflict-free', () => {
|
|
expect(getEffectiveKeybindingsForAction('tab.rename', 'darwin')).toEqual(['Mod+R'])
|
|
expect(getEffectiveKeybindingsForAction('tab.rename', 'linux')).toEqual([])
|
|
expect(getEffectiveKeybindingsForAction('tab.rename', 'win32')).toEqual([])
|
|
expect(getEffectiveKeybindingsForAction('workspace.rename', 'darwin')).toEqual(['Mod+Alt+R'])
|
|
expect(getEffectiveKeybindingsForAction('workspace.rename', 'linux')).toEqual([])
|
|
expect(formatKeybindingList(['Mod+Alt+R'], 'darwin')).toBe('⌘⌥R')
|
|
expect(getKeybindingDefinition('tab.rename')?.searchKeywords).not.toContain('set title')
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'tab.rename',
|
|
{
|
|
key: 'r',
|
|
code: 'KeyR',
|
|
meta: true,
|
|
control: false,
|
|
alt: false,
|
|
shift: false
|
|
},
|
|
'darwin'
|
|
)
|
|
).toBe(true)
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'tab.rename',
|
|
{
|
|
key: 'r',
|
|
code: 'KeyR',
|
|
meta: false,
|
|
control: true,
|
|
alt: false,
|
|
shift: false
|
|
},
|
|
'linux'
|
|
)
|
|
).toBe(false)
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'tab.rename',
|
|
{
|
|
key: 'r',
|
|
code: 'KeyR',
|
|
meta: true,
|
|
control: false,
|
|
alt: false,
|
|
shift: false
|
|
},
|
|
'darwin',
|
|
undefined,
|
|
{ context: 'terminal', terminalShortcutPolicy: 'terminal-first' }
|
|
)
|
|
).toBe(false)
|
|
|
|
// Why: tab.rename (Mod+R) intentionally shares its binding with
|
|
// browser.reload, but the two live in different scopes (tabs vs browser),
|
|
// so customizing tab.rename to its default must not flag a conflict.
|
|
expect(findKeybindingConflicts('darwin', { 'tab.rename': ['Mod+R'] })).toEqual([])
|
|
// Why: tab/workspace rename share the same active workspace keydown path,
|
|
// so Settings must reject user overrides that make one shadow the other.
|
|
expect(findKeybindingConflicts('darwin', { 'workspace.rename': ['Mod+R'] })).toEqual([
|
|
{
|
|
binding: 'Mod+R',
|
|
actionIds: ['workspace.rename', 'tab.rename']
|
|
}
|
|
])
|
|
expect(findKeybindingConflicts('darwin', { 'tab.rename': ['Mod+Alt+R'] })).toEqual([
|
|
{
|
|
binding: 'Mod+Alt+R',
|
|
actionIds: ['workspace.rename', 'tab.rename']
|
|
}
|
|
])
|
|
})
|
|
|
|
it('defaults tab-switch chords to the swapped convention for fresh installs', () => {
|
|
// New users get the widespread mapping: Shift+bracket cycles all tabs,
|
|
// Alt+bracket cycles within the active type.
|
|
expect(getEffectiveKeybindingsForAction('tab.nextAllTypes', 'darwin')).toEqual([
|
|
'Mod+Shift+BracketRight'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('tab.previousAllTypes', 'darwin')).toEqual([
|
|
'Mod+Shift+BracketLeft'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('tab.nextSameType', 'darwin')).toEqual([
|
|
'Mod+Alt+BracketRight'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('tab.previousSameType', 'darwin')).toEqual([
|
|
'Mod+Alt+BracketLeft'
|
|
])
|
|
})
|
|
|
|
it('pins the pre-swap chords via LEGACY_TAB_SWITCH_BINDINGS for upgrading installs', () => {
|
|
// These are what the seed migration writes so pre-existing users keep the
|
|
// shortcuts they learned; overriding an action with its legacy value must
|
|
// reproduce the old effective binding.
|
|
expect(LEGACY_TAB_SWITCH_BINDINGS).toEqual({
|
|
'tab.nextSameType': ['Mod+Shift+BracketRight'],
|
|
'tab.previousSameType': ['Mod+Shift+BracketLeft'],
|
|
'tab.nextAllTypes': ['Mod+Alt+BracketRight'],
|
|
'tab.previousAllTypes': ['Mod+Alt+BracketLeft']
|
|
})
|
|
for (const [actionId, bindings] of Object.entries(LEGACY_TAB_SWITCH_BINDINGS)) {
|
|
expect(
|
|
getEffectiveKeybindingsForAction(actionId as KeybindingActionId, 'darwin', {
|
|
[actionId]: bindings
|
|
})
|
|
).toEqual(bindings)
|
|
}
|
|
})
|
|
|
|
it('defines browser history shortcuts for Logitech side-button remaps', () => {
|
|
expect(getEffectiveKeybindingsForAction('browser.back', 'darwin')).toEqual(['Mod+BracketLeft'])
|
|
expect(getEffectiveKeybindingsForAction('browser.forward', 'darwin')).toEqual([
|
|
'Mod+BracketRight'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('browser.back', 'linux')).toEqual(['Alt+ArrowLeft'])
|
|
expect(getEffectiveKeybindingsForAction('browser.forward', 'win32')).toEqual(['Alt+ArrowRight'])
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'browser.back',
|
|
{
|
|
key: '[',
|
|
code: 'BracketLeft',
|
|
meta: true,
|
|
control: false,
|
|
alt: false,
|
|
shift: false
|
|
},
|
|
'darwin'
|
|
)
|
|
).toBe(true)
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'browser.forward',
|
|
{
|
|
key: 'ArrowRight',
|
|
code: 'ArrowRight',
|
|
meta: false,
|
|
control: false,
|
|
alt: true,
|
|
shift: false
|
|
},
|
|
'linux'
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('binds close-all editor tabs to Mod+Alt+W beside tab.close', () => {
|
|
expect(getEffectiveKeybindingsForAction('tab.closeAll', 'darwin')).toEqual(['Mod+Alt+W'])
|
|
expect(getEffectiveKeybindingsForAction('tab.closeAll', 'linux')).toEqual(['Mod+Alt+W'])
|
|
expect(getEffectiveKeybindingsForAction('tab.closeAll', 'win32')).toEqual(['Mod+Alt+W'])
|
|
expect(formatKeybindingList(['Mod+Alt+W'], 'darwin')).toBe('⌘⌥W')
|
|
expect(formatKeybindingList(['Mod+Alt+W'], 'linux')).toBe('Ctrl+Alt+W')
|
|
|
|
// Why: macOS Option+W composes to a glyph (∑), so the chord must resolve
|
|
// through the physical-code fallback rather than the logical key.
|
|
const macComposedCloseAll = {
|
|
key: '∑',
|
|
code: 'KeyW',
|
|
meta: true,
|
|
control: false,
|
|
alt: true,
|
|
shift: false
|
|
}
|
|
expect(keybindingMatchesAction('tab.closeAll', macComposedCloseAll, 'darwin')).toBe(true)
|
|
const linuxCloseAll = {
|
|
key: 'w',
|
|
code: 'KeyW',
|
|
meta: false,
|
|
control: true,
|
|
alt: true,
|
|
shift: false
|
|
}
|
|
expect(keybindingMatchesAction('tab.closeAll', linuxCloseAll, 'linux')).toBe(true)
|
|
expect(
|
|
keybindingMatchesAction('tab.closeAll', linuxCloseAll, 'linux', undefined, {
|
|
context: 'terminal',
|
|
terminalShortcutPolicy: 'orca-first'
|
|
})
|
|
).toBe(true)
|
|
// Why: close-all is a workspace tab command, so terminal-first mode should
|
|
// keep passing the chord through to shells and TUIs.
|
|
expect(
|
|
keybindingMatchesAction('tab.closeAll', linuxCloseAll, 'linux', undefined, {
|
|
context: 'terminal',
|
|
terminalShortcutPolicy: 'terminal-first'
|
|
})
|
|
).toBe(false)
|
|
|
|
// Why: Mod+Alt+W and Mod+W are neighbors; the extra Alt must keep the two
|
|
// actions from firing on each other's chord.
|
|
const macCloseActive = {
|
|
key: 'w',
|
|
code: 'KeyW',
|
|
meta: true,
|
|
control: false,
|
|
alt: false,
|
|
shift: false
|
|
}
|
|
expect(keybindingMatchesAction('tab.close', macComposedCloseAll, 'darwin')).toBe(false)
|
|
expect(keybindingMatchesAction('tab.closeAll', macCloseActive, 'darwin')).toBe(false)
|
|
|
|
// Stays in the Tabs group/scope so Settings → Shortcuts lists it for rebinding.
|
|
const definition = getKeybindingDefinition('tab.closeAll')
|
|
expect(definition?.group).toBe('Tabs')
|
|
expect(definition?.scope).toBe('tabs')
|
|
|
|
// Why: both live in the Tabs scope, so rebinding closeAll onto Mod+W must
|
|
// surface as a conflict with tab.close in Settings.
|
|
expect(findKeybindingConflicts('darwin', { 'tab.closeAll': ['Mod+W'] })).toContainEqual({
|
|
binding: 'Mod+W',
|
|
actionIds: expect.arrayContaining(['tab.close', 'tab.closeAll'])
|
|
})
|
|
})
|
|
|
|
it('defines floating workspace panel action metadata', () => {
|
|
const actionIds = [
|
|
'floatingWorkspace.maximize' as KeybindingActionId,
|
|
'floatingWorkspace.minimize' as KeybindingActionId
|
|
] as const
|
|
|
|
for (const actionId of actionIds) {
|
|
expect(getKeybindingDefinition(actionId), actionId).toMatchObject({ id: actionId })
|
|
}
|
|
})
|
|
|
|
it('assigns the floating workspace maximize default only on macOS', () => {
|
|
const maximizeAction = 'floatingWorkspace.maximize' as KeybindingActionId
|
|
|
|
expect(getEffectiveKeybindingsForAction(maximizeAction, 'darwin')).toEqual(['Mod+Alt+Shift+A'])
|
|
expect(getEffectiveKeybindingsForAction(maximizeAction, 'linux')).toEqual([])
|
|
expect(getEffectiveKeybindingsForAction(maximizeAction, 'win32')).toEqual([])
|
|
})
|
|
|
|
it('captures and round-trips the macOS Option-composed maximize chord', () => {
|
|
const maximizeAction = 'floatingWorkspace.maximize' as KeybindingActionId
|
|
|
|
// Why: macOS Option+A composes to a glyph (å), so capture must resolve the
|
|
// chord through the physical-code fallback rather than the composed key,
|
|
// matching the matcher so a user override round-trips to the same binding.
|
|
const macComposedMaximize = {
|
|
key: 'å',
|
|
code: 'KeyA',
|
|
meta: true,
|
|
control: false,
|
|
alt: true,
|
|
shift: true
|
|
}
|
|
expect(keybindingFromInput(macComposedMaximize, 'darwin')).toEqual({
|
|
ok: true,
|
|
value: 'Mod+Alt+Shift+A'
|
|
})
|
|
expect(keybindingMatchesAction(maximizeAction, macComposedMaximize, 'darwin')).toBe(true)
|
|
// The captured override formats back to the same effective shortcut.
|
|
expect(
|
|
getEffectiveKeybindingsForAction(maximizeAction, 'darwin', {
|
|
[maximizeAction]: ['Mod+Alt+Shift+A']
|
|
})
|
|
).toEqual(['Mod+Alt+Shift+A'])
|
|
expect(formatKeybindingList(['Mod+Alt+Shift+A'], 'darwin')).toBe('⌘⌥⇧A')
|
|
})
|
|
|
|
it('defines a macOS-only default for the new agent tab shortcut', () => {
|
|
expect(getEffectiveKeybindingsForAction('tab.newAgent', 'darwin')).toEqual(['Mod+Alt+T'])
|
|
expect(getEffectiveKeybindingsForAction('tab.newAgent', 'linux')).toEqual([])
|
|
expect(getEffectiveKeybindingsForAction('tab.newAgent', 'win32')).toEqual([])
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'tab.newAgent',
|
|
{ key: 't', code: 'KeyT', meta: true, control: false, alt: true, shift: false },
|
|
'darwin'
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
// Why: #8533 — both previously defaulted to Mod+Shift+E on darwin; emulator won.
|
|
it('keeps explorer on Mod+Shift+E and gives the mobile emulator a non-colliding macOS default', () => {
|
|
expect(getEffectiveKeybindingsForAction('sidebar.explorer.toggle', 'darwin')).toEqual([
|
|
'Mod+Shift+E'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('tab.newSimulator', 'darwin')).toEqual([
|
|
'Mod+Alt+Shift+E'
|
|
])
|
|
expect(getEffectiveKeybindingsForAction('tab.newSimulator', 'linux')).toEqual([])
|
|
expect(getEffectiveKeybindingsForAction('tab.newSimulator', 'win32')).toEqual([])
|
|
expect(formatKeybindingList(['Mod+Alt+Shift+E'], 'darwin')).toBe('⌘⌥⇧E')
|
|
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'sidebar.explorer.toggle',
|
|
{ key: 'e', code: 'KeyE', meta: true, control: false, alt: false, shift: true },
|
|
'darwin'
|
|
)
|
|
).toBe(true)
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'tab.newSimulator',
|
|
{ key: 'e', code: 'KeyE', meta: true, control: false, alt: false, shift: true },
|
|
'darwin'
|
|
)
|
|
).toBe(false)
|
|
expect(
|
|
keybindingMatchesAction(
|
|
'tab.newSimulator',
|
|
{ key: 'e', code: 'KeyE', meta: true, control: false, alt: true, shift: true },
|
|
'darwin'
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('defines an unassigned per-agent tab action for every TUI agent', () => {
|
|
for (const agent of ALL_TUI_AGENTS) {
|
|
const actionId = agentTabActionId(agent)
|
|
const definition = getKeybindingDefinition(actionId)
|
|
expect(definition, actionId).toBeDefined()
|
|
expect(definition?.group).toBe('Agents')
|
|
expect(definition?.scope).toBe('tabs')
|
|
expect(getEffectiveKeybindingsForAction(actionId, 'darwin')).toEqual([])
|
|
}
|
|
})
|
|
|
|
it('matches per-agent tab actions only through user overrides', () => {
|
|
const binding = { key: 'k', code: 'KeyK', meta: true, control: false, alt: true, shift: true }
|
|
expect(keybindingMatchesAction(agentTabActionId('claude'), binding, 'darwin')).toBe(false)
|
|
expect(
|
|
keybindingMatchesAction(agentTabActionId('claude'), binding, 'darwin', {
|
|
'tab.newAgent.claude': ['Mod+Alt+Shift+K']
|
|
})
|
|
).toBe(true)
|
|
})
|
|
|
|
it('binds annotate to Cmd+Shift+C on macOS', () => {
|
|
expect(getEffectiveKeybindingsForAction('browser.annotateElement', 'darwin')).toEqual([
|
|
'Mod+Shift+C'
|
|
])
|
|
})
|
|
|
|
// Ctrl+Shift+C is terminal copy on Linux and Windows, including web terminals inside a page.
|
|
it.each(['linux', 'win32'] as const)('leaves Ctrl+Shift+C to terminal copy on %s', (platform) => {
|
|
const ctrlShiftC = {
|
|
key: 'C',
|
|
code: 'KeyC',
|
|
meta: false,
|
|
control: true,
|
|
alt: false,
|
|
shift: true
|
|
}
|
|
const altShiftN = {
|
|
key: 'N',
|
|
code: 'KeyN',
|
|
meta: false,
|
|
control: false,
|
|
alt: true,
|
|
shift: true
|
|
}
|
|
|
|
expect(keybindingMatchesAction('browser.annotateElement', ctrlShiftC, platform)).toBe(false)
|
|
expect(keybindingMatchesAction('browser.annotateElement', altShiftN, platform)).toBe(true)
|
|
})
|
|
})
|