✨ feat(vscode): 代码阅读滚进卡片的子块时把外层卡片钉在顶上,同 VS Code 的 Sticky Scroll

This commit is contained in:
cruldra committed 2026-10-08 07:17:26 +08:00
1 parent 5ce2e904f4
commit eb3d1b90cd
17 files changed
+548 -31

No files matched your search

@@ -0,0 +1,63 @@
/**
* 钉在顶上的外层卡片(同 VS Code 编辑器的 Sticky Scroll):滚进一张卡片的子块、卡片自己的开头已经滚出去时,顶上钉一行它的图标和标题,
* 外层的在上、里层的往下摞,每行一样高。竖直坐标都以筛选栏下沿为 0、往下为正(像素)。
*/
/**
* 页面上一张能钉住的卡片:子块画出来了的(折叠着的、被筛掉的、正在编辑的卡片下面没有)。
* top 是卡片上沿,bottom 是它子块最后的下沿(卡片比子块还靠下时取卡片的);depth 是它外面套着几张卡片;left、right 是卡片左右边(视口里的 x)
*/
export interface StickyScope {
id: string
depth: number
top: number
bottom: number
left: number
right: number
}
/** 眼下钉着的卡片,从外到里;offset 是最里那行被下面的内容往上顶了多少(≤ 0,它那张卡片快滚完时) */
export interface PinnedLines {
scopes: StickyScope[]
offset: number
}
export const NO_PINNED: PinnedLines = { scopes: [], offset: 0 }
/**
* 钉住哪几张,照 VS Code 的 `StickyScrollController.findScrollWidgetState`:按页面先后(外层在前)看每张,它那一行在 depth × lineHeight;
* 卡片上沿已经在这一行顶上面、子块的下沿还没过这一行顶时钉住。子块的下沿高过这一行的下沿时,这一行跟着往上推。
* 钉满 limit 行就不再往里看:套得深时留外层的,同 VS Code。scopes 要按页面先后排。
*/
export function pinnedLines(scopes: readonly StickyScope[], lineHeight: number, limit: number): PinnedLines {
const pinned: StickyScope[] = []
let offset = 0
for (const scope of scopes) {
if (pinned.length >= limit)
break
const top = scope.depth * lineHeight
if (top <= scope.top || top > scope.bottom)
continue
pinned.push(scope)
offset = Math.min(0, scope.bottom - (top + lineHeight))
}
return { scopes: pinned, offset }
}
/** 最多钉几行:设置里的 maxLineCount,且不超过可视区域(筛选栏下面,高 viewHeight)的四分之一,同 VS Code,窗口矮时不让钉住的行占满 */
export function lineLimit(viewHeight: number, lineHeight: number, maxLineCount: number): number {
return Math.min(maxLineCount, Math.round(viewHeight / lineHeight / 4))
}
/** 一张卡片滚到顶上时,它上面钉着多高:外层的卡片都钉住,最多 limit 行;它自己在这几行下面露出来 */
export function insetAbove(depth: number, lineHeight: number, limit: number): number {
return Math.min(depth, limit) * lineHeight
}
/** 两次算出来钉的一样(同几张、左右位置一样、推得一样多):一样就不重画 */
export function samePinned(a: PinnedLines, b: PinnedLines): boolean {
return a.offset === b.offset && a.scopes.length === b.scopes.length && a.scopes.every((scope, index) => {
const other = b.scopes[index]
return scope.id === other.id && scope.left === other.left && scope.right === other.right
})
}