97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
/**
|
|
* 同时在途的最多几个。语言服务一次只处理一个请求(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()
|
|
}
|
|
}
|
|
}
|