mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Guard terminal search shortcut navigation (#6872)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user