Guard terminal search shortcut navigation (#6872)

This commit is contained in:
Neil
2026-06-30 01:25:56 -07:00
committed by GitHub
parent 48f0e54c03
commit f769fc7052
2 changed files with 70 additions and 9 deletions
@@ -1,7 +1,11 @@
// src/renderer/src/components/terminal-pane/keyboard-handlers.test.ts
import { describe, it, expect } from 'vitest'
import { describe, it, expect, vi } from 'vitest'
import { FIND_QUERY_MAX_BYTES } from '@/lib/find-query-bounds'
import { matchFileSearchShortcut, matchSearchNavigate } from './keyboard-handlers'
import {
matchFileSearchShortcut,
matchSearchNavigate,
runTerminalSearchNavigation
} from './keyboard-handlers'
function makeKeyEvent(
overrides: Partial<{
@@ -87,6 +91,46 @@ describe('matchSearchNavigate', () => {
})
})
describe('runTerminalSearchNavigation', () => {
const searchState = { query: 'hello', caseSensitive: true, regex: false }
it('runs the next search through the guarded xterm path', () => {
const findNext = vi.fn(() => true)
const findPrevious = vi.fn(() => false)
const pane = { searchAddon: { findNext, findPrevious } } as unknown as Parameters<
typeof runTerminalSearchNavigation
>[0]
expect(runTerminalSearchNavigation(pane, 'next', searchState)).toBe(true)
expect(findNext).toHaveBeenCalledWith('hello', { caseSensitive: true, regex: false })
expect(findPrevious).not.toHaveBeenCalled()
})
it('runs the previous search through the guarded xterm path', () => {
const findNext = vi.fn(() => false)
const findPrevious = vi.fn(() => true)
const pane = { searchAddon: { findNext, findPrevious } } as unknown as Parameters<
typeof runTerminalSearchNavigation
>[0]
expect(runTerminalSearchNavigation(pane, 'previous', searchState)).toBe(true)
expect(findPrevious).toHaveBeenCalledWith('hello', { caseSensitive: true, regex: false })
expect(findNext).not.toHaveBeenCalled()
})
it('contains the xterm decoration positive-integer crash from shortcut navigation', () => {
const findNext = vi.fn(() => {
throw new Error('This API only accepts positive integers')
})
const pane = { searchAddon: { findNext } } as unknown as Parameters<
typeof runTerminalSearchNavigation
>[0]
expect(() => runTerminalSearchNavigation(pane, 'next', searchState)).not.toThrow()
expect(runTerminalSearchNavigation(pane, 'next', searchState)).toBe(false)
})
})
describe('matchFileSearchShortcut', () => {
it('matches Cmd+Shift+F on macOS', () => {
expect(
@@ -4,6 +4,7 @@
import { useEffect } from 'react'
import type { ManagedPane, PaneManager } from '@/lib/pane-manager/pane-manager'
import type { PtyTransport } from './pty-transport'
import { safeFind } from '../terminal-search-safe-find'
import { resolveTerminalShortcutAction } from './terminal-shortcut-policy'
import type { MacOptionAsAlt } from './terminal-shortcut-policy'
import {
@@ -65,6 +66,8 @@ export type SearchState = {
regex: boolean
}
export type SearchNavigationDirection = 'next' | 'previous'
/**
* Pure decision function for Cmd+G / Cmd+Shift+G search navigation.
* Returns 'next', 'previous', or null (no match).
@@ -75,7 +78,7 @@ export function matchSearchNavigate(
isMac: boolean,
searchOpen: boolean,
searchState: SearchState
): 'next' | 'previous' | null {
): SearchNavigationDirection | null {
if (e.altKey) {
return null
}
@@ -98,6 +101,25 @@ export function matchSearchNavigate(
return e.shiftKey ? 'previous' : 'next'
}
export function runTerminalSearchNavigation(
pane: Pick<ManagedPane, 'searchAddon'>,
direction: SearchNavigationDirection,
searchState: SearchState
): boolean {
const { query, caseSensitive, regex } = searchState
const options = { caseSensitive, regex }
// Why: Cmd/Ctrl+G hits the same xterm decoration path as the search panel,
// so narrow-viewport highlight failures need the same containment.
return direction === 'next'
? safeFind((term, findOptions) => pane.searchAddon.findNext(term, findOptions), query, options)
: safeFind(
(term, findOptions) => pane.searchAddon.findPrevious(term, findOptions),
query,
options
)
}
export function matchFileSearchShortcut(
e: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey' | 'repeat'>,
platform: KeybindingPlatform,
@@ -233,12 +255,7 @@ export function useTerminalKeyboardShortcuts({
if (!pane) {
return
}
const { query, caseSensitive, regex } = searchStateRef.current
if (direction === 'next') {
pane.searchAddon.findNext(query, { caseSensitive, regex })
} else {
pane.searchAddon.findPrevious(query, { caseSensitive, regex })
}
runTerminalSearchNavigation(pane, direction, searchStateRef.current)
pane.terminal.focus()
return
}