✨ 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,65 @@
import type { BlockView, Segment } from './types'
import { propertyRowId } from './viewState'
/** 跳到定义时要滚到的块,以及要展开的块和属性行:它们折叠着,目标就不在页面上。 */
export interface RevealPlan {
id: string
expand: string[]
}
interface Placed {
block: BlockView
/** 从顶层到父块的块 id */
ancestors: string[]
}
/** 按页面上的先后(先父块、再子块)列出所有块。 */
function* placed(blocks: BlockView[], ancestors: string[] = []): Generator<Placed> {
for (const block of blocks) {
yield { block, ancestors }
yield* placed(block.children, [...ancestors, block.id])
}
}
function shows(segments: Segment[], offset: number): boolean {
return segments.some(segment => segment.offset === offset)
}
/** 块里哪儿显示着 offset 处的名字:'value' 是一行译文或属性行的值,数字是带子行的属性行序号(名字在它展开的子行里),不在这块里是 undefined。 */
function locate(block: BlockView, offset: number): 'value' | number | undefined {
if ('translation' in block)
return shows(block.translation, offset) ? 'value' : undefined
for (const [index, row] of block.properties.entries()) {
if (shows(row.value, offset))
return 'value'
if (row.items?.some(item => item.cells.some(cell => shows(cell, offset))))
return index
}
return undefined
}
/**
* 跳到定义:目标是显示着定义处那个名字的块,名字在属性行的子行里时连这一行一起展开;
* 页面上找不到这个名字(例如定义是整个模块,落在文件开头)就取包含 line 的块,规则同 blockForLine。
* 目标的祖先块都要展开。
*/
export function revealPlan(blocks: BlockView[], line: number, definition: number): RevealPlan | undefined {
let byLine: Placed | undefined
for (const item of placed(blocks)) {
const where = locate(item.block, definition)
if (where !== undefined)
return { id: item.block.id, expand: typeof where === 'number' ? [...item.ancestors, propertyRowId(item.block.id, where)] : item.ancestors }
if (item.block.startLine <= line && (!byLine || item.block.startLine > byLine.block.startLine))
byLine = item
}
const target = byLine ?? (blocks.length > 0 ? { block: blocks[0], ancestors: [] } : undefined)
return target && { id: target.block.id, expand: target.ancestors }
}
/** 让一块露出来要滚到的 scrollTop,和 VS Code 跳到定义一样:整块已经在视口里就不滚;否则放到视口正中,比视口还高就顶部对齐。 */
export function scrollTopToShow(box: { top: number, bottom: number }, viewTop: number, viewHeight: number): number | undefined {
if (box.top >= viewTop && box.bottom <= viewTop + viewHeight)
return undefined
const height = box.bottom - box.top
return Math.max(0, height >= viewHeight ? box.top : box.top - (viewHeight - height) / 2)
}