✨ feat(vscode): 代码阅读预览加 ⌘F 查找栏:整份文件查(折叠着的块里的也算,跳过去时展开),按页面上显示的字不分大小写匹配、中文名也认原名,Enter / ⌘G 跳、Esc 关,高亮直接画在页面上不重画卡片

This commit is contained in:
cruldra committed 2026-10-14 05:40:59 +11:00
1 parent cc3fcda051
commit bd474986bd
27 files changed
+1833 -114

No files matched your search

@@ -8,7 +8,7 @@ import { inlineText } from './segments'
import { SegmentText } from './SegmentText'
import { subRowsLayout, tableCells, tableTemplate } from './subRows'
import { TextPreview } from './TextPreview'
import { propertyItemId, propertyRowId, tableId } from './viewState'
import { propertyItemId, propertyRowId, tableId, textKey } from './viewState'
/** 列表子行里除最后一列以外各列的宽度(字典的键),最后一列占满剩下的;编辑区窄时按比例收,不把最后一列挤没 */
const CELL_WIDTHS = ['min(150px, 28%)', 'min(220px, 38%)']
@@ -64,18 +64,18 @@ function CellText({ cell, parts }: { cell: PreviewText, parts: TextDiff | undefi
return 'markdown' in cell ? <TextPreview text={cell} className="min-w-0" /> : <SegmentText segments={cell} />
}
/** 一格连外面那层:着色的词排在 .cr-code 的段落里,Markdown 说明是块,lead(差异记号、展开按钮)排在它左边。 */
function CellBox({ cell, parts, className, lead, ...attrs }: { cell: PreviewText, parts: TextDiff | undefined, className?: string, lead?: ReactNode, role?: string }): ReactElement {
/** 一格连外面那层:着色的词排在 .cr-code 的段落里,Markdown 说明是块,lead(差异记号、展开按钮)排在它左边。`find` 是这一格的记号(见 [textKey]),记在外面那层上。 */
function CellBox({ cell, parts, className, lead, find, ...attrs }: { cell: PreviewText, parts: TextDiff | undefined, className?: string, lead?: ReactNode, find?: string, role?: string }): ReactElement {
if (isBlock(cell, parts)) {
return (
<div className={cn('flex min-w-0 items-start gap-1', className)} {...attrs}>
<div className={cn('flex min-w-0 items-start gap-1', className)} data-find={find} {...attrs}>
{lead}
<CellText cell={cell} parts={parts} />
</div>
)
}
return (
<p className={cn('cr-code min-w-0', className)} {...attrs}>
<p className={cn('cr-code min-w-0', className)} data-find={find} {...attrs}>
{lead}
<CellText cell={cell} parts={parts} />
</p>
@@ -91,12 +91,12 @@ function cellLabel(cell: PreviewText): string {
* 子行的第一列(字典的键):带下一层的子行前面是折叠箭头(图标、大小、颜色同属性行的),点箭头或这一列切换;
* 同一层里不带下一层的子行在同样的位置留空,第一列才对得齐。
*/
function ItemHead({ id, cell, foldable, folding }: { id: string, cell: PreviewText, foldable: boolean, folding: Folding }): ReactElement {
function ItemHead({ id, cell, foldable, folding, find }: { id: string, cell: PreviewText, foldable: boolean, folding: Folding, find?: string }): ReactElement {
if (!foldable) {
return (
<div className="flex min-w-0 gap-1">
<span className="w-3.5 shrink-0" />
<CellBox cell={cell} parts={undefined} />
<CellBox cell={cell} parts={undefined} find={find} />
</div>
)
}
@@ -116,7 +116,7 @@ function ItemHead({ id, cell, foldable, folding }: { id: string, cell: PreviewTe
<Chevron size={14} strokeWidth={1.8} className="text-[var(--vscode-descriptionForeground)] group-hover:text-[var(--vscode-foreground)]" />
</button>
<div className="min-w-0 cursor-pointer" onClick={toggle}>
<CellBox cell={cell} parts={undefined} />
<CellBox cell={cell} parts={undefined} find={find} />
</div>
</div>
)
@@ -163,9 +163,12 @@ export function PropertyItems({ parentId, items, folding, marks }: { parentId: s
data-diff={change}
style={{ gridTemplateColumns: gridTemplate(item.cells.length) }}
>
{item.cells.map((cell, i) => i === 0 && nested
? <ItemHead key={i} id={id} cell={cell} foldable={children.length > 0 && change !== 'del'} folding={folds} />
: <CellBox key={i} cell={cell} parts={mark?.cells?.[i]} />)}
{item.cells.map((cell, i) => {
const find = change === 'del' ? undefined : textKey(id, String(i))
return i === 0 && nested
? <ItemHead key={i} id={id} cell={cell} foldable={children.length > 0 && change !== 'del'} folding={folds} find={find} />
: <CellBox key={i} cell={cell} parts={mark?.cells?.[i]} find={find} />
})}
</div>
{(open || item.table) && (change === 'del' ? <div className="cr-diff-gone">{below}</div> : below)}
</>
@@ -214,17 +217,18 @@ export function PropertyTable({ parentId, columns, items, marks, folding, toggle
const cells = tableCells(item, columns)
const listed = (item.items?.length ?? 0) > 0
const sign = (i: number): ReactNode => signed && i === 0 && <span className="cr-diff-sign" data-change={change} aria-hidden>{SIGNS[change ?? 'none']}</span>
const find = (i: number): string | undefined => change === 'del' ? undefined : textKey(id, String(i))
/** 一格:讲子块的那一格是展开它的按钮(只放着色的词),别的照常(见 CellBox) */
const cellBox = (cell: PreviewText, i: number, className?: string): ReactElement => toggle && i === 0
? (
<p className={cn('cr-code min-w-0', className)} role="cell">
<p className={cn('cr-code min-w-0', className)} role="cell" data-find={find(i)}>
{sign(i)}
<button type="button" className="cr-item-toggle" aria-expanded={toggle.open} aria-label={`${toggle.open ? '收起' : '展开'}${cellLabel(cell)}`} onClick={toggle.onToggle}>
<CellText cell={cell} parts={mark?.cells?.[i]} />
</button>
</p>
)
: <CellBox cell={cell} parts={mark?.cells?.[i]} className={className} lead={sign(i)} role="cell" />
: <CellBox cell={cell} parts={mark?.cells?.[i]} className={className} lead={sign(i)} find={find(i)} role="cell" />
const tint = toggle?.open && toggle.badge
? { 'data-badge': '', 'style': { '--kind-dark': toggle.badge.color.dark, '--kind-light': toggle.badge.color.light } as CSSProperties }
: {}
@@ -233,7 +237,7 @@ export function PropertyTable({ parentId, columns, items, marks, folding, toggle
{cells.map((cell, i) => (listed || item.table) && i === cells.length - 1
? (
<div key={i} className="cr-table-cell flex min-w-0 flex-col gap-[3px]" role="cell">
<CellBox cell={cell} parts={mark?.cells?.[i]} lead={sign(i)} />
<CellBox cell={cell} parts={mark?.cells?.[i]} lead={sign(i)} find={find(i)} />
{listed && <PropertyItems parentId={id} items={item.items ?? []} folding={change === 'del' ? FIXED : folding} marks={mark?.items} />}
{item.table && <PropertyTable parentId={tableId(id)} columns={item.table.columns} items={item.table.items} marks={mark?.table} folding={change === 'del' ? FIXED : folding} />}
</div>
@@ -248,7 +252,7 @@ export function PropertyTable({ parentId, columns, items, marks, folding, toggle
<div className="pl-[5px]">
<div className="cr-table" role="table" style={{ gridTemplateColumns: tableTemplate(columns.length) }}>
<div className="cr-table-head" role="row">
{columns.map((name, i) => <span key={i} className="cr-table-cell" role="columnheader">{name}</span>)}
{columns.map((name, i) => <span key={i} className="cr-table-cell" role="columnheader" data-find={textKey(parentId, `h${i}`)}>{name}</span>)}
</div>
{removed(-1)}
{items.map((item, index) => {
@@ -308,14 +312,15 @@ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, sl
className="cr-prop-label group flex h-5 items-center gap-1 rounded-sm"
aria-label={`${folded ? '展开' : '折叠'}${row.label}`}
aria-expanded={!folded}
data-find={textKey(id, 'label')}
onClick={() => onToggleFold(id)}
>
<Chevron size={14} strokeWidth={1.8} className="shrink-0 text-[var(--vscode-descriptionForeground)] group-hover:text-[var(--vscode-foreground)]" />
{row.label}
</button>
)
: <span className="cr-prop-label flex h-5 items-center">{row.label}</span>}
<RowValue row={row} mark={mark} />
: <span className="cr-prop-label flex h-5 items-center" data-find={textKey(id, 'label')}>{row.label}</span>}
<RowValue row={row} mark={mark} id={id} />
</div>
{!folded && layout === 'table' && <PropertyTable parentId={id} columns={row.columns ?? []} items={row.items ?? []} marks={mark?.items} folding={folding} toggles={toggles} />}
{!folded && layout === 'list' && <PropertyItems parentId={id} items={row.items ?? []} folding={folding} marks={mark?.items} />}
@@ -330,16 +335,17 @@ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, sl
/**
* 一行的值:着色的词或 Markdown(改了的逐截 / 逐词标);跟着说明(PropertyRow.note,输出参数的 `@return`)时说明接在值后面,
* 同一行放得下就并排,剩的地方不到 12 个字宽时换到下一行。
* 同一行放得下就并排,剩的地方不到 12 个字宽时换到下一行。`id` 是这一行的 id,值和说明按它记上记号(见 [textKey]);删掉的行没有。
*/
function RowValue({ row, mark }: { row: PropertyRow, mark: RowDiff | undefined }): ReactElement {
const value = mark?.value ? <DiffText diff={mark.value} className="min-w-0" /> : <TextPreview text={row.value} className="min-w-0" />
function RowValue({ row, mark, id }: { row: PropertyRow, mark: RowDiff | undefined, id?: string }): ReactElement {
const find = (part: string): string | undefined => id === undefined ? undefined : textKey(id, part)
const value = mark?.value ? <DiffText diff={mark.value} className="min-w-0" find={find('value')} /> : <TextPreview text={row.value} className="min-w-0" find={find('value')} />
if (!row.note)
return value
return (
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2">
{value}
{mark?.note ? <DiffText diff={mark.note} className="min-w-0 flex-[1_1_12em]" /> : <TextPreview text={row.note} className="min-w-0 flex-[1_1_12em]" />}
{mark?.note ? <DiffText diff={mark.note} className="min-w-0 flex-[1_1_12em]" find={find('note')} /> : <TextPreview text={row.note} className="min-w-0 flex-[1_1_12em]" find={find('note')} />}
</div>
)
}