refactor(quick-open): split ripgrep guidance (#6808)

This commit is contained in:
Neil 2026-06-29 18:04:28 -07:00 committed by GitHub
parent 669ff13467
commit cd9744d43f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 148 additions and 140 deletions

View File

@ -1,6 +1,4 @@
/* oxlint-disable max-lines */
import React, { useCallback, useDeferredValue, useMemo, useRef, useState } from 'react'
import { AlertTriangle, Check, Copy } from 'lucide-react'
import React, { useCallback, useDeferredValue, useMemo, useState } from 'react'
import { useAppStore } from '@/store'
import { useActiveWorktree } from '@/store/selectors'
import { detectLanguage } from '@/lib/language-detect'
@ -17,38 +15,10 @@ import { prepareQuickOpenFiles, rankQuickOpenFiles } from '@/components/quick-op
import { useRuntimeFileListForWorktree } from '@/components/quick-open-file-list'
import { useModalReturnFocus } from '@/hooks/useModalReturnFocus'
import { translate } from '@/i18n/i18n'
/**
* Parses the install-ripgrep guidance message produced by the relay's
* buildInstallRgMessage(). Returns the parts needed to render as formatted
* guidance (reason + install command) when matched, or null otherwise so
* callers can fall back to plain-text display.
*
* Why: the message is plain text on the wire (thrown as an Error), but the
* renderer is the only place with enough UI vocabulary to present ripgrep
* as an inline code span and the install command as a copyable code block.
*/
function parseInstallRgGuidance(
message: string
): { reason: string; command: string | null; guidance: string | null } | null {
const match = message.match(
/^Quick Open scan too large \(([^)]+)\)\. Install ripgrep on the remote to enable fast, gitignore-aware listing: (.+)$/
)
if (!match) {
return null
}
const reason = match[1]
const tail = match[2].trim()
// Why: on unknown distros the relay emits prose like "install ripgrep via
// your package manager (e.g. apt/dnf/pacman)" — there's no single command
// to copy, so surface it as plain guidance without the code block.
const looksLikeCommand = /^(sudo\s+)?(brew|apt|dnf|pacman|apk)\s/.test(tail)
return {
reason,
command: looksLikeCommand ? tail : null,
guidance: looksLikeCommand ? null : tail
}
}
import {
parseQuickOpenInstallRgGuidance,
QuickOpenInstallRgGuidance
} from '@/components/quick-open-install-rg-guidance'
function FooterKey({ children }: { children: React.ReactNode }): React.JSX.Element {
return (
@ -58,109 +28,6 @@ function FooterKey({ children }: { children: React.ReactNode }): React.JSX.Eleme
)
}
function InstallRgGuidance({
reason,
command,
guidance
}: {
reason: string
command: string | null
guidance?: string | null
}): React.JSX.Element {
const [copied, setCopied] = useState(false)
const copiedResetTimerRef = useRef<number | null>(null)
// Why: clipboard IPC can resolve after this guidance unmounts; avoid
// starting a reset timer that will outlive the component.
const isMountedRef = useRef(false)
const clearCopiedResetTimer = useCallback((): void => {
if (copiedResetTimerRef.current !== null) {
window.clearTimeout(copiedResetTimerRef.current)
copiedResetTimerRef.current = null
}
}, [])
const setCopyButtonRef = useCallback(
(node: HTMLButtonElement | null) => {
isMountedRef.current = node !== null
if (node === null) {
clearCopiedResetTimer()
}
},
[clearCopiedResetTimer]
)
const handleCopy = useCallback(() => {
if (!command) {
return
}
// Why: use Electron's clipboard IPC instead of navigator.clipboard — the
// latter often fails silently in the renderer due to focus/permission
// quirks inside Radix dialogs. All other copy buttons in the app go
// through window.api.ui.writeClipboardText for consistency.
void window.api.ui
.writeClipboardText(command)
.then(() => {
if (!isMountedRef.current) {
return
}
clearCopiedResetTimer()
setCopied(true)
copiedResetTimerRef.current = window.setTimeout(() => {
copiedResetTimerRef.current = null
setCopied(false)
}, 1500)
})
.catch(() => {
/* best-effort */
})
}, [clearCopiedResetTimer, command])
return (
<div className="px-4 py-5 text-sm text-muted-foreground space-y-3">
<div
role="alert"
className="flex items-start gap-2.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2.5 text-amber-700 dark:text-amber-300"
>
<AlertTriangle size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
<p className="text-[13px] leading-5">
{translate('auto.components.QuickOpen.4725b0e931', 'Quick Open scan too large (')}
{reason}).
</p>
</div>
<p>
{translate('auto.components.QuickOpen.2ca749c15d', 'Install')}{' '}
<code className="rounded bg-muted px-1 py-0.5 font-mono text-foreground">
{translate('auto.components.QuickOpen.5d80dc39bb', 'ripgrep')}
</code>{' '}
{translate(
'auto.components.QuickOpen.1cf8561ab4',
'on the remote to enable fast, gitignore-aware listing:'
)}
</p>
{command ? (
<div className="flex items-center gap-2 rounded border border-border bg-muted/50 px-3 py-2 font-mono text-xs text-foreground">
<span className="flex-1 truncate">{command}</span>
<button
ref={setCopyButtonRef}
type="button"
onClick={handleCopy}
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
aria-label={translate('auto.components.QuickOpen.73b44e7bde', 'Copy install command')}
>
{copied ? <Check size={12} /> : <Copy size={12} />}
{copied
? translate('auto.components.QuickOpen.cf144856dc', 'Copied')
: translate('auto.components.QuickOpen.995be8ea22', 'Copy')}
</button>
</div>
) : guidance ? (
<p className="text-[13px] leading-5 text-foreground">{guidance}</p>
) : null}
</div>
)
}
export default function QuickOpen(): React.JSX.Element | null {
const visible = useAppStore((s) => s.activeModal === 'quick-open')
const closeModal = useAppStore((s) => s.closeModal)
@ -259,9 +126,9 @@ export default function QuickOpen(): React.JSX.Element | null {
</div>
) : loadError ? (
(() => {
const guidance = parseInstallRgGuidance(loadError)
const guidance = parseQuickOpenInstallRgGuidance(loadError)
return guidance ? (
<InstallRgGuidance
<QuickOpenInstallRgGuidance
reason={guidance.reason}
command={guidance.command}
guidance={guidance.guidance}

View File

@ -0,0 +1,141 @@
import type React from 'react'
import { useCallback, useRef, useState } from 'react'
import { AlertTriangle, Check, Copy } from 'lucide-react'
import { translate } from '@/i18n/i18n'
export type QuickOpenInstallRgGuidanceParts = {
reason: string
command: string | null
guidance: string | null
}
/**
* Parses the install-ripgrep guidance message produced by the relay's
* buildInstallRgMessage(). Returns the parts needed to render as formatted
* guidance (reason + install command) when matched, or null otherwise so
* callers can fall back to plain-text display.
*
* Why: the message is plain text on the wire (thrown as an Error), but the
* renderer is the only place with enough UI vocabulary to present ripgrep
* as an inline code span and the install command as a copyable code block.
*/
export function parseQuickOpenInstallRgGuidance(
message: string
): QuickOpenInstallRgGuidanceParts | null {
const match = message.match(
/^Quick Open scan too large \(([^)]+)\)\. Install ripgrep on the remote to enable fast, gitignore-aware listing: (.+)$/
)
if (!match) {
return null
}
const reason = match[1]
const tail = match[2].trim()
// Why: on unknown distros the relay emits prose like "install ripgrep via
// your package manager (e.g. apt/dnf/pacman)"; there is no single command
// to copy, so surface it as plain guidance without the code block.
const looksLikeCommand = /^(sudo\s+)?(brew|apt|dnf|pacman|apk)\s/.test(tail)
return {
reason,
command: looksLikeCommand ? tail : null,
guidance: looksLikeCommand ? null : tail
}
}
export function QuickOpenInstallRgGuidance({
reason,
command,
guidance
}: QuickOpenInstallRgGuidanceParts): React.JSX.Element {
const [copied, setCopied] = useState(false)
const copiedResetTimerRef = useRef<number | null>(null)
// Why: clipboard IPC can resolve after this guidance unmounts; avoid
// starting a reset timer that will outlive the component.
const isMountedRef = useRef(false)
const clearCopiedResetTimer = useCallback((): void => {
if (copiedResetTimerRef.current !== null) {
window.clearTimeout(copiedResetTimerRef.current)
copiedResetTimerRef.current = null
}
}, [])
const setCopyButtonRef = useCallback(
(node: HTMLButtonElement | null) => {
isMountedRef.current = node !== null
if (node === null) {
clearCopiedResetTimer()
}
},
[clearCopiedResetTimer]
)
const handleCopy = useCallback(() => {
if (!command) {
return
}
// Why: use Electron's clipboard IPC instead of navigator.clipboard; the
// latter often fails silently in the renderer due to focus/permission
// quirks inside Radix dialogs. All other copy buttons in the app go
// through window.api.ui.writeClipboardText for consistency.
void window.api.ui
.writeClipboardText(command)
.then(() => {
if (!isMountedRef.current) {
return
}
clearCopiedResetTimer()
setCopied(true)
copiedResetTimerRef.current = window.setTimeout(() => {
copiedResetTimerRef.current = null
setCopied(false)
}, 1500)
})
.catch(() => {
/* best-effort */
})
}, [clearCopiedResetTimer, command])
return (
<div className="px-4 py-5 text-sm text-muted-foreground space-y-3">
<div
role="alert"
className="flex items-start gap-2.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2.5 text-amber-700 dark:text-amber-300"
>
<AlertTriangle size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
<p className="text-[13px] leading-5">
{translate('auto.components.QuickOpen.4725b0e931', 'Quick Open scan too large (')}
{reason}).
</p>
</div>
<p>
{translate('auto.components.QuickOpen.2ca749c15d', 'Install')}{' '}
<code className="rounded bg-muted px-1 py-0.5 font-mono text-foreground">
{translate('auto.components.QuickOpen.5d80dc39bb', 'ripgrep')}
</code>{' '}
{translate(
'auto.components.QuickOpen.1cf8561ab4',
'on the remote to enable fast, gitignore-aware listing:'
)}
</p>
{command ? (
<div className="flex items-center gap-2 rounded border border-border bg-muted/50 px-3 py-2 font-mono text-xs text-foreground">
<span className="flex-1 truncate">{command}</span>
<button
ref={setCopyButtonRef}
type="button"
onClick={handleCopy}
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
aria-label={translate('auto.components.QuickOpen.73b44e7bde', 'Copy install command')}
>
{copied ? <Check size={12} /> : <Copy size={12} />}
{copied
? translate('auto.components.QuickOpen.cf144856dc', 'Copied')
: translate('auto.components.QuickOpen.995be8ea22', 'Copy')}
</button>
</div>
) : guidance ? (
<p className="text-[13px] leading-5 text-foreground">{guidance}</p>
) : null}
</div>
)
}