⚡ perf(vscode): 预览发文档后在后台问语言扩展的请求排队发:同时在途最多 4 个、离视口近的先问、文档改了排着的作废,看得见的中文名先交

This commit is contained in:
cruldra committed 2026-10-13 14:15:37 +11:00
1 parent 5a0c5490ea
commit 12ad047b4d
7 files changed
+335 -45

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
/**
* 同时在途的最多几个。语言服务一次只处理一个请求(Pylance、tsserver 都是;VS Code 自带的 TypeScript 扩展也在自己这边排队,
* 一次只往 tsserver 发一个),多发出去的只是在它那儿排着,还把用户自己的悬停、跳到定义挤到后面;留几个在途够填上来回的空当。
*/
const LIMIT = 4
/** 页面只报视口顶部那块的起始行,一屏按这么多行算。 */
const SCREEN_LINES = 60
/** 排着的一个请求。 */
interface Waiting {
version: number
line: number
start: () => void
drop: () => void
}
/**
* 文档发出去之后在后台问语言扩展的请求(中文名、库规则查的定义、调用的种类,连同没答时隔一会儿的重试)排队发:
* 同时在途的最多 limit 个;每空出一个位子挑排着的里面离视口最近的(视口顶部往下的按远近,往上的排在往下一屏之后);
* 文档换了版本,排着的旧版本请求不再发,按没答交回去(问的那边见版本变了会丢掉)。用户自己点的(悬停、跳到定义)不排。这里不碰 VS Code。
*/
export class LanguageRequests {
private waiting: Waiting[] = []
private running = 0
constructor(
/** 文档眼下的版本 */
private readonly version: () => number,
/** 页面视口顶部那块的起始行;页面还没报时没有,按文件开头算 */
private readonly visibleLine: () => number | undefined,
private readonly limit = LIMIT,
) {}
/** 排一个 version 这一版文档、问第 line 行的请求;没发就作废的交回 dropped。 */
run<T>(version: number, line: number, request: () => Promise<T>, dropped: T): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.waiting.push({
version,
line,
start: () => {
request().then(resolve, reject).finally(() => {
this.running--
this.next()
})
},
drop: () => resolve(dropped),
})
this.next()
})
}
/** 第 line 行在不在视口里:视口顶部往下一屏。 */
visible(line: number): boolean {
const top = this.visibleLine() ?? 0
return line >= top && line < top + SCREEN_LINES
}
/** 第 line 行离视口多远:视口顶部往下的按行数,往上的排在往下一屏之后。 */
distance(line: number): number {
const top = this.visibleLine() ?? 0
return line >= top ? line - top : top - line + SCREEN_LINES
}
/** 预览关了:排着的都作废。 */
dispose(): void {
const waiting = this.waiting
this.waiting = []
for (const item of waiting)
item.drop()
}
private next(): void {
const version = this.version()
const stale = this.waiting.filter(item => item.version !== version)
if (stale.length > 0) {
this.waiting = this.waiting.filter(item => item.version === version)
for (const item of stale)
item.drop()
}
while (this.running < this.limit && this.waiting.length > 0) {
let nearest = 0
let nearestDistance = this.distance(this.waiting[0].line)
for (let i = 1; i < this.waiting.length; i++) {
const distance = this.distance(this.waiting[i].line)
if (distance < nearestDistance) {
nearest = i
nearestDistance = distance
}
}
const [item] = this.waiting.splice(nearest, 1)
this.running++
item.start()
}
}
}