"use strict"; /* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.forEach = exports.mapAsync = exports.map = exports.clearTestMode = exports.setTestMode = exports.Semaphore = exports.Delayer = void 0; const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); class Delayer { constructor(defaultDelay) { this.defaultDelay = defaultDelay; this.timeout = undefined; this.completionPromise = undefined; this.onSuccess = undefined; this.task = undefined; } trigger(task, delay = this.defaultDelay) { this.task = task; if (delay >= 0) { this.cancelTimeout(); } if (!this.completionPromise) { this.completionPromise = new Promise((resolve) => { this.onSuccess = resolve; }).then(() => { this.completionPromise = undefined; this.onSuccess = undefined; var result = this.task(); this.task = undefined; return result; }); } if (delay >= 0 || this.timeout === void 0) { this.timeout = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => { this.timeout = undefined; this.onSuccess(undefined); }, delay >= 0 ? delay : this.defaultDelay); } return this.completionPromise; } forceDelivery() { if (!this.completionPromise) { return undefined; } this.cancelTimeout(); let result = this.task(); this.completionPromise = undefined; this.onSuccess = undefined; this.task = undefined; return result; } isTriggered() { return this.timeout !== undefined; } cancel() { this.cancelTimeout(); this.completionPromise = undefined; } cancelTimeout() { if (this.timeout !== undefined) { this.timeout.dispose(); this.timeout = undefined; } } } exports.Delayer = Delayer; class Semaphore { constructor(capacity = 1) { if (capacity <= 0) { throw new Error('Capacity must be greater than 0'); } this._capacity = capacity; this._active = 0; this._waiting = []; } lock(thunk) { return new Promise((resolve, reject) => { this._waiting.push({ thunk, resolve, reject }); this.runNext(); }); } get active() { return this._active; } runNext() { if (this._waiting.length === 0 || this._active === this._capacity) { return; } (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => this.doRunNext()); } doRunNext() { if (this._waiting.length === 0 || this._active === this._capacity) { return; } const next = this._waiting.shift(); this._active++; if (this._active > this._capacity) { throw new Error(`To many thunks active`); } try { const result = next.thunk(); if (result instanceof Promise) { result.then((value) => { this._active--; next.resolve(value); this.runNext(); }, (err) => { this._active--; next.reject(err); this.runNext(); }); } else { this._active--; next.resolve(result); this.runNext(); } } catch (err) { this._active--; next.reject(err); this.runNext(); } } } exports.Semaphore = Semaphore; let $test = false; function setTestMode() { $test = true; } exports.setTestMode = setTestMode; function clearTestMode() { $test = false; } exports.clearTestMode = clearTestMode; const defaultYieldTimeout = 15 /*ms*/; class Timer { constructor(yieldAfter = defaultYieldTimeout) { this.yieldAfter = $test === true ? Math.max(yieldAfter, 2) : Math.max(yieldAfter, defaultYieldTimeout); this.startTime = Date.now(); this.counter = 0; this.total = 0; // start with a counter interval of 1. this.counterInterval = 1; } start() { this.counter = 0; this.total = 0; this.counterInterval = 1; this.startTime = Date.now(); } shouldYield() { if (++this.counter >= this.counterInterval) { const timeTaken = Date.now() - this.startTime; const timeLeft = Math.max(0, this.yieldAfter - timeTaken); this.total += this.counter; this.counter = 0; if (timeTaken >= this.yieldAfter || timeLeft <= 1) { // Yield also if time left <= 1 since we compute the counter // for max < 2 ms. // Start with interval 1 again. We could do some calculation // with using 80% of the last counter however other things (GC) // affect the timing heavily since we have small timings (1 - 15ms). this.counterInterval = 1; this.total = 0; return true; } else { // Only increase the counter until we have spent <= 2 ms. Increasing // the counter further is very fragile since timing is influenced // by other things and can increase the counter too much. This will result // that we yield in average after [14 - 16]ms. switch (timeTaken) { case 0: case 1: this.counterInterval = this.total * 2; break; } } } return false; } } async function map(items, func, token, options) { if (items.length === 0) { return []; } const result = new Array(items.length); const timer = new Timer(options?.yieldAfter); function convertBatch(start) { timer.start(); for (let i = start; i < items.length; i++) { result[i] = func(items[i]); if (timer.shouldYield()) { options?.yieldCallback && options.yieldCallback(); return i + 1; } } return -1; } // Convert the first batch sync on the same frame. let index = convertBatch(0); while (index !== -1) { if (token !== undefined && token.isCancellationRequested) { break; } index = await new Promise((resolve) => { (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => { resolve(convertBatch(index)); }); }); } return result; } exports.map = map; async function mapAsync(items, func, token, options) { if (items.length === 0) { return []; } const result = new Array(items.length); const timer = new Timer(options?.yieldAfter); async function convertBatch(start) { timer.start(); for (let i = start; i < items.length; i++) { result[i] = await func(items[i], token); if (timer.shouldYield()) { options?.yieldCallback && options.yieldCallback(); return i + 1; } } return -1; } let index = await convertBatch(0); while (index !== -1) { if (token !== undefined && token.isCancellationRequested) { break; } index = await new Promise((resolve) => { (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => { resolve(convertBatch(index)); }); }); } return result; } exports.mapAsync = mapAsync; async function forEach(items, func, token, options) { if (items.length === 0) { return; } const timer = new Timer(options?.yieldAfter); function runBatch(start) { timer.start(); for (let i = start; i < items.length; i++) { func(items[i]); if (timer.shouldYield()) { options?.yieldCallback && options.yieldCallback(); return i + 1; } } return -1; } // Convert the first batch sync on the same frame. let index = runBatch(0); while (index !== -1) { if (token !== undefined && token.isCancellationRequested) { break; } index = await new Promise((resolve) => { (0, vscode_languageserver_protocol_1.RAL)().timer.setImmediate(() => { resolve(runBatch(index)); }); }); } } exports.forEach = forEach;