mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,324 +1,308 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRegisterOverlay } from '../context/overlayContext.js';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { Text } from '../ink.js';
|
||||
import { logEvent } from '../services/analytics/index.js';
|
||||
import { getCwd } from '../utils/cwd.js';
|
||||
import { openFileInExternalEditor } from '../utils/editor.js';
|
||||
import { truncatePathMiddle, truncateToWidth } from '../utils/format.js';
|
||||
import { highlightMatch } from '../utils/highlightMatch.js';
|
||||
import { relativePath } from '../utils/permissions/filesystem.js';
|
||||
import { readFileInRange } from '../utils/readFileInRange.js';
|
||||
import { ripGrepStream } from '../utils/ripgrep.js';
|
||||
import { FuzzyPicker } from './design-system/FuzzyPicker.js';
|
||||
import { LoadingState } from './design-system/LoadingState.js';
|
||||
import { resolve as resolvePath } from 'path'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useRegisterOverlay } from '../context/overlayContext.js'
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js'
|
||||
import { Text } from '../ink.js'
|
||||
import { logEvent } from '../services/analytics/index.js'
|
||||
import { getCwd } from '../utils/cwd.js'
|
||||
import { openFileInExternalEditor } from '../utils/editor.js'
|
||||
import { truncatePathMiddle, truncateToWidth } from '../utils/format.js'
|
||||
import { highlightMatch } from '../utils/highlightMatch.js'
|
||||
import { relativePath } from '../utils/permissions/filesystem.js'
|
||||
import { readFileInRange } from '../utils/readFileInRange.js'
|
||||
import { ripGrepStream } from '../utils/ripgrep.js'
|
||||
import { FuzzyPicker } from './design-system/FuzzyPicker.js'
|
||||
import { LoadingState } from './design-system/LoadingState.js'
|
||||
|
||||
type Props = {
|
||||
onDone: () => void;
|
||||
onInsert: (text: string) => void;
|
||||
};
|
||||
onDone: () => void
|
||||
onInsert: (text: string) => void
|
||||
}
|
||||
|
||||
type Match = {
|
||||
file: string;
|
||||
line: number;
|
||||
text: string;
|
||||
};
|
||||
const VISIBLE_RESULTS = 12;
|
||||
const DEBOUNCE_MS = 100;
|
||||
const PREVIEW_CONTEXT_LINES = 4;
|
||||
file: string
|
||||
line: number
|
||||
text: string
|
||||
}
|
||||
|
||||
const VISIBLE_RESULTS = 12
|
||||
const DEBOUNCE_MS = 100
|
||||
const PREVIEW_CONTEXT_LINES = 4
|
||||
// rg -m is per-file; we also cap the parsed array to keep memory bounded.
|
||||
const MAX_MATCHES_PER_FILE = 10;
|
||||
const MAX_TOTAL_MATCHES = 500;
|
||||
const MAX_MATCHES_PER_FILE = 10
|
||||
const MAX_TOTAL_MATCHES = 500
|
||||
|
||||
/**
|
||||
* Global Search dialog (ctrl+shift+f / cmd+shift+f).
|
||||
* Debounced ripgrep search across the workspace.
|
||||
*/
|
||||
export function GlobalSearchDialog(t0) {
|
||||
const $ = _c(40);
|
||||
const {
|
||||
onDone,
|
||||
onInsert
|
||||
} = t0;
|
||||
useRegisterOverlay("global-search", undefined);
|
||||
const {
|
||||
columns,
|
||||
rows
|
||||
} = useTerminalSize();
|
||||
const previewOnRight = columns >= 140;
|
||||
const visibleResults = Math.min(VISIBLE_RESULTS, Math.max(4, rows - 14));
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
const [matches, setMatches] = useState(t1);
|
||||
const [truncated, setTruncated] = useState(false);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [focused, setFocused] = useState(undefined);
|
||||
const [preview, setPreview] = useState(null);
|
||||
const abortRef = useRef(null);
|
||||
const timeoutRef = useRef(null);
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = () => () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
t3 = [];
|
||||
$[1] = t2;
|
||||
$[2] = t3;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t3 = $[2];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
let t4;
|
||||
let t5;
|
||||
if ($[3] !== focused) {
|
||||
t4 = () => {
|
||||
if (!focused) {
|
||||
setPreview(null);
|
||||
return;
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const absolute = resolvePath(getCwd(), focused.file);
|
||||
const start = Math.max(0, focused.line - PREVIEW_CONTEXT_LINES - 1);
|
||||
readFileInRange(absolute, start, PREVIEW_CONTEXT_LINES * 2 + 1, undefined, controller.signal).then(r => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
export function GlobalSearchDialog({
|
||||
onDone,
|
||||
onInsert,
|
||||
}: Props): React.ReactNode {
|
||||
useRegisterOverlay('global-search')
|
||||
const { columns, rows } = useTerminalSize()
|
||||
const previewOnRight = columns >= 140
|
||||
// Chrome (title + search + matchLabel + hints + pane border + gaps) eats
|
||||
// ~14 rows. Shrink the list on short terminals so the dialog doesn't clip.
|
||||
const visibleResults = Math.min(VISIBLE_RESULTS, Math.max(4, rows - 14))
|
||||
|
||||
const [matches, setMatches] = useState<Match[]>([])
|
||||
const [truncated, setTruncated] = useState(false)
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
const [focused, setFocused] = useState<Match | undefined>(undefined)
|
||||
const [preview, setPreview] = useState<{
|
||||
file: string
|
||||
line: number
|
||||
content: string
|
||||
} | null>(null)
|
||||
const abortRef = useRef<AbortController | null>(null)
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
||||
abortRef.current?.abort()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Load context lines around the focused match. AbortController prevents
|
||||
// holding ↓ from piling up reads.
|
||||
useEffect(() => {
|
||||
if (!focused) {
|
||||
setPreview(null)
|
||||
return
|
||||
}
|
||||
const controller = new AbortController()
|
||||
const absolute = resolvePath(getCwd(), focused.file)
|
||||
const start = Math.max(0, focused.line - PREVIEW_CONTEXT_LINES - 1)
|
||||
void readFileInRange(
|
||||
absolute,
|
||||
start,
|
||||
PREVIEW_CONTEXT_LINES * 2 + 1,
|
||||
undefined,
|
||||
controller.signal,
|
||||
)
|
||||
.then(r => {
|
||||
if (controller.signal.aborted) return
|
||||
setPreview({
|
||||
file: focused.file,
|
||||
line: focused.line,
|
||||
content: r.content
|
||||
});
|
||||
}).catch(() => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
content: r.content,
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
if (controller.signal.aborted) return
|
||||
setPreview({
|
||||
file: focused.file,
|
||||
line: focused.line,
|
||||
content: "(preview unavailable)"
|
||||
});
|
||||
});
|
||||
return () => controller.abort();
|
||||
};
|
||||
t5 = [focused];
|
||||
$[3] = focused;
|
||||
$[4] = t4;
|
||||
$[5] = t5;
|
||||
} else {
|
||||
t4 = $[4];
|
||||
t5 = $[5];
|
||||
content: '(preview unavailable)',
|
||||
})
|
||||
})
|
||||
return () => controller.abort()
|
||||
}, [focused])
|
||||
|
||||
const handleQueryChange = (q: string) => {
|
||||
setQuery(q)
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
||||
abortRef.current?.abort()
|
||||
|
||||
if (!q.trim()) {
|
||||
setMatches(m => (m.length ? [] : m))
|
||||
setIsSearching(false)
|
||||
setTruncated(false)
|
||||
return
|
||||
}
|
||||
const controller = new AbortController()
|
||||
abortRef.current = controller
|
||||
setIsSearching(true)
|
||||
setTruncated(false)
|
||||
// Client-filter existing results while rg walks — keeps something on
|
||||
// screen instead of flashing blank. rg results are merged in (deduped by
|
||||
// file:line) rather than replaced, so the count is monotonic within a
|
||||
// query: it only grows as rg streams, never dips to the first chunk's
|
||||
// size. Narrowing (new query extends old): filter is exact — any line
|
||||
// that matched the old -F -i literal contains the new one iff its text
|
||||
// includes the new query lowered. Non-narrowing (broadening/different):
|
||||
// filter is best-effort — may briefly show a subset until rg fills in
|
||||
// the rest.
|
||||
const queryLower = q.toLowerCase()
|
||||
setMatches(m => {
|
||||
const filtered = m.filter(match =>
|
||||
match.text.toLowerCase().includes(queryLower),
|
||||
)
|
||||
return filtered.length === m.length ? m : filtered
|
||||
})
|
||||
|
||||
timeoutRef.current = setTimeout(
|
||||
(query, controller, setMatches, setTruncated, setIsSearching) => {
|
||||
// ripgrep outputs absolute paths when given an absolute target, so
|
||||
// relativize against cwd to preserve directory context in the truncated
|
||||
// display (otherwise the cwd prefix eats the width budget).
|
||||
// relativePath() returns POSIX-normalized output so truncatePathMiddle
|
||||
// (which uses lastIndexOf('/')) works on Windows too.
|
||||
const cwd = getCwd()
|
||||
let collected = 0
|
||||
void ripGrepStream(
|
||||
// -e disambiguates pattern from options when the query starts with '-'
|
||||
// (e.g. searching for "--verbose" or "-rf"). See GrepTool.ts for the
|
||||
// same precaution.
|
||||
[
|
||||
'-n',
|
||||
'--no-heading',
|
||||
'-i',
|
||||
'-m',
|
||||
String(MAX_MATCHES_PER_FILE),
|
||||
'-F',
|
||||
'-e',
|
||||
query,
|
||||
],
|
||||
cwd,
|
||||
controller.signal,
|
||||
lines => {
|
||||
if (controller.signal.aborted) return
|
||||
const parsed: Match[] = []
|
||||
for (const line of lines) {
|
||||
const m = parseRipgrepLine(line)
|
||||
if (!m) continue
|
||||
const rel = relativePath(cwd, m.file)
|
||||
parsed.push({ ...m, file: rel.startsWith('..') ? m.file : rel })
|
||||
}
|
||||
if (!parsed.length) return
|
||||
collected += parsed.length
|
||||
setMatches(prev => {
|
||||
// Append+dedupe instead of replace: prev may hold client-
|
||||
// filtered results that are valid matches for this query.
|
||||
// Replacing would drop the count to this chunk's size then
|
||||
// grow it back — visible as a flicker.
|
||||
const seen = new Set(prev.map(matchKey))
|
||||
const fresh = parsed.filter(p => !seen.has(matchKey(p)))
|
||||
if (!fresh.length) return prev
|
||||
const next = prev.concat(fresh)
|
||||
return next.length > MAX_TOTAL_MATCHES
|
||||
? next.slice(0, MAX_TOTAL_MATCHES)
|
||||
: next
|
||||
})
|
||||
if (collected >= MAX_TOTAL_MATCHES) {
|
||||
controller.abort()
|
||||
setTruncated(true)
|
||||
setIsSearching(false)
|
||||
}
|
||||
},
|
||||
)
|
||||
.catch(() => {})
|
||||
// Stream closed with zero chunks — clear stale results so
|
||||
// "No matches" renders instead of the previous query's list.
|
||||
.finally(() => {
|
||||
if (controller.signal.aborted) return
|
||||
if (collected === 0) setMatches(m => (m.length ? [] : m))
|
||||
setIsSearching(false)
|
||||
})
|
||||
},
|
||||
DEBOUNCE_MS,
|
||||
q,
|
||||
controller,
|
||||
setMatches,
|
||||
setTruncated,
|
||||
setIsSearching,
|
||||
)
|
||||
}
|
||||
useEffect(t4, t5);
|
||||
let t6;
|
||||
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t6 = q => {
|
||||
setQuery(q);
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
|
||||
const listWidth = previewOnRight
|
||||
? Math.floor((columns - 10) * 0.5)
|
||||
: columns - 8
|
||||
const maxPathWidth = Math.max(20, Math.floor(listWidth * 0.4))
|
||||
const maxTextWidth = Math.max(20, listWidth - maxPathWidth - 4)
|
||||
const previewWidth = previewOnRight
|
||||
? Math.max(40, columns - listWidth - 14)
|
||||
: columns - 6
|
||||
|
||||
const handleOpen = (m: Match) => {
|
||||
const opened = openFileInExternalEditor(
|
||||
resolvePath(getCwd(), m.file),
|
||||
m.line,
|
||||
)
|
||||
logEvent('tengu_global_search_select', {
|
||||
result_count: matches.length,
|
||||
opened_editor: opened,
|
||||
})
|
||||
onDone()
|
||||
}
|
||||
|
||||
const handleInsert = (m: Match, mention: boolean) => {
|
||||
onInsert(mention ? `@${m.file}#L${m.line} ` : `${m.file}:${m.line} `)
|
||||
logEvent('tengu_global_search_insert', {
|
||||
result_count: matches.length,
|
||||
mention,
|
||||
})
|
||||
onDone()
|
||||
}
|
||||
|
||||
// Always pass a non-empty string so the line is reserved — prevents the
|
||||
// searchBox from bouncing when the count appears/disappears.
|
||||
const matchLabel =
|
||||
matches.length > 0
|
||||
? `${matches.length}${truncated ? '+' : ''} matches${isSearching ? '…' : ''}`
|
||||
: ' '
|
||||
|
||||
return (
|
||||
<FuzzyPicker
|
||||
title="Global Search"
|
||||
placeholder="Type to search…"
|
||||
items={matches}
|
||||
getKey={matchKey}
|
||||
visibleCount={visibleResults}
|
||||
direction="up"
|
||||
previewPosition={previewOnRight ? 'right' : 'bottom'}
|
||||
onQueryChange={handleQueryChange}
|
||||
onFocus={setFocused}
|
||||
onSelect={handleOpen}
|
||||
onTab={{ action: 'mention', handler: m => handleInsert(m, true) }}
|
||||
onShiftTab={{
|
||||
action: 'insert path',
|
||||
handler: m => handleInsert(m, false),
|
||||
}}
|
||||
onCancel={onDone}
|
||||
emptyMessage={q =>
|
||||
isSearching ? 'Searching…' : q ? 'No matches' : 'Type to search…'
|
||||
}
|
||||
abortRef.current?.abort();
|
||||
if (!q.trim()) {
|
||||
setMatches(_temp);
|
||||
setIsSearching(false);
|
||||
setTruncated(false);
|
||||
return;
|
||||
matchLabel={matchLabel}
|
||||
selectAction="open in editor"
|
||||
renderItem={(m, isFocused) => (
|
||||
<Text color={isFocused ? 'suggestion' : undefined}>
|
||||
<Text dimColor>
|
||||
{truncatePathMiddle(m.file, maxPathWidth)}:{m.line}
|
||||
</Text>{' '}
|
||||
{highlightMatch(
|
||||
truncateToWidth(m.text.trimStart(), maxTextWidth),
|
||||
query,
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
renderPreview={m =>
|
||||
preview?.file === m.file && preview.line === m.line ? (
|
||||
<>
|
||||
<Text dimColor>
|
||||
{truncatePathMiddle(m.file, previewWidth)}:{m.line}
|
||||
</Text>
|
||||
{preview.content.split('\n').map((line, i) => (
|
||||
<Text key={i}>
|
||||
{highlightMatch(truncateToWidth(line, previewWidth), query)}
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<LoadingState message="Loading…" dimColor />
|
||||
)
|
||||
}
|
||||
const controller_0 = new AbortController();
|
||||
abortRef.current = controller_0;
|
||||
setIsSearching(true);
|
||||
setTruncated(false);
|
||||
const queryLower = q.toLowerCase();
|
||||
setMatches(m_0 => {
|
||||
const filtered = m_0.filter(match => match.text.toLowerCase().includes(queryLower));
|
||||
return filtered.length === m_0.length ? m_0 : filtered;
|
||||
});
|
||||
timeoutRef.current = setTimeout(_temp4, DEBOUNCE_MS, q, controller_0, setMatches, setTruncated, setIsSearching);
|
||||
};
|
||||
$[6] = t6;
|
||||
} else {
|
||||
t6 = $[6];
|
||||
}
|
||||
const handleQueryChange = t6;
|
||||
const listWidth = previewOnRight ? Math.floor((columns - 10) * 0.5) : columns - 8;
|
||||
const maxPathWidth = Math.max(20, Math.floor(listWidth * 0.4));
|
||||
const maxTextWidth = Math.max(20, listWidth - maxPathWidth - 4);
|
||||
const previewWidth = previewOnRight ? Math.max(40, columns - listWidth - 14) : columns - 6;
|
||||
let t7;
|
||||
if ($[7] !== matches.length || $[8] !== onDone) {
|
||||
t7 = m_3 => {
|
||||
const opened = openFileInExternalEditor(resolvePath(getCwd(), m_3.file), m_3.line);
|
||||
logEvent("tengu_global_search_select", {
|
||||
result_count: matches.length,
|
||||
opened_editor: opened
|
||||
});
|
||||
onDone();
|
||||
};
|
||||
$[7] = matches.length;
|
||||
$[8] = onDone;
|
||||
$[9] = t7;
|
||||
} else {
|
||||
t7 = $[9];
|
||||
}
|
||||
const handleOpen = t7;
|
||||
let t8;
|
||||
if ($[10] !== matches.length || $[11] !== onDone || $[12] !== onInsert) {
|
||||
t8 = (m_4, mention) => {
|
||||
onInsert(mention ? `@${m_4.file}#L${m_4.line} ` : `${m_4.file}:${m_4.line} `);
|
||||
logEvent("tengu_global_search_insert", {
|
||||
result_count: matches.length,
|
||||
mention
|
||||
});
|
||||
onDone();
|
||||
};
|
||||
$[10] = matches.length;
|
||||
$[11] = onDone;
|
||||
$[12] = onInsert;
|
||||
$[13] = t8;
|
||||
} else {
|
||||
t8 = $[13];
|
||||
}
|
||||
const handleInsert = t8;
|
||||
const matchLabel = matches.length > 0 ? `${matches.length}${truncated ? "+" : ""} matches${isSearching ? "\u2026" : ""}` : " ";
|
||||
const t9 = previewOnRight ? "right" : "bottom";
|
||||
let t10;
|
||||
if ($[14] !== handleInsert) {
|
||||
t10 = {
|
||||
action: "mention",
|
||||
handler: m_5 => handleInsert(m_5, true)
|
||||
};
|
||||
$[14] = handleInsert;
|
||||
$[15] = t10;
|
||||
} else {
|
||||
t10 = $[15];
|
||||
}
|
||||
let t11;
|
||||
if ($[16] !== handleInsert) {
|
||||
t11 = {
|
||||
action: "insert path",
|
||||
handler: m_6 => handleInsert(m_6, false)
|
||||
};
|
||||
$[16] = handleInsert;
|
||||
$[17] = t11;
|
||||
} else {
|
||||
t11 = $[17];
|
||||
}
|
||||
let t12;
|
||||
if ($[18] !== isSearching) {
|
||||
t12 = q_0 => isSearching ? "Searching\u2026" : q_0 ? "No matches" : "Type to search\u2026";
|
||||
$[18] = isSearching;
|
||||
$[19] = t12;
|
||||
} else {
|
||||
t12 = $[19];
|
||||
}
|
||||
let t13;
|
||||
if ($[20] !== maxPathWidth || $[21] !== maxTextWidth || $[22] !== query) {
|
||||
t13 = (m_7, isFocused) => <Text color={isFocused ? "suggestion" : undefined}><Text dimColor={true}>{truncatePathMiddle(m_7.file, maxPathWidth)}:{m_7.line}</Text>{" "}{highlightMatch(truncateToWidth(m_7.text.trimStart(), maxTextWidth), query)}</Text>;
|
||||
$[20] = maxPathWidth;
|
||||
$[21] = maxTextWidth;
|
||||
$[22] = query;
|
||||
$[23] = t13;
|
||||
} else {
|
||||
t13 = $[23];
|
||||
}
|
||||
let t14;
|
||||
if ($[24] !== preview || $[25] !== previewWidth || $[26] !== query) {
|
||||
t14 = m_8 => preview?.file === m_8.file && preview.line === m_8.line ? <><Text dimColor={true}>{truncatePathMiddle(m_8.file, previewWidth)}:{m_8.line}</Text>{preview.content.split("\n").map((line_0, i) => <Text key={i}>{highlightMatch(truncateToWidth(line_0, previewWidth), query)}</Text>)}</> : <LoadingState message={"Loading\u2026"} dimColor={true} />;
|
||||
$[24] = preview;
|
||||
$[25] = previewWidth;
|
||||
$[26] = query;
|
||||
$[27] = t14;
|
||||
} else {
|
||||
t14 = $[27];
|
||||
}
|
||||
let t15;
|
||||
if ($[28] !== handleOpen || $[29] !== matchLabel || $[30] !== matches || $[31] !== onDone || $[32] !== t10 || $[33] !== t11 || $[34] !== t12 || $[35] !== t13 || $[36] !== t14 || $[37] !== t9 || $[38] !== visibleResults) {
|
||||
t15 = <FuzzyPicker title="Global Search" placeholder={"Type to search\u2026"} items={matches} getKey={matchKey} visibleCount={visibleResults} direction="up" previewPosition={t9} onQueryChange={handleQueryChange} onFocus={setFocused} onSelect={handleOpen} onTab={t10} onShiftTab={t11} onCancel={onDone} emptyMessage={t12} matchLabel={matchLabel} selectAction="open in editor" renderItem={t13} renderPreview={t14} />;
|
||||
$[28] = handleOpen;
|
||||
$[29] = matchLabel;
|
||||
$[30] = matches;
|
||||
$[31] = onDone;
|
||||
$[32] = t10;
|
||||
$[33] = t11;
|
||||
$[34] = t12;
|
||||
$[35] = t13;
|
||||
$[36] = t14;
|
||||
$[37] = t9;
|
||||
$[38] = visibleResults;
|
||||
$[39] = t15;
|
||||
} else {
|
||||
t15 = $[39];
|
||||
}
|
||||
return t15;
|
||||
}
|
||||
function _temp4(query_0, controller_1, setMatches_0, setTruncated_0, setIsSearching_0) {
|
||||
const cwd = getCwd();
|
||||
let collected = 0;
|
||||
ripGrepStream(["-n", "--no-heading", "-i", "-m", String(MAX_MATCHES_PER_FILE), "-F", "-e", query_0], cwd, controller_1.signal, lines => {
|
||||
if (controller_1.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
const parsed = [];
|
||||
for (const line of lines) {
|
||||
const m_1 = parseRipgrepLine(line);
|
||||
if (!m_1) {
|
||||
continue;
|
||||
}
|
||||
const rel = relativePath(cwd, m_1.file);
|
||||
parsed.push({
|
||||
...m_1,
|
||||
file: rel.startsWith("..") ? m_1.file : rel
|
||||
});
|
||||
}
|
||||
if (!parsed.length) {
|
||||
return;
|
||||
}
|
||||
collected = collected + parsed.length;
|
||||
collected;
|
||||
setMatches_0(prev => {
|
||||
const seen = new Set(prev.map(matchKey));
|
||||
const fresh = parsed.filter(p => !seen.has(matchKey(p)));
|
||||
if (!fresh.length) {
|
||||
return prev;
|
||||
}
|
||||
const next = prev.concat(fresh);
|
||||
return next.length > MAX_TOTAL_MATCHES ? next.slice(0, MAX_TOTAL_MATCHES) : next;
|
||||
});
|
||||
if (collected >= MAX_TOTAL_MATCHES) {
|
||||
controller_1.abort();
|
||||
setTruncated_0(true);
|
||||
setIsSearching_0(false);
|
||||
}
|
||||
}).catch(_temp2).finally(() => {
|
||||
if (controller_1.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (collected === 0) {
|
||||
setMatches_0(_temp3);
|
||||
}
|
||||
setIsSearching_0(false);
|
||||
});
|
||||
}
|
||||
function _temp3(m_2) {
|
||||
return m_2.length ? [] : m_2;
|
||||
}
|
||||
function _temp2() {}
|
||||
function _temp(m) {
|
||||
return m.length ? [] : m;
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function matchKey(m: Match): string {
|
||||
return `${m.file}:${m.line}`;
|
||||
return `${m.file}:${m.line}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,14 +313,10 @@ function matchKey(m: Match): string {
|
||||
* @internal exported for testing
|
||||
*/
|
||||
export function parseRipgrepLine(line: string): Match | null {
|
||||
const m = /^(.*?):(\d+):(.*)$/.exec(line);
|
||||
if (!m) return null;
|
||||
const [, file, lineStr, text] = m;
|
||||
const lineNum = Number(lineStr);
|
||||
if (!file || !Number.isFinite(lineNum)) return null;
|
||||
return {
|
||||
file,
|
||||
line: lineNum,
|
||||
text: text ?? ''
|
||||
};
|
||||
const m = /^(.*?):(\d+):(.*)$/.exec(line)
|
||||
if (!m) return null
|
||||
const [, file, lineStr, text] = m
|
||||
const lineNum = Number(lineStr)
|
||||
if (!file || !Number.isFinite(lineNum)) return null
|
||||
return { file, line: lineNum, text: text ?? '' }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user