ifc-language-server/client/node_modules/vscode-languageclient/lib/common/features.js
Ryan Schultz 8afacf268a Implemented a working Language Server Protocol (LSP) for IFC files with:
- 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."
2025-12-07 10:20:07 -06:00

291 lines
11 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.WorkspaceFeature = exports.TextDocumentLanguageFeature = exports.TextDocumentEventFeature = exports.DynamicDocumentFeature = exports.DynamicFeature = exports.StaticFeature = exports.ensure = exports.LSPCancellationError = void 0;
const vscode_1 = require("vscode");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const Is = require("./utils/is");
const UUID = require("./utils/uuid");
class LSPCancellationError extends vscode_1.CancellationError {
constructor(data) {
super();
this.data = data;
}
}
exports.LSPCancellationError = LSPCancellationError;
function ensure(target, key) {
if (target[key] === undefined) {
target[key] = {};
}
return target[key];
}
exports.ensure = ensure;
var StaticFeature;
(function (StaticFeature) {
function is(value) {
const candidate = value;
return candidate !== undefined && candidate !== null &&
Is.func(candidate.fillClientCapabilities) && Is.func(candidate.initialize) && Is.func(candidate.getState) && Is.func(candidate.clear) &&
(candidate.fillInitializeParams === undefined || Is.func(candidate.fillInitializeParams));
}
StaticFeature.is = is;
})(StaticFeature || (exports.StaticFeature = StaticFeature = {}));
var DynamicFeature;
(function (DynamicFeature) {
function is(value) {
const candidate = value;
return candidate !== undefined && candidate !== null &&
Is.func(candidate.fillClientCapabilities) && Is.func(candidate.initialize) && Is.func(candidate.getState) && Is.func(candidate.clear) &&
(candidate.fillInitializeParams === undefined || Is.func(candidate.fillInitializeParams)) && Is.func(candidate.register) &&
Is.func(candidate.unregister) && candidate.registrationType !== undefined;
}
DynamicFeature.is = is;
})(DynamicFeature || (exports.DynamicFeature = DynamicFeature = {}));
/**
* An abstract dynamic feature implementation that operates on documents (e.g. text
* documents or notebooks).
*/
class DynamicDocumentFeature {
constructor(client) {
this._client = client;
}
/**
* Returns the state the feature is in.
*/
getState() {
const selectors = this.getDocumentSelectors();
let count = 0;
for (const selector of selectors) {
count++;
for (const document of vscode_1.workspace.textDocuments) {
if (vscode_1.languages.match(selector, document) > 0) {
return { kind: 'document', id: this.registrationType.method, registrations: true, matches: true };
}
}
}
const registrations = count > 0;
return { kind: 'document', id: this.registrationType.method, registrations, matches: false };
}
}
exports.DynamicDocumentFeature = DynamicDocumentFeature;
/**
* An abstract base class to implement features that react to events
* emitted from text documents.
*/
class TextDocumentEventFeature extends DynamicDocumentFeature {
static textDocumentFilter(selectors, textDocument) {
for (const selector of selectors) {
if (vscode_1.languages.match(selector, textDocument) > 0) {
return true;
}
}
return false;
}
constructor(client, event, type, middleware, createParams, textDocument, selectorFilter) {
super(client);
this._event = event;
this._type = type;
this._middleware = middleware;
this._createParams = createParams;
this._textDocument = textDocument;
this._selectorFilter = selectorFilter;
this._selectors = new Map();
this._onNotificationSent = new vscode_1.EventEmitter();
}
getStateInfo() {
return [this._selectors.values(), false];
}
getDocumentSelectors() {
return this._selectors.values();
}
register(data) {
if (!data.registerOptions.documentSelector) {
return;
}
if (!this._listener) {
this._listener = this._event((data) => {
this.callback(data).catch((error) => {
this._client.error(`Sending document notification ${this._type.method} failed.`, error);
});
});
}
this._selectors.set(data.id, this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector));
}
async callback(data) {
const doSend = async (data) => {
const params = this._createParams(data);
await this._client.sendNotification(this._type, params);
this.notificationSent(this.getTextDocument(data), this._type, params);
};
if (this.matches(data)) {
const middleware = this._middleware();
return middleware ? middleware(data, (data) => doSend(data)) : doSend(data);
}
}
matches(data) {
if (this._client.hasDedicatedTextSynchronizationFeature(this._textDocument(data))) {
return false;
}
return !this._selectorFilter || this._selectorFilter(this._selectors.values(), data);
}
get onNotificationSent() {
return this._onNotificationSent.event;
}
notificationSent(textDocument, type, params) {
this._onNotificationSent.fire({ textDocument, type, params });
}
unregister(id) {
this._selectors.delete(id);
if (this._selectors.size === 0 && this._listener) {
this._listener.dispose();
this._listener = undefined;
}
}
clear() {
this._selectors.clear();
this._onNotificationSent.dispose();
if (this._listener) {
this._listener.dispose();
this._listener = undefined;
}
}
getProvider(document) {
for (const selector of this._selectors.values()) {
if (vscode_1.languages.match(selector, document) > 0) {
return {
send: (data) => {
return this.callback(data);
}
};
}
}
return undefined;
}
}
exports.TextDocumentEventFeature = TextDocumentEventFeature;
/**
* A abstract feature implementation that registers language providers
* for text documents using a given document selector.
*/
class TextDocumentLanguageFeature extends DynamicDocumentFeature {
constructor(client, registrationType) {
super(client);
this._registrationType = registrationType;
this._registrations = new Map();
}
*getDocumentSelectors() {
for (const registration of this._registrations.values()) {
const selector = registration.data.registerOptions.documentSelector;
if (selector === null) {
continue;
}
yield this._client.protocol2CodeConverter.asDocumentSelector(selector);
}
}
get registrationType() {
return this._registrationType;
}
register(data) {
if (!data.registerOptions.documentSelector) {
return;
}
let registration = this.registerLanguageProvider(data.registerOptions, data.id);
this._registrations.set(data.id, { disposable: registration[0], data, provider: registration[1] });
}
unregister(id) {
let registration = this._registrations.get(id);
if (registration !== undefined) {
registration.disposable.dispose();
}
}
clear() {
this._registrations.forEach((value) => {
value.disposable.dispose();
});
this._registrations.clear();
}
getRegistration(documentSelector, capability) {
if (!capability) {
return [undefined, undefined];
}
else if (vscode_languageserver_protocol_1.TextDocumentRegistrationOptions.is(capability)) {
const id = vscode_languageserver_protocol_1.StaticRegistrationOptions.hasId(capability) ? capability.id : UUID.generateUuid();
const selector = capability.documentSelector ?? documentSelector;
if (selector) {
return [id, Object.assign({}, capability, { documentSelector: selector })];
}
}
else if (Is.boolean(capability) && capability === true || vscode_languageserver_protocol_1.WorkDoneProgressOptions.is(capability)) {
if (!documentSelector) {
return [undefined, undefined];
}
const options = (Is.boolean(capability) && capability === true ? { documentSelector } : Object.assign({}, capability, { documentSelector }));
return [UUID.generateUuid(), options];
}
return [undefined, undefined];
}
getRegistrationOptions(documentSelector, capability) {
if (!documentSelector || !capability) {
return undefined;
}
return (Is.boolean(capability) && capability === true ? { documentSelector } : Object.assign({}, capability, { documentSelector }));
}
getProvider(textDocument) {
for (const registration of this._registrations.values()) {
let selector = registration.data.registerOptions.documentSelector;
if (selector !== null && vscode_1.languages.match(this._client.protocol2CodeConverter.asDocumentSelector(selector), textDocument) > 0) {
return registration.provider;
}
}
return undefined;
}
getAllProviders() {
const result = [];
for (const item of this._registrations.values()) {
result.push(item.provider);
}
return result;
}
}
exports.TextDocumentLanguageFeature = TextDocumentLanguageFeature;
class WorkspaceFeature {
constructor(client, registrationType) {
this._client = client;
this._registrationType = registrationType;
this._registrations = new Map();
}
getState() {
const registrations = this._registrations.size > 0;
return { kind: 'workspace', id: this._registrationType.method, registrations };
}
get registrationType() {
return this._registrationType;
}
register(data) {
const registration = this.registerLanguageProvider(data.registerOptions);
this._registrations.set(data.id, { disposable: registration[0], provider: registration[1] });
}
unregister(id) {
let registration = this._registrations.get(id);
if (registration !== undefined) {
registration.disposable.dispose();
}
}
clear() {
this._registrations.forEach((registration) => {
registration.disposable.dispose();
});
this._registrations.clear();
}
getProviders() {
const result = [];
for (const registration of this._registrations.values()) {
result.push(registration.provider);
}
return result;
}
}
exports.WorkspaceFeature = WorkspaceFeature;