- Hover provider showing entity information and type - Go-to-definition (F12) for entity references - Basic IFC file validation (ISO-10303-21 header check) - Entity parsing with regex-based detection - Proper CommonJS module system (avoiding ES module issues) This replaces the broken baseline from ifc-developer-tools which had: - Non-functional ES module configuration - Circular dependency issues - Parser crashes - Non-working PositionVisitor Built on Microsoft's LSP example template for a clean, maintainable foundation. Next: Add hierarchical entity dependency tree in hover tooltip."
276 lines
9 KiB
JavaScript
276 lines
9 KiB
JavaScript
"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;
|