mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +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,243 +1,182 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRegisterOverlay } from '../context/overlayContext.js';
|
||||
import { generateFileSuggestions } from '../hooks/fileSuggestions.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 { readFileInRange } from '../utils/readFileInRange.js';
|
||||
import { FuzzyPicker } from './design-system/FuzzyPicker.js';
|
||||
import { LoadingState } from './design-system/LoadingState.js';
|
||||
import * as path from 'path'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useRegisterOverlay } from '../context/overlayContext.js'
|
||||
import { generateFileSuggestions } from '../hooks/fileSuggestions.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 { readFileInRange } from '../utils/readFileInRange.js'
|
||||
import { FuzzyPicker } from './design-system/FuzzyPicker.js'
|
||||
import { LoadingState } from './design-system/LoadingState.js'
|
||||
|
||||
type Props = {
|
||||
onDone: () => void;
|
||||
onInsert: (text: string) => void;
|
||||
};
|
||||
const VISIBLE_RESULTS = 8;
|
||||
const PREVIEW_LINES = 20;
|
||||
onDone: () => void
|
||||
onInsert: (text: string) => void
|
||||
}
|
||||
|
||||
const VISIBLE_RESULTS = 8
|
||||
const PREVIEW_LINES = 20
|
||||
|
||||
/**
|
||||
* Quick Open dialog (ctrl+shift+p / cmd+shift+p).
|
||||
* Fuzzy file finder with a syntax-highlighted preview of the focused file.
|
||||
*/
|
||||
export function QuickOpenDialog(t0) {
|
||||
const $ = _c(35);
|
||||
const {
|
||||
onDone,
|
||||
onInsert
|
||||
} = t0;
|
||||
useRegisterOverlay("quick-open", undefined);
|
||||
const {
|
||||
columns,
|
||||
rows
|
||||
} = useTerminalSize();
|
||||
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];
|
||||
export function QuickOpenDialog({ onDone, onInsert }: Props): React.ReactNode {
|
||||
useRegisterOverlay('quick-open')
|
||||
const { columns, rows } = useTerminalSize()
|
||||
// Chrome (title + search + 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 [results, setResults] = useState<string[]>([])
|
||||
const [query, setQuery] = useState('')
|
||||
const [focusedPath, setFocusedPath] = useState<string | undefined>(undefined)
|
||||
const [preview, setPreview] = useState<{
|
||||
path: string
|
||||
content: string
|
||||
} | null>(null)
|
||||
const queryGenRef = useRef(0)
|
||||
useEffect(() => () => void queryGenRef.current++, [])
|
||||
|
||||
const previewOnRight = columns >= 120
|
||||
// Side preview sits in a fixed-height row alongside the list (visibleCount
|
||||
// rows), so overflowing that height garbles the layout — cap to fit, minus
|
||||
// one for the path header line.
|
||||
const effectivePreviewLines = previewOnRight
|
||||
? VISIBLE_RESULTS - 1
|
||||
: PREVIEW_LINES
|
||||
|
||||
// A generation counter invalidates stale results if the user types faster
|
||||
// than the index can respond.
|
||||
const handleQueryChange = (q: string) => {
|
||||
setQuery(q)
|
||||
const gen = ++queryGenRef.current
|
||||
if (!q.trim()) {
|
||||
// generateFileSuggestions('') returns raw readdir() of cwd (designed for
|
||||
// @-mentions). For Quick Open that's just noise — show the empty state.
|
||||
setResults([])
|
||||
return
|
||||
}
|
||||
void generateFileSuggestions(q, true).then(items => {
|
||||
if (gen !== queryGenRef.current) return
|
||||
// Filter out directory entries — they come back with a trailing path.sep
|
||||
// from getTopLevelPaths() and would cause readFileInRange to throw EISDIR,
|
||||
// leaving the preview pane stuck on "Loading preview…".
|
||||
// Normalize separators to '/' so truncatePathMiddle (which uses
|
||||
// lastIndexOf('/')) can find the filename on Windows too.
|
||||
const paths = items
|
||||
.filter(i => i.id.startsWith('file-'))
|
||||
.map(i => i.displayText)
|
||||
.filter(p => !p.endsWith(path.sep))
|
||||
.map(p => p.split(path.sep).join('/'))
|
||||
setResults(paths)
|
||||
})
|
||||
}
|
||||
const [results, setResults] = useState(t1);
|
||||
const [query, setQuery] = useState("");
|
||||
const [focusedPath, setFocusedPath] = useState(undefined);
|
||||
const [preview, setPreview] = useState(null);
|
||||
const queryGenRef = useRef(0);
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = () => () => {
|
||||
queryGenRef.current = queryGenRef.current + 1;
|
||||
return void queryGenRef.current;
|
||||
};
|
||||
t3 = [];
|
||||
$[1] = t2;
|
||||
$[2] = t3;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t3 = $[2];
|
||||
|
||||
// Load a short preview of the focused file. Each navigation aborts the
|
||||
// previous read so holding ↓ doesn't pile up whole-file reads and so a
|
||||
// slow early read can't overwrite a faster later one. The stale preview
|
||||
// stays visible until the new one arrives — renderPreview overlays a dim
|
||||
// loading indicator rather than blanking the pane.
|
||||
useEffect(() => {
|
||||
if (!focusedPath) {
|
||||
// No results — clear so the empty-state renders instead of a stale
|
||||
// preview from a previous query.
|
||||
setPreview(null)
|
||||
return
|
||||
}
|
||||
const controller = new AbortController()
|
||||
const absolute = path.resolve(getCwd(), focusedPath)
|
||||
void readFileInRange(
|
||||
absolute,
|
||||
0,
|
||||
effectivePreviewLines,
|
||||
undefined,
|
||||
controller.signal,
|
||||
)
|
||||
.then(r => {
|
||||
if (controller.signal.aborted) return
|
||||
setPreview({ path: focusedPath, content: r.content })
|
||||
})
|
||||
.catch(() => {
|
||||
if (controller.signal.aborted) return
|
||||
setPreview({ path: focusedPath, content: '(preview unavailable)' })
|
||||
})
|
||||
return () => controller.abort()
|
||||
}, [focusedPath, effectivePreviewLines])
|
||||
|
||||
const maxPathWidth = previewOnRight
|
||||
? Math.max(20, Math.floor((columns - 10) * 0.4))
|
||||
: Math.max(20, columns - 8)
|
||||
const previewWidth = previewOnRight
|
||||
? Math.max(40, columns - maxPathWidth - 14)
|
||||
: columns - 6
|
||||
|
||||
const handleOpen = (p: string) => {
|
||||
const opened = openFileInExternalEditor(path.resolve(getCwd(), p))
|
||||
logEvent('tengu_quick_open_select', {
|
||||
result_count: results.length,
|
||||
opened_editor: opened,
|
||||
})
|
||||
onDone()
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
const previewOnRight = columns >= 120;
|
||||
const effectivePreviewLines = previewOnRight ? VISIBLE_RESULTS - 1 : PREVIEW_LINES;
|
||||
let t4;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = q => {
|
||||
setQuery(q);
|
||||
const gen = queryGenRef.current = queryGenRef.current + 1;
|
||||
if (!q.trim()) {
|
||||
setResults([]);
|
||||
return;
|
||||
|
||||
const handleInsert = (p: string, mention: boolean) => {
|
||||
onInsert(mention ? `@${p} ` : `${p} `)
|
||||
logEvent('tengu_quick_open_insert', {
|
||||
result_count: results.length,
|
||||
mention,
|
||||
})
|
||||
onDone()
|
||||
}
|
||||
|
||||
return (
|
||||
<FuzzyPicker
|
||||
title="Quick Open"
|
||||
placeholder="Type to search files…"
|
||||
items={results}
|
||||
getKey={p => p}
|
||||
visibleCount={visibleResults}
|
||||
direction="up"
|
||||
previewPosition={previewOnRight ? 'right' : 'bottom'}
|
||||
onQueryChange={handleQueryChange}
|
||||
onFocus={setFocusedPath}
|
||||
onSelect={handleOpen}
|
||||
onTab={{ action: 'mention', handler: p => handleInsert(p, true) }}
|
||||
onShiftTab={{
|
||||
action: 'insert path',
|
||||
handler: p => handleInsert(p, false),
|
||||
}}
|
||||
onCancel={onDone}
|
||||
emptyMessage={q => (q ? 'No matching files' : 'Start typing to search…')}
|
||||
selectAction="open in editor"
|
||||
renderItem={(p, isFocused) => (
|
||||
<Text color={isFocused ? 'suggestion' : undefined}>
|
||||
{truncatePathMiddle(p, maxPathWidth)}
|
||||
</Text>
|
||||
)}
|
||||
renderPreview={p =>
|
||||
preview ? (
|
||||
<>
|
||||
<Text dimColor>
|
||||
{truncatePathMiddle(p, previewWidth)}
|
||||
{preview.path !== p ? ' · loading…' : ''}
|
||||
</Text>
|
||||
{preview.content.split('\n').map((line, i) => (
|
||||
<Text key={i}>
|
||||
{highlightMatch(truncateToWidth(line, previewWidth), query)}
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<LoadingState message="Loading preview…" dimColor />
|
||||
)
|
||||
}
|
||||
generateFileSuggestions(q, true).then(items => {
|
||||
if (gen !== queryGenRef.current) {
|
||||
return;
|
||||
}
|
||||
const paths = items.filter(_temp).map(_temp2).filter(_temp3).map(_temp4);
|
||||
setResults(paths);
|
||||
});
|
||||
};
|
||||
$[3] = t4;
|
||||
} else {
|
||||
t4 = $[3];
|
||||
}
|
||||
const handleQueryChange = t4;
|
||||
let t5;
|
||||
let t6;
|
||||
if ($[4] !== effectivePreviewLines || $[5] !== focusedPath) {
|
||||
t5 = () => {
|
||||
if (!focusedPath) {
|
||||
setPreview(null);
|
||||
return;
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const absolute = path.resolve(getCwd(), focusedPath);
|
||||
readFileInRange(absolute, 0, effectivePreviewLines, undefined, controller.signal).then(r => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
setPreview({
|
||||
path: focusedPath,
|
||||
content: r.content
|
||||
});
|
||||
}).catch(() => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
setPreview({
|
||||
path: focusedPath,
|
||||
content: "(preview unavailable)"
|
||||
});
|
||||
});
|
||||
return () => controller.abort();
|
||||
};
|
||||
t6 = [focusedPath, effectivePreviewLines];
|
||||
$[4] = effectivePreviewLines;
|
||||
$[5] = focusedPath;
|
||||
$[6] = t5;
|
||||
$[7] = t6;
|
||||
} else {
|
||||
t5 = $[6];
|
||||
t6 = $[7];
|
||||
}
|
||||
useEffect(t5, t6);
|
||||
const maxPathWidth = previewOnRight ? Math.max(20, Math.floor((columns - 10) * 0.4)) : Math.max(20, columns - 8);
|
||||
const previewWidth = previewOnRight ? Math.max(40, columns - maxPathWidth - 14) : columns - 6;
|
||||
let t7;
|
||||
if ($[8] !== onDone || $[9] !== results.length) {
|
||||
t7 = p_1 => {
|
||||
const opened = openFileInExternalEditor(path.resolve(getCwd(), p_1));
|
||||
logEvent("tengu_quick_open_select", {
|
||||
result_count: results.length,
|
||||
opened_editor: opened
|
||||
});
|
||||
onDone();
|
||||
};
|
||||
$[8] = onDone;
|
||||
$[9] = results.length;
|
||||
$[10] = t7;
|
||||
} else {
|
||||
t7 = $[10];
|
||||
}
|
||||
const handleOpen = t7;
|
||||
let t8;
|
||||
if ($[11] !== onDone || $[12] !== onInsert || $[13] !== results.length) {
|
||||
t8 = (p_2, mention) => {
|
||||
onInsert(mention ? `@${p_2} ` : `${p_2} `);
|
||||
logEvent("tengu_quick_open_insert", {
|
||||
result_count: results.length,
|
||||
mention
|
||||
});
|
||||
onDone();
|
||||
};
|
||||
$[11] = onDone;
|
||||
$[12] = onInsert;
|
||||
$[13] = results.length;
|
||||
$[14] = t8;
|
||||
} else {
|
||||
t8 = $[14];
|
||||
}
|
||||
const handleInsert = t8;
|
||||
const t9 = previewOnRight ? "right" : "bottom";
|
||||
let t10;
|
||||
if ($[15] !== handleInsert) {
|
||||
t10 = {
|
||||
action: "mention",
|
||||
handler: p_4 => handleInsert(p_4, true)
|
||||
};
|
||||
$[15] = handleInsert;
|
||||
$[16] = t10;
|
||||
} else {
|
||||
t10 = $[16];
|
||||
}
|
||||
let t11;
|
||||
if ($[17] !== handleInsert) {
|
||||
t11 = {
|
||||
action: "insert path",
|
||||
handler: p_5 => handleInsert(p_5, false)
|
||||
};
|
||||
$[17] = handleInsert;
|
||||
$[18] = t11;
|
||||
} else {
|
||||
t11 = $[18];
|
||||
}
|
||||
let t12;
|
||||
if ($[19] !== maxPathWidth) {
|
||||
t12 = (p_6, isFocused) => <Text color={isFocused ? "suggestion" : undefined}>{truncatePathMiddle(p_6, maxPathWidth)}</Text>;
|
||||
$[19] = maxPathWidth;
|
||||
$[20] = t12;
|
||||
} else {
|
||||
t12 = $[20];
|
||||
}
|
||||
let t13;
|
||||
if ($[21] !== preview || $[22] !== previewWidth || $[23] !== query) {
|
||||
t13 = p_7 => preview ? <><Text dimColor={true}>{truncatePathMiddle(p_7, previewWidth)}{preview.path !== p_7 ? " \xB7 loading\u2026" : ""}</Text>{preview.content.split("\n").map((line, i_1) => <Text key={i_1}>{highlightMatch(truncateToWidth(line, previewWidth), query)}</Text>)}</> : <LoadingState message={"Loading preview\u2026"} dimColor={true} />;
|
||||
$[21] = preview;
|
||||
$[22] = previewWidth;
|
||||
$[23] = query;
|
||||
$[24] = t13;
|
||||
} else {
|
||||
t13 = $[24];
|
||||
}
|
||||
let t14;
|
||||
if ($[25] !== handleOpen || $[26] !== onDone || $[27] !== results || $[28] !== t10 || $[29] !== t11 || $[30] !== t12 || $[31] !== t13 || $[32] !== t9 || $[33] !== visibleResults) {
|
||||
t14 = <FuzzyPicker title="Quick Open" placeholder={"Type to search files\u2026"} items={results} getKey={_temp5} visibleCount={visibleResults} direction="up" previewPosition={t9} onQueryChange={handleQueryChange} onFocus={setFocusedPath} onSelect={handleOpen} onTab={t10} onShiftTab={t11} onCancel={onDone} emptyMessage={_temp6} selectAction="open in editor" renderItem={t12} renderPreview={t13} />;
|
||||
$[25] = handleOpen;
|
||||
$[26] = onDone;
|
||||
$[27] = results;
|
||||
$[28] = t10;
|
||||
$[29] = t11;
|
||||
$[30] = t12;
|
||||
$[31] = t13;
|
||||
$[32] = t9;
|
||||
$[33] = visibleResults;
|
||||
$[34] = t14;
|
||||
} else {
|
||||
t14 = $[34];
|
||||
}
|
||||
return t14;
|
||||
}
|
||||
function _temp6(q_0) {
|
||||
return q_0 ? "No matching files" : "Start typing to search\u2026";
|
||||
}
|
||||
function _temp5(p_3) {
|
||||
return p_3;
|
||||
}
|
||||
function _temp4(p_0) {
|
||||
return p_0.split(path.sep).join("/");
|
||||
}
|
||||
function _temp3(p) {
|
||||
return !p.endsWith(path.sep);
|
||||
}
|
||||
function _temp2(i_0) {
|
||||
return i_0.displayText;
|
||||
}
|
||||
function _temp(i) {
|
||||
return i.id.startsWith("file-");
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user