mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
Use Ctrl+K for search on Windows and Linux (#39767)
* Use Ctrl+K for search on Linux and Windows Preserve existing search and terminal clear shortcuts for legacy profiles until reset. * Preserve existing Windows and Linux search shortcuts on rollout * Preserve saved shortcuts in new profiles * Keep window shortcut tests independent of defaults
This commit is contained in:
@@ -205,7 +205,7 @@ describe('KeybindingService profile defaults', () => {
|
||||
})
|
||||
|
||||
it.each(['linux', 'win32'] as const)(
|
||||
'keeps tab defaults local and search unchanged on %s',
|
||||
'preserves legacy search and tab defaults on %s',
|
||||
(platform) => {
|
||||
const older = new KeybindingService({
|
||||
homePath: home,
|
||||
@@ -217,7 +217,9 @@ describe('KeybindingService profile defaults', () => {
|
||||
'tab.nextSameType': ['Mod+Shift+BracketRight'],
|
||||
'tab.previousSameType': ['Mod+Shift+BracketLeft'],
|
||||
'tab.nextAllTypes': ['Mod+Alt+BracketRight'],
|
||||
'tab.previousAllTypes': ['Mod+Alt+BracketLeft']
|
||||
'tab.previousAllTypes': ['Mod+Alt+BracketLeft'],
|
||||
'worktree.palette': ['Mod+Shift+J'],
|
||||
'terminal.clear': ['Mod+K']
|
||||
})
|
||||
older.setActionBindings('tab.nextAllTypes', [])
|
||||
const reset = older.setActionBindings('tab.nextSameType', null)
|
||||
@@ -231,6 +233,31 @@ describe('KeybindingService profile defaults', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['linux', 'win32'] as const)(
|
||||
'uses Ctrl+K for new profiles and preserves old shortcuts until reset on %s',
|
||||
(platform) => {
|
||||
const fresh = new KeybindingService({ homePath: home, platform })
|
||||
const older = new KeybindingService({ homePath: home, platform, legacySearchDefaults: true })
|
||||
const ctrlK = { key: 'k', code: 'KeyK', meta: false, control: true, alt: false, shift: false }
|
||||
expect(
|
||||
keybindingMatchesAction('worktree.palette', ctrlK, platform, fresh.getOverrides())
|
||||
).toBe(true)
|
||||
expect(keybindingMatchesAction('terminal.clear', ctrlK, platform, older.getOverrides())).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
getEffectiveKeybindingsForAction('worktree.palette', platform, older.getOverrides())
|
||||
).toEqual(['Mod+Shift+J'])
|
||||
const reset = older.setActionBindings('worktree.palette', null)
|
||||
expect(keybindingMatchesAction('worktree.palette', ctrlK, platform, reset.overrides)).toBe(
|
||||
true
|
||||
)
|
||||
expect(keybindingMatchesAction('terminal.clear', ctrlK, platform, reset.overrides)).toBe(
|
||||
false
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('yields inherited tab shortcuts to explicit assignments during loads and settings writes', () => {
|
||||
write({ platforms: { darwin: { 'tab.previousAllTypes': ['Mod+Shift+BracketLeft'] } } })
|
||||
const older = new KeybindingService({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
LEGACY_MAC_SEARCH_BINDINGS,
|
||||
getLegacySearchBindings,
|
||||
LEGACY_TAB_SWITCH_BINDINGS,
|
||||
type KeybindingActionId,
|
||||
type KeybindingFileSnapshot,
|
||||
@@ -33,9 +33,7 @@ export class KeybindingService {
|
||||
this.platform = options.platform ?? process.platform
|
||||
this.defaultOverrides = {
|
||||
...(options.legacyTabSwitchDefaults ? LEGACY_TAB_SWITCH_BINDINGS : {}),
|
||||
...(options.legacySearchDefaults && this.platform === 'darwin'
|
||||
? LEGACY_MAC_SEARCH_BINDINGS
|
||||
: {})
|
||||
...(options.legacySearchDefaults ? getLegacySearchBindings(this.platform) : {})
|
||||
}
|
||||
const legacyOverrides = options.getLegacyOverrides?.()
|
||||
try {
|
||||
|
||||
@@ -23,7 +23,11 @@ export class LoadedCohortMigrationOperations {
|
||||
Object.keys(state.settings).length === 1 &&
|
||||
isRecord(state.settings.telemetry)
|
||||
const existingProfile = fileExistedOnLoad && !telemetryOnly
|
||||
for (const key of ['tabSwitchKeybindingSeed', 'searchKeybindingSeed'] as const) {
|
||||
for (const key of [
|
||||
'tabSwitchKeybindingSeed',
|
||||
'searchKeybindingSeed',
|
||||
'nonMacSearchKeybindingSeed'
|
||||
] as const) {
|
||||
const existing = state.settings?.[key]
|
||||
if (existing === 'legacy' || existing === 'done') {
|
||||
continue
|
||||
|
||||
@@ -109,9 +109,28 @@ describe('Store', () => {
|
||||
const store = await createStore()
|
||||
expect(store.getSettings().tabSwitchKeybindingSeed).toBe('done')
|
||||
expect(store.getSettings().searchKeybindingSeed).toBe('done')
|
||||
expect(store.getSettings().nonMacSearchKeybindingSeed).toBe('done')
|
||||
await closeTestStores()
|
||||
const reopened = await createStore()
|
||||
expect(reopened.getSettings().searchKeybindingSeed).toBe('done')
|
||||
expect(reopened.getSettings().nonMacSearchKeybindingSeed).toBe('done')
|
||||
})
|
||||
|
||||
it('keeps profiles created after the Mac rollout in the legacy non-Mac search cohort', async () => {
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
repos: [],
|
||||
worktreeMeta: {},
|
||||
ui: {},
|
||||
settings: { searchKeybindingSeed: 'done' }
|
||||
})
|
||||
const store = await createStore()
|
||||
expect(store.getSettings().nonMacSearchKeybindingSeed).toBe('legacy')
|
||||
store.flush()
|
||||
await closeTestStores()
|
||||
const reopened = await createStore()
|
||||
expect(reopened.getSettings().searchKeybindingSeed).toBe('done')
|
||||
expect(reopened.getSettings().nonMacSearchKeybindingSeed).toBe('legacy')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -79,7 +79,10 @@ export function initializeMainProcessAccountServices(): void {
|
||||
homePath: app.getPath('home'),
|
||||
getLegacyOverrides: () => store.getSettings().keybindings,
|
||||
legacyTabSwitchDefaults: store.getSettings().tabSwitchKeybindingSeed === 'legacy',
|
||||
legacySearchDefaults: store.getSettings().searchKeybindingSeed === 'legacy'
|
||||
legacySearchDefaults:
|
||||
(process.platform === 'darwin'
|
||||
? store.getSettings().searchKeybindingSeed
|
||||
: store.getSettings().nonMacSearchKeybindingSeed) === 'legacy'
|
||||
})
|
||||
browserManager.setSettingsResolver(() => ({ keybindings: state.keybindings?.getOverrides() }))
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ describe('createMainWindow', () => {
|
||||
expect(webContents.send).toHaveBeenCalledWith('ui:dictationKeyDown')
|
||||
})
|
||||
|
||||
it('forwards the search shortcut to the worktree palette toggle event', () => {
|
||||
it('forwards the configured search shortcut to the worktree palette toggle event', () => {
|
||||
const windowHandlers: Record<string, (...args: any[]) => void> = {}
|
||||
const webContents = {
|
||||
on: vi.fn((event, handler) => {
|
||||
@@ -253,27 +253,29 @@ describe('createMainWindow', () => {
|
||||
return browserWindowInstance
|
||||
})
|
||||
|
||||
createMainWindow(null)
|
||||
createMainWindow(null, {
|
||||
getKeybindings: () => ({ 'worktree.palette': ['Mod+Alt+K'] })
|
||||
})
|
||||
|
||||
const isDarwin = process.platform === 'darwin'
|
||||
for (const input of [
|
||||
{
|
||||
type: 'keyDown',
|
||||
code: isDarwin ? 'KeyK' : 'KeyJ',
|
||||
key: isDarwin ? 'k' : 'j',
|
||||
code: 'KeyK',
|
||||
key: 'k',
|
||||
meta: isDarwin,
|
||||
control: !isDarwin,
|
||||
alt: false,
|
||||
shift: !isDarwin
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
{
|
||||
type: 'keyDown',
|
||||
code: isDarwin ? 'KeyK' : 'KeyJ',
|
||||
code: 'KeyK',
|
||||
key: '',
|
||||
meta: isDarwin,
|
||||
control: !isDarwin,
|
||||
alt: false,
|
||||
shift: !isDarwin
|
||||
alt: true,
|
||||
shift: false
|
||||
}
|
||||
]) {
|
||||
const preventDefault = vi.fn()
|
||||
@@ -583,10 +585,13 @@ describe('createMainWindow', () => {
|
||||
return browserWindowInstance
|
||||
})
|
||||
|
||||
createMainWindow({
|
||||
getUI: () => ({}),
|
||||
getSettings: () => ({ terminalShortcutPolicy: 'orca-first' })
|
||||
} as never)
|
||||
createMainWindow(
|
||||
{
|
||||
getUI: () => ({}),
|
||||
getSettings: () => ({ terminalShortcutPolicy: 'orca-first' })
|
||||
} as never,
|
||||
{ getKeybindings: () => ({ 'worktree.palette': ['Mod+Alt+K'] }) }
|
||||
)
|
||||
|
||||
const setFocusedListener = vi
|
||||
.mocked(ipcMain.on)
|
||||
@@ -598,12 +603,12 @@ describe('createMainWindow', () => {
|
||||
const isDarwin = process.platform === 'darwin'
|
||||
windowHandlers['before-input-event']({ preventDefault } as never, {
|
||||
type: 'keyDown',
|
||||
code: isDarwin ? 'KeyK' : 'KeyJ',
|
||||
key: isDarwin ? 'k' : 'j',
|
||||
code: 'KeyK',
|
||||
key: 'k',
|
||||
meta: isDarwin,
|
||||
control: !isDarwin,
|
||||
alt: false,
|
||||
shift: !isDarwin
|
||||
alt: true,
|
||||
shift: false
|
||||
})
|
||||
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
getKeybindingPlatform,
|
||||
getKeybindingResetBindings,
|
||||
isKeybindingActionId,
|
||||
LEGACY_MAC_SEARCH_BINDINGS,
|
||||
getLegacySearchBindings,
|
||||
normalizeKeybindingArrayForAction,
|
||||
resolveKeybindingOverrides
|
||||
} from '../../../../shared/keybindings'
|
||||
@@ -40,15 +40,21 @@ export type WebKeybindingsApi = NonNullable<PreloadApi['keybindings']>
|
||||
export const webKeybindingListeners = new Set<(snapshot: KeybindingFileSnapshot) => void>()
|
||||
|
||||
const SEARCH_DEFAULTS_STORAGE_KEY = 'orca.web.searchKeybindingSeed.v1'
|
||||
const NON_MAC_SEARCH_DEFAULTS_STORAGE_KEY = 'orca.web.nonMacSearchKeybindingSeed.v1'
|
||||
let legacySearchDefaults = false
|
||||
|
||||
function migrateWebSearchKeybindings(): void {
|
||||
const cohort = window.localStorage.getItem(SEARCH_DEFAULTS_STORAGE_KEY)
|
||||
const storageKey =
|
||||
getWebKeybindingPlatform() === 'darwin'
|
||||
? SEARCH_DEFAULTS_STORAGE_KEY
|
||||
: NON_MAC_SEARCH_DEFAULTS_STORAGE_KEY
|
||||
const cohort = window.localStorage.getItem(storageKey)
|
||||
if (cohort === 'done' || cohort === 'legacy') {
|
||||
legacySearchDefaults = cohort === 'legacy'
|
||||
return
|
||||
}
|
||||
legacySearchDefaults = [
|
||||
SEARCH_DEFAULTS_STORAGE_KEY,
|
||||
SETTINGS_STORAGE_KEY,
|
||||
KEYBINDINGS_STORAGE_KEY,
|
||||
UI_STORAGE_KEY,
|
||||
@@ -56,13 +62,11 @@ function migrateWebSearchKeybindings(): void {
|
||||
ONBOARDING_STORAGE_KEY,
|
||||
GITHUB_CACHE_STORAGE_KEY
|
||||
].some((key) => window.localStorage.getItem(key) !== null)
|
||||
window.localStorage.setItem(SEARCH_DEFAULTS_STORAGE_KEY, legacySearchDefaults ? 'legacy' : 'done')
|
||||
window.localStorage.setItem(storageKey, legacySearchDefaults ? 'legacy' : 'done')
|
||||
}
|
||||
|
||||
function getWebDefaultOverrides(): KeybindingOverrides {
|
||||
return legacySearchDefaults && getWebKeybindingPlatform() === 'darwin'
|
||||
? LEGACY_MAC_SEARCH_BINDINGS
|
||||
: {}
|
||||
return legacySearchDefaults ? getLegacySearchBindings(getWebKeybindingPlatform()) : {}
|
||||
}
|
||||
|
||||
export function createEmptyWebKeybindingDocument(): WebKeybindingDocument {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getEffectiveKeybindingsForAction } from '../../../shared/keybindings'
|
||||
import {
|
||||
getEffectiveKeybindingsForAction,
|
||||
keybindingMatchesAction
|
||||
} from '../../../shared/keybindings'
|
||||
import { installApi, installBrowserGlobals } from './web-preload-api-test-harness'
|
||||
|
||||
describe('web keybindings preload API', () => {
|
||||
@@ -55,6 +58,37 @@ describe('web keybindings preload API', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['Linux x86_64', 'linux'],
|
||||
['Windows NT 10.0', 'win32']
|
||||
] as const)(
|
||||
'preserves existing %s web shortcuts and resets to Ctrl+K',
|
||||
async (userAgent, platform) => {
|
||||
const { storage } = installBrowserGlobals(userAgent)
|
||||
storage.setItem('orca.web.settings.v1', '{}')
|
||||
storage.setItem('orca.web.searchKeybindingSeed.v1', 'done')
|
||||
const { installWebPreloadApi } = await import('./web-preload-api')
|
||||
installWebPreloadApi()
|
||||
const old = await window.api.keybindings.get()
|
||||
expect(getEffectiveKeybindingsForAction('worktree.palette', platform, old.overrides)).toEqual(
|
||||
['Mod+Shift+J']
|
||||
)
|
||||
expect(getEffectiveKeybindingsForAction('terminal.clear', platform, old.overrides)).toEqual([
|
||||
'Mod+K'
|
||||
])
|
||||
const reset = await window.api.keybindings.setAction({
|
||||
actionId: 'worktree.palette',
|
||||
bindings: null
|
||||
})
|
||||
expect(
|
||||
getEffectiveKeybindingsForAction('worktree.palette', platform, reset.overrides)
|
||||
).toEqual(['Mod+K'])
|
||||
expect(getEffectiveKeybindingsForAction('terminal.clear', platform, reset.overrides)).toEqual(
|
||||
[]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['common', 'platform'])(
|
||||
'keeps an explicit %s palette chord ahead of the generated clear shortcut',
|
||||
async (section) => {
|
||||
@@ -77,6 +111,25 @@ describe('web keybindings preload API', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('keeps saved Ctrl+K clear bindings reachable after the earlier Mac rollout', async () => {
|
||||
const { storage } = installBrowserGlobals('Windows NT 10.0')
|
||||
storage.setItem('orca.web.searchKeybindingSeed.v1', 'done')
|
||||
storage.setItem(
|
||||
'orca.web.keybindings.v1',
|
||||
JSON.stringify({ platforms: { win32: { 'terminal.clear': ['Ctrl+K'] } } })
|
||||
)
|
||||
const { installWebPreloadApi } = await import('./web-preload-api')
|
||||
installWebPreloadApi()
|
||||
installWebPreloadApi()
|
||||
const { overrides } = await window.api.keybindings.get()
|
||||
const ctrlK = { key: 'k', control: true }
|
||||
expect(keybindingMatchesAction('terminal.clear', ctrlK, 'win32', overrides)).toBe(true)
|
||||
expect(keybindingMatchesAction('worktree.palette', ctrlK, 'win32', overrides)).toBe(false)
|
||||
await expect(
|
||||
window.api.keybindings.setAction({ actionId: 'worktree.palette', bindings: null })
|
||||
).rejects.toThrow('conflicts')
|
||||
})
|
||||
|
||||
it('leaves an invalid Mac section intact and retries after repair', async () => {
|
||||
const { storage } = installBrowserGlobals('MacIntel')
|
||||
const raw = '{"platforms":{"darwin":null}}'
|
||||
@@ -112,14 +165,17 @@ describe('web keybindings preload API', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps a fresh web profile on the new defaults after saving settings and restarting', async () => {
|
||||
const { api, storage } = await installApi('MacIntel')
|
||||
expect((await api.keybindings.get()).overrides).toEqual({})
|
||||
storage.setItem('orca.web.settings.v1', '{}')
|
||||
const { installWebPreloadApi } = await import('./web-preload-api')
|
||||
installWebPreloadApi()
|
||||
expect((await window.api.keybindings.get()).overrides).toEqual({})
|
||||
})
|
||||
it.each(['MacIntel', 'Windows NT 10.0'])(
|
||||
'keeps a fresh %s web profile on the new defaults after saving settings and restarting',
|
||||
async (userAgent) => {
|
||||
const { api, storage } = await installApi(userAgent)
|
||||
expect((await api.keybindings.get()).overrides).toEqual({})
|
||||
storage.setItem('orca.web.settings.v1', '{}')
|
||||
const { installWebPreloadApi } = await import('./web-preload-api')
|
||||
installWebPreloadApi()
|
||||
expect((await window.api.keybindings.get()).overrides).toEqual({})
|
||||
}
|
||||
)
|
||||
|
||||
it('does not overwrite unreadable shortcuts while upgrading an existing web profile', async () => {
|
||||
const { storage } = installBrowserGlobals('MacIntel')
|
||||
|
||||
@@ -5,10 +5,59 @@ import {
|
||||
findKeybindingConflicts,
|
||||
getEffectiveKeybindingsForAction,
|
||||
keybindingMatchesAction,
|
||||
KEYBINDING_DEFINITIONS
|
||||
KEYBINDING_DEFINITIONS,
|
||||
resolveKeybindingOverrides,
|
||||
getLegacySearchBindings
|
||||
} from './keybindings'
|
||||
|
||||
describe('keybindings', () => {
|
||||
it('yields fresh-profile search to a saved global Ctrl+K assignment', () => {
|
||||
expect(resolveKeybindingOverrides('linux', { 'worktree.quickOpen': ['Ctrl+K'] }, {})).toEqual({
|
||||
'worktree.palette': [],
|
||||
'worktree.quickOpen': ['Ctrl+K']
|
||||
})
|
||||
})
|
||||
|
||||
it('yields fresh-profile search to a saved terminal Ctrl+K assignment', () => {
|
||||
expect(resolveKeybindingOverrides('win32', { 'terminal.clear': ['Ctrl+K'] }, {})).toEqual({
|
||||
'worktree.palette': [],
|
||||
'terminal.clear': ['Ctrl+K']
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the inherited search chord available for terminal-first chat toggling', () => {
|
||||
const overrides = resolveKeybindingOverrides('win32', {}, getLegacySearchBindings('win32'))
|
||||
const ctrlShiftJ = { key: 'j', control: true, shift: true }
|
||||
const terminalFirst = { context: 'terminal', terminalShortcutPolicy: 'terminal-first' } as const
|
||||
expect(
|
||||
keybindingMatchesAction(
|
||||
'terminal.toggleNativeChat',
|
||||
ctrlShiftJ,
|
||||
'win32',
|
||||
overrides,
|
||||
terminalFirst
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
keybindingMatchesAction('worktree.palette', ctrlShiftJ, 'win32', overrides, terminalFirst)
|
||||
).toBe(false)
|
||||
expect(
|
||||
findKeybindingConflicts('win32', overrides, { includeMainProcessInterception: true })
|
||||
).toEqual([])
|
||||
expect(
|
||||
keybindingMatchesAction(
|
||||
'terminal.toggleNativeChat',
|
||||
ctrlShiftJ,
|
||||
'win32',
|
||||
{
|
||||
...overrides,
|
||||
'worktree.navigateDown': ['Ctrl+Shift+J']
|
||||
},
|
||||
terminalFirst
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('uses overrides as the complete effective binding list for an action', () => {
|
||||
const overrides = {
|
||||
'worktree.quickOpen': ['Ctrl+Alt+O', 'not-a-shortcut']
|
||||
|
||||
@@ -36,11 +36,7 @@ export const KEYBINDING_DEFINITION_CORE_1: readonly KeybindingDefinition[] = [
|
||||
group: 'Global',
|
||||
scope: 'global',
|
||||
searchKeywords: ['shortcut', 'global', 'worktree', 'switch', 'jump'],
|
||||
defaultBindings: {
|
||||
darwin: ['Mod+K'],
|
||||
linux: ['Mod+Shift+J'],
|
||||
win32: ['Mod+Shift+J']
|
||||
}
|
||||
defaultBindings: platformBindings(['Mod+K'])
|
||||
},
|
||||
{
|
||||
id: 'worktree.navigateUp',
|
||||
|
||||
@@ -260,7 +260,7 @@ export const KEYBINDING_DEFINITION_CORE_3: readonly KeybindingDefinition[] = [
|
||||
group: 'Terminal Panes',
|
||||
scope: 'terminal',
|
||||
searchKeywords: ['shortcut', 'pane', 'clear'],
|
||||
defaultBindings: { darwin: [], linux: ['Mod+K'], win32: ['Mod+K'] }
|
||||
defaultBindings: platformBindings([])
|
||||
},
|
||||
{
|
||||
id: 'terminal.focusNextPane',
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
AgentTabActionId,
|
||||
KeybindingActionId,
|
||||
KeybindingDefinition,
|
||||
KeybindingOverrides,
|
||||
PluginKeybindingActionId
|
||||
} from './types'
|
||||
import type { TuiAgent } from '../agent-launch/catalog/tui-agent'
|
||||
@@ -53,9 +54,11 @@ export const LEGACY_TAB_SWITCH_BINDINGS: Readonly<Partial<Record<KeybindingActio
|
||||
'tab.previousAllTypes': ['Mod+Alt+BracketLeft']
|
||||
}
|
||||
|
||||
export const LEGACY_MAC_SEARCH_BINDINGS: Readonly<Partial<Record<KeybindingActionId, string[]>>> = {
|
||||
'worktree.palette': ['Mod+J'],
|
||||
'terminal.clear': ['Mod+K']
|
||||
export function getLegacySearchBindings(platform: NodeJS.Platform): KeybindingOverrides {
|
||||
return {
|
||||
'worktree.palette': [platform === 'darwin' ? 'Mod+J' : 'Mod+Shift+J'],
|
||||
'terminal.clear': ['Mod+K']
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFINITIONS_BY_ID = new Map<KeybindingActionId, KeybindingDefinition>(
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DEFINITIONS_BY_ID,
|
||||
KEYBINDING_DEFINITIONS,
|
||||
getKeybindingPlatform,
|
||||
getLegacySearchBindings,
|
||||
isDigitIndexActionId
|
||||
} from './definitions'
|
||||
import { keybindingConflictIdentities } from './matching-key'
|
||||
@@ -84,6 +85,18 @@ export function bindingsOverlap(
|
||||
)
|
||||
}
|
||||
|
||||
export function isDefaultKeybinding(
|
||||
definition: KeybindingDefinition,
|
||||
binding: string,
|
||||
platform: NodeJS.Platform
|
||||
): boolean {
|
||||
// Previous defaults retain intentional sharing when profiles inherit or save them.
|
||||
return [
|
||||
...getDefaultBindings(definition, platform),
|
||||
...(getLegacySearchBindings(platform)[definition.id] ?? [])
|
||||
].some((ownDefault) => bindingsOverlap(definition, binding, definition, ownDefault, platform))
|
||||
}
|
||||
|
||||
export function getEffectiveKeybindingsForAction(
|
||||
actionId: KeybindingActionId,
|
||||
platform: NodeJS.Platform,
|
||||
@@ -124,9 +137,7 @@ export function getEffectiveKeybindingsForDefinition(
|
||||
getConfiguredBindings(other.id, platform, overrides, other).some(
|
||||
(claim) =>
|
||||
bindingsOverlap(definition, binding, other, claim, platform) &&
|
||||
!getDefaultBindings(other, platform).some((ownDefault) =>
|
||||
bindingsOverlap(other, claim, other, ownDefault, platform)
|
||||
)
|
||||
!isDefaultKeybinding(other, claim, platform)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getEffectiveKeybindingsForAction,
|
||||
getEffectiveKeybindingsForDefinition,
|
||||
getDefaultBindings,
|
||||
isDefaultKeybinding,
|
||||
keybindingConflictGroups,
|
||||
bindingsOverlap,
|
||||
isKeybindingInterceptedBeforeBrowserChords,
|
||||
@@ -227,13 +228,7 @@ export function findKeybindingConflictsForDefinitions(
|
||||
) ||
|
||||
!Array.from(actionIds).every((otherId) => {
|
||||
const other = byId.get(otherId)
|
||||
return (
|
||||
otherId === id ||
|
||||
(other &&
|
||||
getDefaultBindings(other, platform).some((ownDefault) =>
|
||||
bindingsOverlap(definition, binding, other, ownDefault, platform)
|
||||
))
|
||||
)
|
||||
return otherId === id || (other && isDefaultKeybinding(other, binding, platform))
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -7,6 +7,8 @@ export type KeybindingGlobalSettings = {
|
||||
keybindings?: KeybindingOverrides
|
||||
/** Profile-owned defaults; legacy profiles keep their shortcuts without writing the shared file. */
|
||||
tabSwitchKeybindingSeed?: 'pending' | 'done' | 'legacy'
|
||||
/** The Mac search/clear rollout cohort, independent of the shared shortcut choices. */
|
||||
/** The original Mac search/clear rollout cohort. */
|
||||
searchKeybindingSeed?: 'pending' | 'done' | 'legacy'
|
||||
/** The later Windows/Linux rollout must not reuse the Mac cohort. */
|
||||
nonMacSearchKeybindingSeed?: 'pending' | 'done' | 'legacy'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { findKeybindingConflicts } from './formatting'
|
||||
import {
|
||||
getKeybindingDefinition,
|
||||
isKeybindingActionId,
|
||||
LEGACY_MAC_SEARCH_BINDINGS,
|
||||
getLegacySearchBindings,
|
||||
LEGACY_TAB_SWITCH_BINDINGS
|
||||
} from './definitions'
|
||||
import { getDefaultBindings } from './effective'
|
||||
@@ -30,7 +30,15 @@ export function resolveKeybindingOverrides(
|
||||
for (const conflict of conflicts) {
|
||||
const identity = getKeybindingConflictIdentity(conflict.binding, platform)
|
||||
for (const actionId of conflict.actionIds) {
|
||||
const bindings = inherited[actionId]
|
||||
const definition = getKeybindingDefinition(actionId)
|
||||
// Fresh profiles must also yield the new search default to shared explicit choices.
|
||||
const bindings =
|
||||
inherited[actionId] ??
|
||||
(actionId === 'worktree.palette' &&
|
||||
definition &&
|
||||
!Object.hasOwn(explicitOverrides, actionId)
|
||||
? getDefaultBindings(definition, platform)
|
||||
: undefined)
|
||||
if (!bindings) {
|
||||
continue
|
||||
}
|
||||
@@ -61,7 +69,7 @@ export function getKeybindingResetBindings(
|
||||
// Persist rollout resets so another profile's inherited defaults cannot reappear.
|
||||
const migratedAction =
|
||||
Object.hasOwn(LEGACY_TAB_SWITCH_BINDINGS, actionId) ||
|
||||
(platform === 'darwin' && Object.hasOwn(LEGACY_MAC_SEARCH_BINDINGS, actionId))
|
||||
Object.hasOwn(getLegacySearchBindings(platform), actionId)
|
||||
const definition = getKeybindingDefinition(actionId)
|
||||
return migratedAction && definition ? getDefaultBindings(definition, platform) : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user