✨ feat(vscode): 代码阅读预览按住 ⌘ / Ctrl 点名字跳到定义

This commit is contained in:
cruldra committed 2026-09-25 12:54:45 +08:00
1 parent e5db1a2e65
commit 4e146eb48f
20 files changed
+870 -60

No files matched your search

@@ -0,0 +1,88 @@
import type { BlockKind, BlockView, PropertyRow, Segment } from './types'
import { describe, expect, it } from 'vitest'
import { revealPlan, scrollTopToShow } from './reveal'
function name(text: string, offset: number): Segment {
return { text, role: 'variable', offset }
}
function word(text: string): Segment {
return { text, role: 'keyword' }
}
function textBlock(id: string, kind: BlockKind, startLine: number, translation: Segment[], children: BlockView[] = []): BlockView {
return { id, kind, startLine, endLine: startLine, code: '', codeTokens: [], children, translation }
}
function factBlock(id: string, kind: BlockKind, startLine: number, properties: PropertyRow[], children: BlockView[] = []): BlockView {
return { id, kind, startLine, endLine: startLine, code: '', codeTokens: [], children, properties }
}
/**
* 0 from luffy_agent.core.db import get_db
* 2 def load(path, user):
* 3 if not user:
* 4 return get_db(path)
* 8 LIMIT = 3
*/
const BLOCKS: BlockView[] = [
factBlock('0', 'import', 0, [
{ label: '来源', value: [name('luffy_agent', 5), { text: '.', role: 'type' }, name('core', 17)] },
{ label: '导入项', value: [{ text: '1 个', role: 'hint' }], items: [{ cells: [[name('get_db', 34)]] }] },
]),
factBlock('1', 'function', 2, [
{ label: '名称', value: [{ text: 'load', role: 'function', offset: 46 }] },
{ label: '异步', value: [{ text: '否', role: 'storage' }] },
{ label: '输入参数', value: [{ text: '2 个', role: 'hint' }], items: [{ cells: [[name('path', 51)], []] }, { cells: [[name('user', 57)], []] }] },
], [
textBlock('1.0', 'if', 3, [word('如果'), word('非'), name('user', 78)], [
textBlock('1.0.0', 'return', 4, [word('返回'), name('get_db', 102), name('path', 109)]),
]),
]),
factBlock('2', 'const', 8, [{ label: '名称', value: [name('LIMIT', 130)] }]),
]
describe('跳到定义:滚到哪块、展开哪些', () => {
it('名字在嵌套块的译文里:目标是那块,展开它所有的祖先块', () => {
expect(revealPlan(BLOCKS, 4, 102)).toEqual({ id: '1.0.0', expand: ['1', '1.0'] })
})
it('名字在属性行展开的子行里(参数):连那一行一起展开', () => {
expect(revealPlan(BLOCKS, 2, 57)).toEqual({ id: '1', expand: ['1/p2'] })
expect(revealPlan(BLOCKS, 0, 34)).toEqual({ id: '0', expand: ['0/p1'] })
})
it('名字在属性行的值里:不用展开属性行', () => {
expect(revealPlan(BLOCKS, 2, 46)).toEqual({ id: '1', expand: [] })
expect(revealPlan(BLOCKS, 8, 130)).toEqual({ id: '2', expand: [] })
})
it('页面上没有这个名字:按行找包含它的块,也展开祖先', () => {
expect(revealPlan(BLOCKS, 3, 999)).toEqual({ id: '1.0', expand: ['1'] })
expect(revealPlan(BLOCKS, 6, 999)).toEqual({ id: '1.0.0', expand: ['1', '1.0'] })
expect(revealPlan(BLOCKS, 20, 999)).toEqual({ id: '2', expand: [] })
})
it('按行找:同一行有几块取外层那块;行在所有块之前取第一块;没有块时没有目标', () => {
const sameLine = [textBlock('0', 'if', 1, [word('如果')], [textBlock('0.0', 'return', 1, [word('返回')])])]
expect(revealPlan(sameLine, 1, 999)).toEqual({ id: '0', expand: [] })
expect(revealPlan(sameLine, 0, 999)).toEqual({ id: '0', expand: [] })
expect(revealPlan([], 0, 0)).toBeUndefined()
})
})
describe('把目标块滚进视口', () => {
it('整块已经在视口里:不滚', () => {
expect(scrollTopToShow({ top: 120, bottom: 160 }, 100, 500)).toBeUndefined()
})
it('在视口外:放到视口正中', () => {
expect(scrollTopToShow({ top: 1000, bottom: 1040 }, 0, 500)).toBe(770)
expect(scrollTopToShow({ top: 80, bottom: 120 }, 400, 500)).toBe(0)
})
it('露了一半也算在视口外;比视口还高就顶部对齐', () => {
expect(scrollTopToShow({ top: 580, bottom: 620 }, 100, 500)).toBe(350)
expect(scrollTopToShow({ top: 900, bottom: 1600 }, 0, 500)).toBe(900)
})
})