Add mobile terminal Space shortcut (#4335)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo Hong 2026-05-31 23:35:31 -04:00 committed by GitHub
parent eaae2dd4ce
commit a34a6bec7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 4 deletions

View File

@ -23,6 +23,21 @@ describe('TERMINAL_ACCESSORY_KEYS', () => {
})
})
it('includes a non-repeatable Space default key near the primary editing keys', () => {
const ids = TERMINAL_ACCESSORY_KEYS.map((key) => key.id)
expect(TERMINAL_ACCESSORY_KEYS.find((candidate) => candidate.id === 'space')).toEqual({
id: 'space',
label: 'Space',
bytes: ' ',
accessibilityLabel: 'Space'
})
expect(ids.indexOf('space')).toBeGreaterThan(ids.indexOf('shiftTab'))
expect(ids.indexOf('space')).toBeLessThan(ids.indexOf('backspace'))
expect(ids.indexOf('space')).toBeLessThan(ids.indexOf('delete'))
expect(ids.indexOf('space')).toBeLessThan(ids.indexOf('arrowUp'))
})
it('has unique non-empty built-in ids', () => {
const ids = TERMINAL_ACCESSORY_KEYS.map((key) => key.id)
@ -63,6 +78,24 @@ describe('TERMINAL_ACCESSORY_KEYS', () => {
})
})
it('builds custom Space shortcut bytes with terminal modifiers', () => {
expect(buildTerminalShortcutKey({ key: 'space', modifiers: [] })).toEqual({
label: 'Space',
bytes: ' ',
accessibilityLabel: 'Space'
})
expect(buildTerminalShortcutKey({ key: 'space', modifiers: ['ctrl'] })).toEqual({
label: 'Ctrl+Space',
bytes: '\x00',
accessibilityLabel: 'Ctrl Space'
})
expect(buildTerminalShortcutKey({ key: 'space', modifiers: ['alt'] })).toEqual({
label: 'Alt+Space',
bytes: '\x1b ',
accessibilityLabel: 'Alt Space'
})
})
it('builds modified special-key terminal sequences', () => {
expect(buildTerminalShortcutKey({ key: 'tab', modifiers: ['shift'] })).toEqual({
label: 'Shift+Tab',

View File

@ -205,6 +205,7 @@ export const TERMINAL_ACCESSORY_KEYS: TerminalAccessoryKey[] = [
{ id: 'enter', label: 'Enter', bytes: '\r', accessibilityLabel: 'Enter' },
// Why: terminal apps recognize ESC [ Z as the reverse-tab sequence.
{ id: 'shiftTab', label: 'Shift+Tab', bytes: '\x1b[Z', accessibilityLabel: 'Shift Tab' },
{ id: 'space', label: 'Space', bytes: ' ', accessibilityLabel: 'Space' },
{ id: 'backspace', label: '⌫', bytes: '\x7f', accessibilityLabel: 'Backspace', repeatable: true },
{
id: 'delete',

View File

@ -21,16 +21,27 @@ vi.mock('@react-native-async-storage/async-storage', () => ({
default: asyncStorageMock
}))
function oldBuiltInIdsBeforeSpace(): string[] {
return getDefaultTerminalAccessoryBuiltInIds().filter((id) => id !== 'space')
}
describe('terminal accessory layout', () => {
beforeEach(() => {
asyncStorageMock.getItem.mockReset()
asyncStorageMock.setItem.mockReset()
})
it('defaults include enter', () => {
expect(getDefaultTerminalAccessoryBuiltInIds()).toContain('enter')
expect(getVisibleTerminalAccessoryKeys(getDefaultTerminalAccessoryBuiltInIds())).toContainEqual(
expect.objectContaining({ id: 'enter', bytes: '\r' })
it('defaults include Space near Enter, Tab, and Shift+Tab', () => {
const ids = getDefaultTerminalAccessoryBuiltInIds()
expect(ids).toContain('enter')
expect(ids).toContain('space')
expect(ids.indexOf('space')).toBeGreaterThan(ids.indexOf('shiftTab'))
expect(ids.indexOf('space')).toBeLessThan(ids.indexOf('backspace'))
expect(ids.indexOf('space')).toBeLessThan(ids.indexOf('delete'))
expect(ids.indexOf('space')).toBeLessThan(ids.indexOf('arrowUp'))
expect(getVisibleTerminalAccessoryKeys(ids)).toContainEqual(
expect.objectContaining({ id: 'space', bytes: ' ', accessibilityLabel: 'Space' })
)
})
@ -94,6 +105,40 @@ describe('terminal accessory layout', () => {
).toEqual(['escape'])
})
it('migrates Space into old personalized layouts without reordering existing visible keys', () => {
const oldBuiltInIds = oldBuiltInIdsBeforeSpace()
expect(
normalizeTerminalAccessoryLayoutPreference({
version: 1,
visibleBuiltInIds: ['tab', 'enter', 'shiftTab'],
knownBuiltInIds: oldBuiltInIds
}).visibleBuiltInIds
).toEqual(['tab', 'enter', 'shiftTab', 'space'])
})
it('shows Space once for an all-hidden old layout', () => {
const oldBuiltInIds = oldBuiltInIdsBeforeSpace()
expect(
normalizeTerminalAccessoryLayoutPreference({
version: 1,
visibleBuiltInIds: [],
knownBuiltInIds: oldBuiltInIds
}).visibleBuiltInIds
).toEqual(['space'])
})
it('keeps Space hidden after that choice is persisted with current known ids', () => {
const visibleBuiltInIds = getDefaultTerminalAccessoryBuiltInIds().filter((id) => id !== 'space')
const persisted = createTerminalAccessoryLayoutPreference(visibleBuiltInIds)
expect(persisted.knownBuiltInIds).toContain('space')
expect(normalizeTerminalAccessoryLayoutPreference(persisted).visibleBuiltInIds).not.toContain(
'space'
)
})
it('keeps hidden known defaults hidden, including an all-hidden layout', () => {
const current = ['escape', 'tab', 'enter']