ifc-language-server/client/out/extension.js
Ryan Schultz 8838480f6e Add Ctrl+Click documentation links for IFC entities
Implements DocumentLinkProvider to make IFC entity type names clickable,
opening the official buildingSMART documentation in the browser.

Features:
- Automatic schema version detection (IFC2X3, IFC4, IFC4X3)
- Complete entity-to-package mapping for IFC2X3 (653 entities)
- Complete entity name mapping for IFC4X3 (876 entities with PascalCase)
- Proper URL generation for all three schema versions:
  * IFC2X3: https://standards.buildingsmart.org/.../[package]/lexical/[entity].htm
  * IFC4: https://standards.buildingsmart.org/.../link/[entity].htm
  * IFC4X3: https://ifc43-docs.standards.buildingsmart.org/.../lexical/[Entity].htm

Usage:
- Ctrl+Click (or Cmd+Click on Mac) on any IFC entity name in an .ifc file
- Tooltip shows "Open [ENTITY] documentation ([SCHEMA])"
- Browser opens to the correct buildingSMART documentation page

Files added:
- client/src/ifcDocumentationLinkProvider.ts - Main provider implementation
- client/src/ifc2x3-mappings.txt - IFC2X3 entity to package mappings
- client/src/ifc4x3-mappings.txt - IFC4X3 entity to PascalCase mappings
- client/src/generate-ifc-provider-FINAL.py - Script to regenerate provider

The provider is registered in extension.ts for the 'ifc' language.
2025-12-07 18:31:35 -06:00

