✨ feat(vscode): 代码阅读页面画链式调用卡片:竖轨道、小链、收起的闭包和合成的表
链的各步排在值下面的竖轨道上,方法一列对齐、轨道线是函数色淡到 35%;实参是链的缩进成空心点小链,注释挂在步骤下;收起的闭包是能点的小框,点开那一步才画挂着的子块(折叠 id 记在属性行下面);跳到定义、查找、高度估算、只看改动都算上链里的字和步骤下的子块
This commit is contained in:
1 parent
ee595f3da9
commit
1956879aa1
13 files changed
+531
-47
No files matched your search
@@ -9,9 +9,11 @@ import { fileAttributes } from '../../../src/codeReader/attributes'
|
||||
import { readBlockViews, toBlockViews } from '../../../src/codeReader/blockView'
|
||||
import { pageBlocks } from '../../../src/codeReader/editor/pageBlocks'
|
||||
import { createPythonAdapter } from '../../../src/codeReader/languages/python'
|
||||
import { createRustAdapter } from '../../../src/codeReader/languages/rust'
|
||||
import { fastapiRule } from '../../../src/codeReader/rules/fastapi'
|
||||
import { runtime, WASM_DIR, withBlocks } from '../../../src/codeReader/testSupport'
|
||||
import { readFixture, runtime, WASM_DIR, withBlocks } from '../../../src/codeReader/testSupport'
|
||||
import { pythonZhTranslator } from '../../../src/codeReader/translators/pythonZh'
|
||||
import { rustZhTranslator } from '../../../src/codeReader/translators/rustZh'
|
||||
import { BlockTree, treeState } from './BlockTree'
|
||||
import { applyFilter } from './filter'
|
||||
import { FilterBar } from './FilterBar'
|
||||
@@ -19,7 +21,7 @@ import { NO_PLACEMENT, placementOf } from './placement'
|
||||
import { EMPTY_CHAT } from './qa'
|
||||
import { staticStore } from './store'
|
||||
import { NO_SPAN } from './testBlocks'
|
||||
import { collapsedIds } from './viewState'
|
||||
import { chainStepId, collapsedIds } from './viewState'
|
||||
|
||||
// 编辑框连着和 VS Code 收发消息的那一套,渲染成 HTML 用不着
|
||||
vi.mock('./vscode', () => ({ postMessage: () => {}, onMessage: () => () => {}, viewState: { get: () => ({ editing: [], folds: {} }), update: () => {} } }))
|
||||
@@ -323,6 +325,81 @@ describe('一行译文下面拆开的各项', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('链式调用卡片', () => {
|
||||
/** 一段 Rust 经过宿主转成的发给页面的块:第一个函数和它函数体里的块 */
|
||||
async function rustBlocks(source: string): Promise<BlockView[]> {
|
||||
const language = createRustAdapter(WASM_DIR)
|
||||
const views = await withBlocks(language, 'rust', source, blocks => toBlockViews(blocks, { source, language, markers: [] }, rustZhTranslator))
|
||||
return pageBlocks(views, source).filter(block => block.kind === 'function')
|
||||
}
|
||||
|
||||
/** 画成页面,函数展开着;open 是点开的那几步(折叠 id) */
|
||||
async function chainPage(source: string, open: string[] = []): Promise<{ root: HTMLElement, chain: BlockView }> {
|
||||
const [fn] = await rustBlocks(source)
|
||||
const folds = Object.fromEntries([[fn.id, false], ...open.map(id => [id, false])])
|
||||
const root = document.createElement('div')
|
||||
root.innerHTML = markup([fn], tree({ collapsed: collapsedIds([fn], folds) }))
|
||||
const chain = fn.children.find(child => 'translation' in child && child.rows?.some(row => row.chain))!
|
||||
return { root, chain }
|
||||
}
|
||||
|
||||
it('起点、步骤两行,链的各步排在「步骤」那一行的值下面的竖轨道上:一步一个点、方法和值;合成的一步下面是各次实参的表;写在上面的注释挂在那一步下面', async () => {
|
||||
const { root, chain } = await chainPage(readFixture('rustChain.rs'))
|
||||
const card = root.querySelector(`[data-id="${chain.id}"]`)!
|
||||
expect(card.querySelector('.cr-code')?.closest('.cr-prop-row')).not.toBeNull()
|
||||
const steps = [...card.querySelectorAll('.cr-chain-step')]
|
||||
expect(steps.map(step => step.querySelector('.cr-chain-line')?.textContent)).toEqual([
|
||||
'.plugin4 次',
|
||||
'.setup闭包|app| · 3 句',
|
||||
'.on_window_event闭包|window, event| · 1 句',
|
||||
'.manageapp_state',
|
||||
'.invoke_handlertauri::generate_handler![ device_resume, settings_get, web::preview_open ]',
|
||||
'.buildtauri::generate_context!()',
|
||||
'.expect"error while building tauri application"',
|
||||
'.run闭包|app, event| · match event · 2 支',
|
||||
])
|
||||
expect(steps.every(step => step.querySelector(':scope > .cr-chain-rail > .cr-chain-dot'))).toBe(true)
|
||||
const plugin = steps[0]
|
||||
expect(plugin.querySelector('.cr-chain-label')?.textContent).toBe('注释')
|
||||
expect(plugin.querySelector('.cr-table-head')?.textContent).toBe('实参')
|
||||
expect([...plugin.querySelectorAll('.cr-table-row')].map(row => row.textContent)).toHaveLength(4)
|
||||
expect(card.closest('.cr-prop-row')).toBeNull()
|
||||
})
|
||||
|
||||
it('收起来的闭包是一个按钮,默认收着、闭包体的子块不画;点开那一步(它的折叠 id 展开)后子块画在那一步下面,别的步照旧收着', async () => {
|
||||
const source = readFixture('rustChain.rs')
|
||||
const closed = (await chainPage(source)).root
|
||||
const setupFold = closed.querySelectorAll('.cr-chain-step')[1].querySelector('button[data-style="fold"]')!
|
||||
expect(setupFold.getAttribute('aria-expanded')).toBe('false')
|
||||
expect(setupFold.getAttribute('aria-label')).toBe('展开闭包')
|
||||
expect(closed.textContent).not.toContain('tunnels.json')
|
||||
const { root, chain } = await chainPage(source, [chainStepId(chain0(await rustBlocks(source)), { row: 1, step: 1 })])
|
||||
const [, setup, windowEvent] = root.querySelectorAll('.cr-chain-step')
|
||||
expect(setup.querySelector('button[data-style="fold"]')?.getAttribute('aria-expanded')).toBe('true')
|
||||
expect(setup.querySelector('.cr-chain-blocks')?.textContent).toContain('tunnels.json')
|
||||
expect(windowEvent.querySelector('.cr-chain-blocks')).toBeNull()
|
||||
expect(root.textContent).not.toContain('window.hide')
|
||||
// 挂在步骤下面的子块不再排到卡片后面那一层
|
||||
expect(root.querySelector(`[data-id="${chain.id}"] + .cr-level`)).toBeNull()
|
||||
})
|
||||
|
||||
it('实参本身是链的一步在值下面缩进成一条小链(空心点),一节一行;变量的初始值是起点,链接在下面', async () => {
|
||||
const source = 'fn f() {\n let names = rows.iter().filter(|r| r.active).collect();\n tauri::Builder::default()\n .plugin(\n opener::Builder::new()\n .open(false)\n .build(),\n )\n .manage(state);\n}\n'
|
||||
const { root } = await chainPage(source)
|
||||
const mini = root.querySelector('.cr-chain-mini')!
|
||||
expect([...mini.querySelectorAll('.cr-chain-step')].map(line => line.textContent)).toEqual(['opener::Builder::new()', '.open(false)', '.build()'])
|
||||
expect(mini.querySelectorAll('.cr-chain-dot[data-hollow]')).toHaveLength(3)
|
||||
const value = [...root.querySelectorAll('.cr-prop-row')].find(row => row.textContent?.startsWith('初始值'))!
|
||||
expect(value.querySelector('.cr-code')?.textContent).toBe('rows')
|
||||
expect([...value.querySelectorAll('.cr-chain-line')].map(line => line.textContent)).toEqual(['遍历', '筛选|r| r.active', '收集'])
|
||||
})
|
||||
})
|
||||
|
||||
/** 一个函数体里第一张链式调用卡片的块 id */
|
||||
function chain0(blocks: BlockView[]): string {
|
||||
return blocks[0].children.find(child => 'translation' in child && child.rows?.some(row => row.chain))!.id
|
||||
}
|
||||
|
||||
describe('编辑框', () => {
|
||||
it('编辑着的块:编辑框里是文档源码按它的编辑范围取的整行、去掉共同缩进,着色取自整份源码的着色', async () => {
|
||||
const source = 'def f(x):\n if x:\n return "a"\n'
|
||||
|
||||
Reference in new issue
Block a user