✨ feat(vscode): 代码阅读类块改成属性表并默认折叠,类的说明进「说明」行

This commit is contained in:
cruldra committed 2026-09-25 14:46:33 +08:00
1 parent 3329aca2e1
commit 43323659e1
22 files changed
+580 -121

No files matched your search

@@ -62,7 +62,7 @@ export function BlockLevel({ blocks, kind, tree }: { blocks: BlockView[], kind?:
)
}
/** 预览状态的内容:函数、导入、常量、变量块是属性表,其余是一行译文。 */
/** 预览状态的内容:函数、类、导入、常量、变量块是属性表,其余是一行译文。 */
function BlockPreview({ block, tree }: { block: BlockView, tree: BlockTreeState }): ReactElement {
if ('properties' in block)
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} />
@@ -39,7 +39,7 @@ function PropertyItems({ rowId, items }: { rowId: string, items: PropertyItem[]
)
}
/** 函数、导入、常量、变量块的预览:属性名一列对齐,值按词性着色;输入参数、导入项下面展开子行。 */
/** 函数、类、导入、常量、变量块的预览:属性名一列对齐,值按词性着色;输入参数、导入项下面展开子行。 */
export function PropertyGrid({ blockId, rows, collapsed, onToggleFold }: {
blockId: string
rows: PropertyRow[]
+1 -1
View File
@@ -1,7 +1,7 @@
import MarkdownIt from 'markdown-it'
/**
* 注释、函数说明的 Markdown 排版:CommonMark 加 GFM 的表格、删除线,带 http(s):// 的网址和邮箱自动成链接。
* 注释、函数和类说明的 Markdown 排版:CommonMark 加 GFM 的表格、删除线,带 http(s):// 的网址和邮箱自动成链接。
* 源码里的 HTML 当文字显示;链接只认 http / https / mailto,别的写法照原文显示——
* webview 会拦下 <a> 的点击交给 VS Code 在外部打开。图片不渲染:webview 的 CSP 不放行外部图片。
*/
@@ -245,6 +245,28 @@ describe('跳到定义:TS / Python 源码里闪哪一处', () => {
expect(await at('line of')).toEqual({ plan: { id: '1.0', expand: ['1'] }, flashed: 'for (const line of lines) {' })
})
it('类:类名是块自己的名字闪整块,泛型参数只闪「类型参数」一行;类默认折叠,跳到类里的方法、参数要先展开它', async () => {
const source = [
'export class Repo<T> extends Base {',
' load(id: T) {',
' return id',
' }',
'}',
].join('\n')
const at = (needle: string): ReturnType<typeof jump> => jump(ts, 'typescript', typescriptZhTranslator, source, needle)
expect(await at('Repo')).toEqual({ plan: { id: '0', expand: [] }, flashed: 'export class Repo<T> extends Base {' })
expect(await at('T>')).toEqual({ plan: { id: '0', row: '0/p3', expand: [] }, flashed: '类型参数: T' })
expect(await at('load')).toEqual({ plan: { id: '0.0', expand: ['0'] }, flashed: 'load(id: T) {' })
expect(await at('id: T')).toEqual({ plan: { id: '0.0', row: '0.0/p3/0', expand: ['0', '0.0/p3'] }, flashed: 'id | T | ' })
const views = await withBlocks(ts, 'typescript', source, blocks => toBlockViews(blocks, source, ts, typescriptZhTranslator))
expect([...collapsedIds(views, {})].sort()).toEqual(['0', '0.0'])
const pySource = ['class NotFound(AppException):', ' """找不到。"""', ' def run(self):', ' pass'].join('\n')
const atPy = (needle: string): ReturnType<typeof jump> => jump(py, 'python', pythonZhTranslator, pySource, needle)
expect(await atPy('NotFound')).toEqual({ plan: { id: '0', expand: [] }, flashed: 'class NotFound(AppException):' })
expect(await atPy('run')).toEqual({ plan: { id: '0.0', expand: ['0'] }, flashed: 'def run(self):' })
})
it('typeScript:翻译插件没标 defines 时闪整块', async () => {
const at = (needle: string): ReturnType<typeof jump> => jump(ts, 'typescript', withoutDefines(typescriptZhTranslator), TS, needle)
expect(await at('path: string')).toEqual({ plan: { id: '1', expand: ['1/p3'] }, flashed: 'export function load(path: string, { encoding }: Options): string {' })
+1 -1
View File
@@ -37,7 +37,7 @@ function shows(text: PreviewText, offset: number): boolean {
/**
* 块里哪儿是 offset 处的定义,从具体到笼统:
* ① 块自己的名字(函数名、只声明一个变量时的变量名)→ 整块;
* ① 块自己的名字(函数名、类名、只声明一个变量时的变量名)→ 整块;
* ② 属性表里讲这个定义的一行 / 子行(defines 盖住 offset:参数、导入项、多变量声明里的一个)→ 只闪它,子行连它所在的行一起展开;
* ③ 块里只是显示着这个名字(一行译文里的循环变量、没填 defines 的翻译插件)→ 整块,名字在子行里时展开那一行。
* 不在这块里是 undefined。
+3 -3
View File
@@ -20,7 +20,7 @@ export interface Segment { text: string, role: TokenRole, offset?: number }
/** 一段 Markdown 说明文字:注释、JSDoc、docstring 去掉注释符号、引号和共同缩进后的原文,按 Markdown 渲染成注释色的段落、列表、代码块 */
export interface Markdown { markdown: string }
/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数的说明) */
/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数和类的说明) */
export type PreviewText = Segment[] | Markdown
/** 源码里一段的下标,end 为开区间 */
@@ -53,12 +53,12 @@ interface BlockViewBase {
code: string
/** 源码着色(拼起来等于 code) */
codeTokens: Segment[]
/** 块自己的名字(函数名、只声明一个变量时的变量名)在源码里的范围:跳到的定义就是它时闪整块 */
/** 块自己的名字(函数名、类名、只声明一个变量时的变量名)在源码里的范围:跳到的定义就是它时闪整块 */
defines?: SourceRange
children: BlockView[]
}
/** 函数、导入、常量、变量块预览时是属性表,注释块是 Markdown 说明,其余是一行译文 */
/** 函数、类、导入、常量、变量块预览时是属性表,注释块是 Markdown 说明,其余是一行译文 */
export type BlockView = BlockViewBase & ({ translation: PreviewText } | { properties: PropertyRow[] })
export interface TranslatorOption { id: string, label: string }
@@ -76,12 +76,12 @@ describe('webview 状态', () => {
})
describe('眼下折叠着哪些', () => {
it('没点过:函数块(含方法、嵌套函数)折叠,其余块和属性行展开', () => {
expect([...collapsedIds(BLOCKS, {})].sort()).toEqual(['1', '1.0.0', '2.0'])
it('没点过:函数块(含方法、嵌套函数)、类块折叠,其余块和属性行展开', () => {
expect([...collapsedIds(BLOCKS, {})].sort()).toEqual(['1', '1.0.0', '2', '2.0'])
})
it('点过的按用户的:展开的函数保持展开,折叠的其他块、属性行保持折叠', () => {
const folds = { '1': false, '2': true, '1.0.0': false, [propertyRowId('0', 2)]: true, [propertyRowId('2.0', 1)]: false }
expect([...collapsedIds(BLOCKS, folds)].sort()).toEqual(['0/p2', '2', '2.0'])
it('点过的按用户的:展开的函数、类保持展开,折叠的其他块、属性行保持折叠', () => {
const folds = { '1': false, '2': false, '1.0': true, '1.0.0': false, [propertyRowId('0', 2)]: true, [propertyRowId('2.0', 1)]: false }
expect([...collapsedIds(BLOCKS, folds)].sort()).toEqual(['0/p2', '1.0', '2.0'])
})
})
@@ -54,9 +54,9 @@ function readViewState(saved: unknown): ViewState {
return isScrollAnchor(state.scroll) ? { editing, folds, scroll: state.scroll } : { editing, folds }
}
/** 块默认折叠吗:函数块收起主体(属性表照常显示),其余块展开。属性行默认都展开。 */
/** 块默认折叠吗:函数、类块收起主体(属性表照常显示),其余块展开。属性行默认都展开。 */
export function foldedByDefault(kind: BlockKind): boolean {
return kind === 'function'
return kind === 'function' || kind === 'class'
}
/** 眼下折叠着的块和属性行:用户点过的按他点的,没点过的按 foldedByDefault。 */