181 lines
No EOL
8.5 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = activate;
exports.deactivate = deactivate;
const path = require("path");
const vscode = require("vscode");
const node_1 = require("vscode-languageclient/node");
const ifcTreeProvider_1 = require("./ifcTreeProvider");
const ifcReferencesProvider_1 = require("./ifcReferencesProvider");
const ifcDocumentationLinkProvider_1 = require("./ifcDocumentationLinkProvider");
let client;
let treeProvider;
let referencesProvider;
function activate(context) {
console.log('=== IFC Extension Activating ===');
// Create and register the hierarchy tree view
treeProvider = new ifcTreeProvider_1.IfcEntityTreeProvider();
const treeView = vscode.window.createTreeView('ifcEntityHierarchy', {
treeDataProvider: treeProvider,
showCollapseAll: true
});
context.subscriptions.push(treeView);
// Create and register the references tree view
referencesProvider = new ifcReferencesProvider_1.IfcReferencesProvider();
const referencesView = vscode.window.createTreeView('ifcEntityReferences', {
treeDataProvider: referencesProvider,
showCollapseAll: true
});
context.subscriptions.push(referencesView);
// Register command to show entity hierarchy
const showHierarchyCommand = vscode.commands.registerCommand('ifc.showEntityHierarchy', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'ifc') {
vscode.window.showWarningMessage('Please open an IFC file first');
return;
}
const position = editor.selection.active;
const line = editor.document.lineAt(position.line).text;
const entityId = getEntityIdFromLine(line, position.character);
if (entityId !== null) {
await treeProvider.updateForEntity(entityId, editor.document);
await vscode.commands.executeCommand("ifcEntityHierarchy.focus");
vscode.window.showInformationMessage(`Showing hierarchy for #${entityId}`);
}
else {
vscode.window.showWarningMessage('Cursor is not on an entity reference');
}
});
context.subscriptions.push(showHierarchyCommand);
// Register command to find all references
const findReferencesCommand = vscode.commands.registerCommand('ifc.findEntityReferences', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'ifc') {
vscode.window.showWarningMessage('Please open an IFC file first');
return;
}
const position = editor.selection.active;
const line = editor.document.lineAt(position.line).text;
const entityId = getEntityIdFromLine(line, position.character);
if (entityId !== null) {
await referencesProvider.findReferences(entityId, editor.document);
await vscode.commands.executeCommand("ifcEntityReferences.focus");
const count = referencesProvider.getReferencesCount();
vscode.window.showInformationMessage(`Found ${count} reference(s) to #${entityId}`);
}
else {
vscode.window.showWarningMessage('Cursor is not on an entity reference');
}
});
context.subscriptions.push(findReferencesCommand);
// Register link provider for Ctrl+Click on entity references
const linkProvider = vscode.languages.registerDocumentLinkProvider({ language: 'ifc' }, {
provideDocumentLinks(document) {
const links = [];
const text = document.getText();
const lines = text.split('\n');
lines.forEach((line, lineIndex) => {
const pattern = /#(\d+)/g;
let match;
while ((match = pattern.exec(line)) !== null) {
const entityId = parseInt(match[1]);
const startPos = new vscode.Position(lineIndex, match.index);
const endPos = new vscode.Position(lineIndex, match.index + match[0].length);
const range = new vscode.Range(startPos, endPos);
const link = new vscode.DocumentLink(range, vscode.Uri.parse(`command:ifc.showEntityHierarchyForId?${encodeURIComponent(JSON.stringify([entityId]))}`));
links.push(link);
}
});
return links;
}
});
context.subscriptions.push(linkProvider);
// Register documentation link provider for entity types (Ctrl+Click to open docs)
const docLinkProvider = vscode.languages.registerDocumentLinkProvider({ language: 'ifc' }, new ifcDocumentationLinkProvider_1.IfcDocumentationLinkProvider());
context.subscriptions.push(docLinkProvider);
// Command to show hierarchy AND references for a specific entity ID (used by Ctrl+Click)
const showHierarchyForIdCommand = vscode.commands.registerCommand('ifc.showEntityHierarchyForId', async (entityId) => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'ifc') {
return;
}
// Update both hierarchy and references
await treeProvider.updateForEntity(entityId, editor.document);
await referencesProvider.findReferences(entityId, editor.document);
// Focus the hierarchy panel (references will be visible below it)
await vscode.commands.executeCommand("ifcEntityHierarchy.focus");
});
context.subscriptions.push(showHierarchyForIdCommand);
// Command to update BOTH hierarchy and references (triggered from context menu)
const updateBothPanelsCommand = vscode.commands.registerCommand('ifc.updateBothPanelsFromTree', async (treeItem) => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'ifc') {
return;
}
// Extract entity ID from the tree item
// For hierarchy tree items
let entityId = treeItem?.entityNode?.id;
// For reference tree items
if (!entityId && treeItem?.reference?.entityId) {
entityId = treeItem.reference.entityId;
}
if (entityId) {
// Update both panels
await treeProvider.updateForEntity(entityId, editor.document);
await referencesProvider.findReferences(entityId, editor.document);
vscode.window.showInformationMessage(`Updated both panels for #${entityId}`);
}
});
context.subscriptions.push(updateBothPanelsCommand);
// Command to handle clicks from hierarchy tree (go to entity AND update references)
const updateReferencesFromTreeCommand = vscode.commands.registerCommand('ifc.updateReferencesFromTree', async (entityId, uri, line, length) => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
// Jump to the entity line
const range = new vscode.Range(new vscode.Position(line, 0), new vscode.Position(line, length));
editor.selection = new vscode.Selection(range.start, range.end);
editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
// Update the references panel for this entity
await referencesProvider.findReferences(entityId, editor.document);
});
context.subscriptions.push(updateReferencesFromTreeCommand);
// The server is implemented in node
const serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js'));
const serverOptions = {
run: { module: serverModule, transport: node_1.TransportKind.ipc },
debug: {
module: serverModule,
transport: node_1.TransportKind.ipc,
}
};
const clientOptions = {
documentSelector: [{ scheme: 'file', language: 'ifc' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.ifc')
}
};
client = new node_1.LanguageClient('ifcLanguageServer', 'IFC Language Server', serverOptions, clientOptions);
client.start();
console.log('=== IFC Language Server client started ===');
}
function getEntityIdFromLine(line, character) {
const pattern = /#(\d+)/g;
let match;
while ((match = pattern.exec(line)) !== null) {
const matchStart = match.index;
const matchEnd = match.index + match[0].length;
if (character >= matchStart && character <= matchEnd) {
return parseInt(match[1]);
}
}
return null;
}
function deactivate() {
if (!client) {
return undefined;
}
return client.stop();
}
//# sourceMappingURL=extension.js.map