/** * 同时在途的最多几个。语言服务一次只处理一个请求(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(version: number, line: number, request: () => Promise, dropped: T): Promise { return new Promise((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() } } }