/** * 钉在顶上的卡片(同 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 } /** 眼下钉着的几行占多高([StickyLines](vscode/webview-ui/src/codeReader/StickyLines.tsx#StickyLines) 画的 `.cr-sticky`),没钉时是 0。 */ export function stickyHeight(): number { return document.querySelector('.cr-sticky')?.offsetHeight ?? 0 } /** 两次算出来钉的一样(同几张、左右位置一样、推得一样多):一样就不重画 */ 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 }) }