From 4831d0c639445fa373152a49154d1999287fa0e6 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 13:16:03 +0200 Subject: [PATCH 01/20] Use JayveeService instead of Langium specific one in utils --- libs/interpreter-lib/src/parsing-util.ts | 11 +++++++---- libs/language-server/src/test/langium-utils.ts | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libs/interpreter-lib/src/parsing-util.ts b/libs/interpreter-lib/src/parsing-util.ts index 99d63a23..f3b1473d 100644 --- a/libs/interpreter-lib/src/parsing-util.ts +++ b/libs/interpreter-lib/src/parsing-util.ts @@ -6,7 +6,10 @@ import * as fs from 'node:fs'; import path from 'node:path'; import { type Logger } from '@jvalue/jayvee-execution'; -import { initializeWorkspace } from '@jvalue/jayvee-language-server'; +import { + type JayveeServices, + initializeWorkspace, +} from '@jvalue/jayvee-language-server'; import { type AstNode, type LangiumDocument } from 'langium'; import { type LangiumServices } from 'langium/lsp'; import { DiagnosticSeverity } from 'vscode-languageserver-protocol'; @@ -58,7 +61,7 @@ export async function extractDocumentFromFile( */ export async function extractDocumentFromString( modelString: string, - services: LangiumServices, + services: JayveeServices, logger: Logger, ): Promise { const document = services.shared.workspace.LangiumDocumentFactory.fromString( @@ -104,7 +107,7 @@ export async function validateDocument( export async function extractAstNodeFromFile( filePath: string, - services: LangiumServices, + services: JayveeServices, logger: Logger, ): Promise { return (await extractDocumentFromFile(filePath, services, logger)).parseResult @@ -113,7 +116,7 @@ export async function extractAstNodeFromFile( export async function extractAstNodeFromString( modelString: string, - services: LangiumServices, + services: JayveeServices, logger: Logger, ): Promise { return (await extractDocumentFromString(modelString, services, logger)) diff --git a/libs/language-server/src/test/langium-utils.ts b/libs/language-server/src/test/langium-utils.ts index 1b2aa32d..dd6ec920 100644 --- a/libs/language-server/src/test/langium-utils.ts +++ b/libs/language-server/src/test/langium-utils.ts @@ -7,10 +7,10 @@ * https://github.com/langium/langium/blob/main/packages/langium/src/test/langium-test.ts */ import { type AstNode, type BuildOptions, type LangiumDocument } from 'langium'; -import { type LangiumServices } from 'langium/lsp'; import { type Diagnostic } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; +import { type JayveeServices } from '../lib'; import { initializeWorkspace } from '../lib/builtin-library/jayvee-workspace-manager'; export interface ParseHelperOptions extends BuildOptions { @@ -18,7 +18,7 @@ export interface ParseHelperOptions extends BuildOptions { } export function parseHelper( - services: LangiumServices, + services: JayveeServices, ): ( input: string, options?: ParseHelperOptions, @@ -49,7 +49,7 @@ export interface ValidationResult { } export function validationHelper( - services: LangiumServices, + services: JayveeServices, ): (input: string) => Promise> { const parse = parseHelper(services); return async (input) => { From f60e4e79898280b8ee97e8a8ce6895b7a3e17883 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 13:16:18 +0200 Subject: [PATCH 02/20] Add method findUnresolvedImportURIs to JayveeImportResolver --- .../src/lib/services/import-resolver.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libs/language-server/src/lib/services/import-resolver.ts b/libs/language-server/src/lib/services/import-resolver.ts index 879c9687..6c595662 100644 --- a/libs/language-server/src/lib/services/import-resolver.ts +++ b/libs/language-server/src/lib/services/import-resolver.ts @@ -52,6 +52,25 @@ export class JayveeImportResolver { this.availableElementsPerDocumentCache = new DocumentCache(services.shared); } + /** + * Finds all import URIs that could not be resolved. + */ + findUnresolvedImportURIs(model: JayveeModel): URI[] { + const unresolvedURIs: URI[] = []; + for (const importDefinition of model.imports) { + const uri = this.resolveImportUri(importDefinition); + if (uri === undefined) { + continue; + } + + const isDocumentResolved = this.documents.getDocument(uri) !== undefined; + if (!isDocumentResolved) { + unresolvedURIs.push(uri); + } + } + return unresolvedURIs; + } + resolveImport(importDefinition: ImportDefinition): JayveeModel | undefined { const resolvedDocument = this.resolveImportedDocument(importDefinition); if (resolvedDocument === undefined) { From 11849b57c53e4e9581977d438446d6ecd506cb32 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 13:16:57 +0200 Subject: [PATCH 03/20] Add hook on workspace initialization to load unresolved documents outside of the workspace --- .../jayvee-workspace-manager.ts | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index f16f6cd5..aa7562a8 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -4,20 +4,23 @@ import { DefaultWorkspaceManager, - type LangiumCoreServices, + DocumentState, type LangiumDocument, type LangiumDocumentFactory, - type LangiumSharedCoreServices, } from 'langium'; +import { type LangiumSharedServices } from 'langium/lsp'; import { type WorkspaceFolder } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; +import { type JayveeModel, isJayveeModel } from '../ast'; +import { type JayveeServices } from '../jayvee-module'; + import { getStdLib } from './stdlib'; export class JayveeWorkspaceManager extends DefaultWorkspaceManager { private documentFactory: LangiumDocumentFactory; - constructor(services: LangiumSharedCoreServices) { + constructor(services: LangiumSharedServices) { super(services); this.documentFactory = services.workspace.LangiumDocumentFactory; } @@ -39,10 +42,44 @@ export class JayveeWorkspaceManager extends DefaultWorkspaceManager { * Also loads additional required files, e.g., the standard library */ export async function initializeWorkspace( - services: LangiumCoreServices, + services: JayveeServices, workspaceFolders: WorkspaceFolder[] = [], ): Promise { await services.shared.workspace.WorkspaceManager.initializeWorkspace( workspaceFolders, ); + + addCollectUnresolvedImportHook(services); +} + +function addCollectUnresolvedImportHook(services: JayveeServices): void { + const documentBuilder = services.shared.workspace.DocumentBuilder; + const importResolver = services.ImportResolver; + + documentBuilder.onBuildPhase(DocumentState.IndexedContent, async (docs) => { + for (const doc of docs) { + const model = doc.parseResult.value; + if (!isJayveeModel(model)) { + return; + } + const importURIs = importResolver.findUnresolvedImportURIs(model); + + for (const importURI of importURIs) { + await loadDocumentFromFs(importURI, services); + } + } + }); +} + +async function loadDocumentFromFs( + importURI: URI, + services: JayveeServices, +): Promise { + const langiumDocuments = services.shared.workspace.LangiumDocuments; + const documentBuilder = services.shared.workspace.DocumentBuilder; + const documentFactory = services.shared.workspace.LangiumDocumentFactory; + + const document = await documentFactory.fromUri(importURI); + await documentBuilder.build([document], { validation: true }); + langiumDocuments.addDocument(document); } From a79bbd33413a01884b9e592c69f2e43432a491e3 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 13:17:58 +0200 Subject: [PATCH 04/20] Initialize the workspace in the VSCode extension --- apps/vs-code-extension/src/language-server.ts | 100 +++++++++++++++++- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/apps/vs-code-extension/src/language-server.ts b/apps/vs-code-extension/src/language-server.ts index 2c411483..d585051f 100644 --- a/apps/vs-code-extension/src/language-server.ts +++ b/apps/vs-code-extension/src/language-server.ts @@ -2,8 +2,40 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { createJayveeServices } from '@jvalue/jayvee-language-server'; -import { startLanguageServer } from 'langium/lsp'; +import { + type JayveeServices, + createJayveeServices, + initializeWorkspace, +} from '@jvalue/jayvee-language-server'; +import { + type LangiumSharedServices, + addCallHierarchyHandler, + addCodeActionHandler, + addCodeLensHandler, + addCompletionHandler, + addConfigurationChangeHandler, + addDiagnosticsHandler, + addDocumentHighlightsHandler, + addDocumentLinkHandler, + addDocumentSymbolHandler, + addDocumentUpdateHandler, + addExecuteCommandHandler, + addFileOperationHandler, + addFindReferencesHandler, + addFoldingRangeHandler, + addFormattingHandler, + addGoToDeclarationHandler, + addGoToImplementationHandler, + addGoToTypeDefinitionHandler, + addGotoDefinitionHandler, + addHoverHandler, + addInlayHintHandler, + addRenameHandler, + addSemanticTokenHandler, + addSignatureHelpHandler, + addTypeHierarchyHandler, + addWorkspaceSymbolHandler, +} from 'langium/lsp'; import { NodeFileSystem } from 'langium/node'; import { ProposedFeatures, createConnection } from 'vscode-languageserver/node'; @@ -11,10 +43,68 @@ import { ProposedFeatures, createConnection } from 'vscode-languageserver/node'; const connection = createConnection(ProposedFeatures.all); // Inject the shared services and language-specific services -const { shared } = createJayveeServices({ +const services = createJayveeServices({ connection, ...NodeFileSystem, }); -// Start the language server with the shared services -startLanguageServer(shared); +startLanguageServer(services.shared, services.Jayvee); + +/** + * Starts the language server and registers hooks. + * Adapted from 'langium/lsp' to initialize workspace correctly. + */ +function startLanguageServer( + shared: LangiumSharedServices, + jayvee: JayveeServices, +): void { + const connection = shared.lsp.Connection; + if (!connection) { + throw new Error( + 'Starting a language server requires the languageServer.Connection service to be set.', + ); + } + + addDocumentUpdateHandler(connection, shared); + addFileOperationHandler(connection, shared); + addDiagnosticsHandler(connection, shared); + addCompletionHandler(connection, shared); + addFindReferencesHandler(connection, shared); + addDocumentSymbolHandler(connection, shared); + addGotoDefinitionHandler(connection, shared); + addGoToTypeDefinitionHandler(connection, shared); + addGoToImplementationHandler(connection, shared); + addDocumentHighlightsHandler(connection, shared); + addFoldingRangeHandler(connection, shared); + addFormattingHandler(connection, shared); + addCodeActionHandler(connection, shared); + addRenameHandler(connection, shared); + addHoverHandler(connection, shared); + addInlayHintHandler(connection, shared); + addSemanticTokenHandler(connection, shared); + addExecuteCommandHandler(connection, shared); + addSignatureHelpHandler(connection, shared); + addCallHierarchyHandler(connection, shared); + addTypeHierarchyHandler(connection, shared); + addCodeLensHandler(connection, shared); + addDocumentLinkHandler(connection, shared); + addConfigurationChangeHandler(connection, shared); + addGoToDeclarationHandler(connection, shared); + addWorkspaceSymbolHandler(connection, shared); + + connection.onInitialize(async (params) => { + const initResult = shared.lsp.LanguageServer.initialize(params); + await initializeWorkspace(jayvee, []); // Initialize workspace to register dynamic document loading hooks + return initResult; + }); + connection.onInitialized((params) => { + shared.lsp.LanguageServer.initialized(params); + }); + + // Make the text document manager listen on the connection for open, change and close text document events. + const documents = shared.workspace.TextDocuments; + documents.listen(connection); + + // Start listening for incoming messages from the client. + connection.listen(); +} From f619fe4d5f872840a4701650495066d8aa681230 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 13:49:00 +0200 Subject: [PATCH 05/20] Dynamic file loading from fs as string instead from URI to allow browsing the file in VSCode --- .../jayvee-workspace-manager.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index aa7562a8..9fb75ad6 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { readFile } from 'node:fs/promises'; + import { DefaultWorkspaceManager, DocumentState, @@ -79,7 +81,22 @@ async function loadDocumentFromFs( const documentBuilder = services.shared.workspace.DocumentBuilder; const documentFactory = services.shared.workspace.LangiumDocumentFactory; - const document = await documentFactory.fromUri(importURI); + const file = await loadFileFromUri(importURI); + if (file === undefined) { + return; + } + + const document = documentFactory.fromString(file, importURI); await documentBuilder.build([document], { validation: true }); langiumDocuments.addDocument(document); } + +async function loadFileFromUri(uri: URI): Promise { + try { + const path = uri.fsPath; + const fileBuffer = await readFile(path); + return fileBuffer.toString(); + } catch (e) { + return undefined; + } +} From 641d3bea62ee7b19d2e520d5ada5935da333531e Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 13:52:01 +0200 Subject: [PATCH 06/20] Use FileSystemProvider instead of node:fs --- .../builtin-library/jayvee-workspace-manager.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index 9fb75ad6..86b2fb49 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { readFile } from 'node:fs/promises'; - import { DefaultWorkspaceManager, DocumentState, @@ -81,7 +79,7 @@ async function loadDocumentFromFs( const documentBuilder = services.shared.workspace.DocumentBuilder; const documentFactory = services.shared.workspace.LangiumDocumentFactory; - const file = await loadFileFromUri(importURI); + const file = await loadFileFromUri(importURI, services); if (file === undefined) { return; } @@ -91,11 +89,14 @@ async function loadDocumentFromFs( langiumDocuments.addDocument(document); } -async function loadFileFromUri(uri: URI): Promise { +async function loadFileFromUri( + uri: URI, + services: JayveeServices, +): Promise { + const fileSystemProvider = services.shared.workspace.FileSystemProvider; + try { - const path = uri.fsPath; - const fileBuffer = await readFile(path); - return fileBuffer.toString(); + return await fileSystemProvider.readFile(uri); } catch (e) { return undefined; } From b9abbaaaa5d36f68246ee2608da13aae2e4e2439 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 14:47:38 +0200 Subject: [PATCH 07/20] Add launch config for electric vehicle example --- .vscode/launch.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index f54ba5b9..ccf30954 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -40,6 +40,12 @@ "request": "launch", "type": "node-terminal" }, + { + "name": "Run Electric Vehicles Example", + "command": "npm run example:vehicles", + "request": "launch", + "type": "node-terminal" + }, { "name": "Run Gas Reserve Example", "command": "npm run example:gas", From 675c795d5e7f3af181764b5187809ee39c0ede1b Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 14:52:40 +0200 Subject: [PATCH 08/20] Add hooks before initializing the workspace --- .../src/lib/builtin-library/jayvee-workspace-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index 86b2fb49..16607de8 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -45,11 +45,11 @@ export async function initializeWorkspace( services: JayveeServices, workspaceFolders: WorkspaceFolder[] = [], ): Promise { + addCollectUnresolvedImportHook(services); + await services.shared.workspace.WorkspaceManager.initializeWorkspace( workspaceFolders, ); - - addCollectUnresolvedImportHook(services); } function addCollectUnresolvedImportHook(services: JayveeServices): void { From 1da7dc21248525700119e7156280486b6188eeb9 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 15:29:13 +0200 Subject: [PATCH 09/20] Use mutex to prevent adding documents multiple times --- .../lib/builtin-library/jayvee-workspace-manager.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index 16607de8..ecfb7f34 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -62,11 +62,13 @@ function addCollectUnresolvedImportHook(services: JayveeServices): void { if (!isJayveeModel(model)) { return; } - const importURIs = importResolver.findUnresolvedImportURIs(model); + await services.shared.workspace.WorkspaceLock.write(async () => { + const importURIs = importResolver.findUnresolvedImportURIs(model); - for (const importURI of importURIs) { - await loadDocumentFromFs(importURI, services); - } + for (const importURI of importURIs) { + await loadDocumentFromFs(importURI, services); + } + }); } }); } From 835f9cac8a1ebdbaf7905f13698bd3eba2200fa8 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 15:29:43 +0200 Subject: [PATCH 10/20] Only load documents with correct file extension --- .../lib/builtin-library/jayvee-workspace-manager.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index ecfb7f34..e2cab0de 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -77,6 +77,17 @@ async function loadDocumentFromFs( importURI: URI, services: JayveeServices, ): Promise { + const allowedFileExtensions = services.shared.ServiceRegistry.all.flatMap( + (e) => e.LanguageMetaData.fileExtensions, + ); + const fileExtension = importURI.fsPath.split('.').at(-1); + if ( + fileExtension === undefined || + !allowedFileExtensions.includes(fileExtension) + ) { + return; + } + const langiumDocuments = services.shared.workspace.LangiumDocuments; const documentBuilder = services.shared.workspace.DocumentBuilder; const documentFactory = services.shared.workspace.LangiumDocumentFactory; From 9a93a06415e2e0e9b95e36d88fff615fba9368f5 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 16:39:25 +0200 Subject: [PATCH 11/20] Fix error message when import cannot be resolved --- .../src/lib/validation/checks/import-definition.spec.ts | 8 ++++---- .../src/lib/validation/checks/import-definition.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/language-server/src/lib/validation/checks/import-definition.spec.ts b/libs/language-server/src/lib/validation/checks/import-definition.spec.ts index e821df16..cdd1b7c2 100644 --- a/libs/language-server/src/lib/validation/checks/import-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/import-definition.spec.ts @@ -100,13 +100,13 @@ describe('Validation of ImportDefinition', () => { expect(validationAcceptorMock).toHaveBeenNthCalledWith( 1, 'error', - 'Import from "not-existing-imported-file-deeper.jv" could be resolved. Check if the file exists in the given location.', + 'Import from "not-existing-imported-file-deeper.jv" could not be resolved. Check if the file exists in the given location.', expect.any(Object), ); expect(validationAcceptorMock).toHaveBeenNthCalledWith( 2, 'error', - 'Import from "./not-existing-imported-file-deeper.jv" could be resolved. Check if the file exists in the given location.', + 'Import from "./not-existing-imported-file-deeper.jv" could not be resolved. Check if the file exists in the given location.', expect.any(Object), ); }); @@ -121,13 +121,13 @@ describe('Validation of ImportDefinition', () => { expect(validationAcceptorMock).toHaveBeenNthCalledWith( 1, 'error', - 'Import from "existing-imported-file.njv" could be resolved. Check if the file exists in the given location.', + 'Import from "existing-imported-file.njv" could not be resolved. Check if the file exists in the given location.', expect.any(Object), ); expect(validationAcceptorMock).toHaveBeenNthCalledWith( 2, 'error', - 'Import from "./existing-imported-file.njv" could be resolved. Check if the file exists in the given location.', + 'Import from "./existing-imported-file.njv" could not be resolved. Check if the file exists in the given location.', expect.any(Object), ); }); diff --git a/libs/language-server/src/lib/validation/checks/import-definition.ts b/libs/language-server/src/lib/validation/checks/import-definition.ts index d3cde2e8..7be00b77 100644 --- a/libs/language-server/src/lib/validation/checks/import-definition.ts +++ b/libs/language-server/src/lib/validation/checks/import-definition.ts @@ -98,7 +98,7 @@ function checkPathExists( if (resolvedImport === undefined) { props.validationContext.accept( 'error', - `Import from "${importDefinition.path}" could be resolved. Check if the file exists in the given location.`, + `Import from "${importDefinition.path}" could not be resolved. Check if the file exists in the given location.`, { node: importDefinition, property: 'path', From 28039553dfbf07f1c95e8278dfa3458d5bdebea6 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 17:06:03 +0200 Subject: [PATCH 12/20] Refactor check on correct file extension before loading --- .../lib/builtin-library/jayvee-workspace-manager.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index e2cab0de..caa9f21b 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -80,11 +80,11 @@ async function loadDocumentFromFs( const allowedFileExtensions = services.shared.ServiceRegistry.all.flatMap( (e) => e.LanguageMetaData.fileExtensions, ); - const fileExtension = importURI.fsPath.split('.').at(-1); - if ( - fileExtension === undefined || - !allowedFileExtensions.includes(fileExtension) - ) { + const hasAllowedFileExtension = allowedFileExtensions.some((ext) => + importURI.fsPath.endsWith(ext), + ); + if (!hasAllowedFileExtension) { + console.log(importURI.fsPath); return; } From d94213467ac7fff813f88946988c3b49e931942b Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 17:16:35 +0200 Subject: [PATCH 13/20] Remove mutex due to deadlock --- .../lib/builtin-library/jayvee-workspace-manager.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index caa9f21b..895540f1 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -62,13 +62,11 @@ function addCollectUnresolvedImportHook(services: JayveeServices): void { if (!isJayveeModel(model)) { return; } - await services.shared.workspace.WorkspaceLock.write(async () => { - const importURIs = importResolver.findUnresolvedImportURIs(model); + const importURIs = importResolver.findUnresolvedImportURIs(model); - for (const importURI of importURIs) { - await loadDocumentFromFs(importURI, services); - } - }); + for (const importURI of importURIs) { + await loadDocumentFromFs(importURI, services); + } } }); } From 4ce82e2abf499a61fe6929267409a5ec992842a7 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 26 Jul 2024 17:36:36 +0200 Subject: [PATCH 14/20] Add tests for the dynamic import --- .../lib/lsp/import-dynamic-reference.spec.ts | 90 +++++++++++++++++++ .../invvalid-to-import-wrong-file-ending.jvm | 8 ++ .../invalid-import-not-existing-file.jv | 9 ++ .../models/invalid-import-wrong-file-type.jv | 9 ++ .../valid-import-from-outside-workdir.jv | 9 ++ .../valid-to-import.jv | 8 ++ 6 files changed, 133 insertions(+) create mode 100644 libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts create mode 100644 libs/language-server/src/test/assets/import-dynamic-reference/invvalid-to-import-wrong-file-ending.jvm create mode 100644 libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-not-existing-file.jv create mode 100644 libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv create mode 100644 libs/language-server/src/test/assets/import-dynamic-reference/models/valid-import-from-outside-workdir.jv create mode 100644 libs/language-server/src/test/assets/import-dynamic-reference/valid-to-import.jv diff --git a/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts b/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts new file mode 100644 index 00000000..be549ef7 --- /dev/null +++ b/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import path from 'node:path'; + +import { NodeFileSystem } from 'langium/node'; + +import { + type JayveeServices, + createJayveeServices, + isJayveeModel, +} from '../../lib'; +import { + expectNoParserAndLexerErrors, + parseTestFileInWorkingDir, +} from '../../test'; +import { type JayveeModel } from '../ast'; + +describe('References to imported elements outside of the working directory', () => { + const WORKING_DIR = path.resolve( + __dirname, + '../../test/assets/import-dynamic-reference/models', // use the "deep" directory as working dir to avoid loading the "higher" dir + ); + let services: JayveeServices; + + async function parseModel( + relativeTestFilePath: string, + ): Promise { + const document = await parseTestFileInWorkingDir( + WORKING_DIR, + relativeTestFilePath, + services, + ); + expectNoParserAndLexerErrors(document); + + const parsedModel = document.parseResult.value; + assert(isJayveeModel(parsedModel), 'Test file is not valid Jayvee model'); + return parsedModel; + } + + beforeEach(() => { + // Create language services + services = createJayveeServices(NodeFileSystem).Jayvee; + }); + + const validCases: [string, string][] = [ + // [test name, test file path] + [ + 'should resolve reference to imported element', + 'valid-import-from-outside-workdir.jv', + ], + ]; + test.each(validCases)('%s', async (_, relativeTestFilePath) => { + const model = await parseModel(relativeTestFilePath); + + expect(model.pipelines.length).toEqual(1); // file contains one pipeline + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const blocks = model.pipelines[0]!.blocks; + expect(blocks.length).toEqual(1); // pipeline contains one block + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const reference = blocks[0]!.type; // of an imported block type + + expect(reference.ref).toBeDefined(); + }); + + const invalidCases: [string, string][] = [ + // [test name, test file path] + [ + 'should not resolve reference to file with no jv extension', + 'invalid-import-wrong-file-type.jv', + ], + [ + 'should not resolve reference to file that does not exist', + 'invalid-import-not-existing-file.jv', + ], + ]; + test.each(invalidCases)('%s', async (_, relativeTestFilePath) => { + const model = await parseModel(relativeTestFilePath); + + expect(model.pipelines.length).toEqual(1); // file contains one pipeline + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const blocks = model.pipelines[0]!.blocks; + expect(blocks.length).toEqual(1); // pipeline contains one block + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const reference = blocks[0]!.type; // of an imported block type + + expect(reference.ref).toBeUndefined(); + }); +}); diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/invvalid-to-import-wrong-file-ending.jvm b/libs/language-server/src/test/assets/import-dynamic-reference/invvalid-to-import-wrong-file-ending.jvm new file mode 100644 index 00000000..884de615 --- /dev/null +++ b/libs/language-server/src/test/assets/import-dynamic-reference/invvalid-to-import-wrong-file-ending.jvm @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +publish builtin blocktype PublishedBlockType { + input inPort oftype None; + output outPort oftype None; +} diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-not-existing-file.jv b/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-not-existing-file.jv new file mode 100644 index 00000000..9b213d48 --- /dev/null +++ b/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-not-existing-file.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use { PublishedBlockType } from '../non-existing-file.jv'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv b/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv new file mode 100644 index 00000000..1ece41a6 --- /dev/null +++ b/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use { PublishedBlockType } from '../invvalid-to-import-wrong-file-ending.jvm'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/models/valid-import-from-outside-workdir.jv b/libs/language-server/src/test/assets/import-dynamic-reference/models/valid-import-from-outside-workdir.jv new file mode 100644 index 00000000..0d7c62e7 --- /dev/null +++ b/libs/language-server/src/test/assets/import-dynamic-reference/models/valid-import-from-outside-workdir.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use { PublishedBlockType } from '../valid-to-import.jv'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/valid-to-import.jv b/libs/language-server/src/test/assets/import-dynamic-reference/valid-to-import.jv new file mode 100644 index 00000000..884de615 --- /dev/null +++ b/libs/language-server/src/test/assets/import-dynamic-reference/valid-to-import.jv @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +publish builtin blocktype PublishedBlockType { + input inPort oftype None; + output outPort oftype None; +} From 74a5ae0c9af189855bbfd3c5bd29ed26099ee39d Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Mon, 29 Jul 2024 10:49:10 +0200 Subject: [PATCH 15/20] Place more narrow mutex lock around adding new documents --- .../src/lib/builtin-library/jayvee-workspace-manager.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index 895540f1..bf7a766c 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -97,7 +97,12 @@ async function loadDocumentFromFs( const document = documentFactory.fromString(file, importURI); await documentBuilder.build([document], { validation: true }); - langiumDocuments.addDocument(document); + + await services.shared.workspace.WorkspaceLock.write(() => { + if (!langiumDocuments.hasDocument(document.uri)) { + langiumDocuments.addDocument(document); + } + }); } async function loadFileFromUri( From 25274d6d38d2dd6c9a6ef0302664fa3abe5d685f Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Mon, 29 Jul 2024 15:37:56 +0200 Subject: [PATCH 16/20] Rename std-lib directory to workspace --- apps/docs/docs/dev/04-guides/05-standard-library.md | 4 ++-- libs/language-server/.gitignore | 2 +- libs/language-server/src/lib/index.ts | 2 +- libs/language-server/src/lib/jayvee-module.ts | 2 +- libs/language-server/src/lib/services/import-resolver.ts | 2 +- .../src/lib/validation/docs-constraint-examples.spec.ts | 2 +- .../src/lib/{builtin-library => workspace}/index.ts | 0 .../jayvee-workspace-manager.ts | 0 .../src/lib/{builtin-library => workspace}/stdlib.ts | 0 libs/language-server/src/test/langium-utils.ts | 2 +- libs/language-server/src/test/utils.ts | 2 +- tools/scripts/language-server/generate-stdlib.mjs | 2 +- 12 files changed, 10 insertions(+), 10 deletions(-) rename libs/language-server/src/lib/{builtin-library => workspace}/index.ts (100%) rename libs/language-server/src/lib/{builtin-library => workspace}/jayvee-workspace-manager.ts (100%) rename libs/language-server/src/lib/{builtin-library => workspace}/stdlib.ts (100%) diff --git a/apps/docs/docs/dev/04-guides/05-standard-library.md b/apps/docs/docs/dev/04-guides/05-standard-library.md index 2da1646f..8a118b45 100644 --- a/apps/docs/docs/dev/04-guides/05-standard-library.md +++ b/apps/docs/docs/dev/04-guides/05-standard-library.md @@ -42,7 +42,7 @@ We use code generation to transform these `.jv` files into TypeScript files that ### 2. Builtin libraries -The solution we chose to implement the standard library mechanism is close to the [built-in library tutorial](https://langium.org/guides/builtin-library/) by Langium. The following components are of interest: +The solution we chose to implement the standard library mechanism is close to the [built-in library tutorial](https://langium.org/guides/workspace/) by Langium. The following components are of interest: -- [JayveeWorkspaceManager](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts) in the `language-server` that registers all libraries with the langium framework. +- [JayveeWorkspaceManager](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts) in the `language-server` that registers all libraries with the langium framework. - [StandardLibraryFileSystemProvider](https://github.com/jvalue/jayvee/tree/main/apps/vs-code-extension/src/standard-library-file-system-provider.ts) in the `vs-code-extension` that registers all libraries with the vscode plugin framework. diff --git a/libs/language-server/.gitignore b/libs/language-server/.gitignore index 1f17e0dd..4a8b91d3 100644 --- a/libs/language-server/.gitignore +++ b/libs/language-server/.gitignore @@ -4,4 +4,4 @@ # files generated by Langium src/lib/ast/generated -src/lib/builtin-library/generated +src/lib/workspace/generated diff --git a/libs/language-server/src/lib/index.ts b/libs/language-server/src/lib/index.ts index a6ef2067..8b5f0621 100644 --- a/libs/language-server/src/lib/index.ts +++ b/libs/language-server/src/lib/index.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only export * from './ast'; -export * from './builtin-library'; +export * from './workspace'; export * from './docs'; export * from './services'; export * from './util'; diff --git a/libs/language-server/src/lib/jayvee-module.ts b/libs/language-server/src/lib/jayvee-module.ts index cbf8094f..6f7e3c3e 100644 --- a/libs/language-server/src/lib/jayvee-module.ts +++ b/libs/language-server/src/lib/jayvee-module.ts @@ -24,7 +24,6 @@ import { } from './ast/generated/module'; import { ValueTypeProvider } from './ast/wrappers/value-type/primitive/primitive-value-type-provider'; import { WrapperFactoryProvider } from './ast/wrappers/wrapper-factory-provider'; -import { JayveeWorkspaceManager } from './builtin-library/jayvee-workspace-manager'; import { JayveeValueConverter } from './jayvee-value-converter'; import { JayveeCodeActionProvider, @@ -37,6 +36,7 @@ import { import { JayveeImportResolver } from './services/import-resolver'; import { RuntimeParameterProvider } from './services/runtime-parameter-provider'; import { JayveeValidationRegistry } from './validation/validation-registry'; +import { JayveeWorkspaceManager } from './workspace/jayvee-workspace-manager'; /** * Declaration of custom services for the Jayvee language. diff --git a/libs/language-server/src/lib/services/import-resolver.ts b/libs/language-server/src/lib/services/import-resolver.ts index 6c595662..9fd24f2a 100644 --- a/libs/language-server/src/lib/services/import-resolver.ts +++ b/libs/language-server/src/lib/services/import-resolver.ts @@ -20,8 +20,8 @@ import { isExportableElement, isJayveeModel, } from '../ast/generated/ast'; -import { getStdLib } from '../builtin-library/stdlib'; import { type JayveeServices } from '../jayvee-module'; +import { getStdLib } from '../workspace/stdlib'; export interface ImportDetails { element: ExportableElement; diff --git a/libs/language-server/src/lib/validation/docs-constraint-examples.spec.ts b/libs/language-server/src/lib/validation/docs-constraint-examples.spec.ts index 62f0473c..c2317406 100644 --- a/libs/language-server/src/lib/validation/docs-constraint-examples.spec.ts +++ b/libs/language-server/src/lib/validation/docs-constraint-examples.spec.ts @@ -6,8 +6,8 @@ import { NodeFileSystem } from 'langium/node'; import { validationHelper } from '../../test/langium-utils'; import { getAllBuiltinConstraintTypes } from '../ast'; -import { initializeWorkspace } from '../builtin-library'; import { createJayveeServices } from '../jayvee-module'; +import { initializeWorkspace } from '../workspace'; describe('Validation of docs examples of ConstraintTypes', () => { it('should have no validation errors', async () => { diff --git a/libs/language-server/src/lib/builtin-library/index.ts b/libs/language-server/src/lib/workspace/index.ts similarity index 100% rename from libs/language-server/src/lib/builtin-library/index.ts rename to libs/language-server/src/lib/workspace/index.ts diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts similarity index 100% rename from libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts rename to libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts diff --git a/libs/language-server/src/lib/builtin-library/stdlib.ts b/libs/language-server/src/lib/workspace/stdlib.ts similarity index 100% rename from libs/language-server/src/lib/builtin-library/stdlib.ts rename to libs/language-server/src/lib/workspace/stdlib.ts diff --git a/libs/language-server/src/test/langium-utils.ts b/libs/language-server/src/test/langium-utils.ts index dd6ec920..53d3cbe7 100644 --- a/libs/language-server/src/test/langium-utils.ts +++ b/libs/language-server/src/test/langium-utils.ts @@ -11,7 +11,7 @@ import { type Diagnostic } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { type JayveeServices } from '../lib'; -import { initializeWorkspace } from '../lib/builtin-library/jayvee-workspace-manager'; +import { initializeWorkspace } from '../lib/workspace/jayvee-workspace-manager'; export interface ParseHelperOptions extends BuildOptions { documentUri?: string; diff --git a/libs/language-server/src/test/utils.ts b/libs/language-server/src/test/utils.ts index c9eee53b..2f3ae2c0 100644 --- a/libs/language-server/src/test/utils.ts +++ b/libs/language-server/src/test/utils.ts @@ -20,7 +20,7 @@ import { type JayveeValidationProps, ValidationContext, } from '../lib'; -import { initializeWorkspace } from '../lib/builtin-library/jayvee-workspace-manager'; +import { initializeWorkspace } from '../lib/workspace/jayvee-workspace-manager'; // eslint-disable-next-line @typescript-eslint/no-empty-function export const validationAcceptorMockImpl: ValidationAcceptor = () => {}; diff --git a/tools/scripts/language-server/generate-stdlib.mjs b/tools/scripts/language-server/generate-stdlib.mjs index 5f403580..94d52439 100644 --- a/tools/scripts/language-server/generate-stdlib.mjs +++ b/tools/scripts/language-server/generate-stdlib.mjs @@ -9,7 +9,7 @@ import { join } from "path"; const projectName = 'language-server'; const stdLibInputPath = 'stdlib'; -const outputDirPath = join('lib', 'builtin-library', 'generated'); +const outputDirPath = join('lib', 'workspace', 'generated'); const outputFilePath = join(outputDirPath, 'partial-stdlib.ts'); // Executing this script: node path/to/generate-stdlib.mjs From 86a66f739dc2371825104ee9e0e7adc3f8db2f5c Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Mon, 29 Jul 2024 15:48:42 +0200 Subject: [PATCH 17/20] Extract dynamic file import to own file and initialize while creating jayvee services --- apps/vs-code-extension/src/language-server.ts | 99 +------------------ libs/language-server/src/lib/jayvee-module.ts | 3 + .../src/lib/workspace/dynamic-file-import.ts | 71 +++++++++++++ .../src/lib/workspace/index.ts | 1 + .../lib/workspace/jayvee-workspace-manager.ts | 70 ------------- 5 files changed, 79 insertions(+), 165 deletions(-) create mode 100644 libs/language-server/src/lib/workspace/dynamic-file-import.ts diff --git a/apps/vs-code-extension/src/language-server.ts b/apps/vs-code-extension/src/language-server.ts index d585051f..690e523c 100644 --- a/apps/vs-code-extension/src/language-server.ts +++ b/apps/vs-code-extension/src/language-server.ts @@ -2,40 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type JayveeServices, - createJayveeServices, - initializeWorkspace, -} from '@jvalue/jayvee-language-server'; -import { - type LangiumSharedServices, - addCallHierarchyHandler, - addCodeActionHandler, - addCodeLensHandler, - addCompletionHandler, - addConfigurationChangeHandler, - addDiagnosticsHandler, - addDocumentHighlightsHandler, - addDocumentLinkHandler, - addDocumentSymbolHandler, - addDocumentUpdateHandler, - addExecuteCommandHandler, - addFileOperationHandler, - addFindReferencesHandler, - addFoldingRangeHandler, - addFormattingHandler, - addGoToDeclarationHandler, - addGoToImplementationHandler, - addGoToTypeDefinitionHandler, - addGotoDefinitionHandler, - addHoverHandler, - addInlayHintHandler, - addRenameHandler, - addSemanticTokenHandler, - addSignatureHelpHandler, - addTypeHierarchyHandler, - addWorkspaceSymbolHandler, -} from 'langium/lsp'; +import { createJayveeServices } from '@jvalue/jayvee-language-server'; +import { startLanguageServer } from 'langium/lsp'; import { NodeFileSystem } from 'langium/node'; import { ProposedFeatures, createConnection } from 'vscode-languageserver/node'; @@ -43,68 +11,9 @@ import { ProposedFeatures, createConnection } from 'vscode-languageserver/node'; const connection = createConnection(ProposedFeatures.all); // Inject the shared services and language-specific services -const services = createJayveeServices({ +const { shared } = createJayveeServices({ connection, ...NodeFileSystem, }); -startLanguageServer(services.shared, services.Jayvee); - -/** - * Starts the language server and registers hooks. - * Adapted from 'langium/lsp' to initialize workspace correctly. - */ -function startLanguageServer( - shared: LangiumSharedServices, - jayvee: JayveeServices, -): void { - const connection = shared.lsp.Connection; - if (!connection) { - throw new Error( - 'Starting a language server requires the languageServer.Connection service to be set.', - ); - } - - addDocumentUpdateHandler(connection, shared); - addFileOperationHandler(connection, shared); - addDiagnosticsHandler(connection, shared); - addCompletionHandler(connection, shared); - addFindReferencesHandler(connection, shared); - addDocumentSymbolHandler(connection, shared); - addGotoDefinitionHandler(connection, shared); - addGoToTypeDefinitionHandler(connection, shared); - addGoToImplementationHandler(connection, shared); - addDocumentHighlightsHandler(connection, shared); - addFoldingRangeHandler(connection, shared); - addFormattingHandler(connection, shared); - addCodeActionHandler(connection, shared); - addRenameHandler(connection, shared); - addHoverHandler(connection, shared); - addInlayHintHandler(connection, shared); - addSemanticTokenHandler(connection, shared); - addExecuteCommandHandler(connection, shared); - addSignatureHelpHandler(connection, shared); - addCallHierarchyHandler(connection, shared); - addTypeHierarchyHandler(connection, shared); - addCodeLensHandler(connection, shared); - addDocumentLinkHandler(connection, shared); - addConfigurationChangeHandler(connection, shared); - addGoToDeclarationHandler(connection, shared); - addWorkspaceSymbolHandler(connection, shared); - - connection.onInitialize(async (params) => { - const initResult = shared.lsp.LanguageServer.initialize(params); - await initializeWorkspace(jayvee, []); // Initialize workspace to register dynamic document loading hooks - return initResult; - }); - connection.onInitialized((params) => { - shared.lsp.LanguageServer.initialized(params); - }); - - // Make the text document manager listen on the connection for open, change and close text document events. - const documents = shared.workspace.TextDocuments; - documents.listen(connection); - - // Start listening for incoming messages from the client. - connection.listen(); -} +startLanguageServer(shared); diff --git a/libs/language-server/src/lib/jayvee-module.ts b/libs/language-server/src/lib/jayvee-module.ts index 6f7e3c3e..73d2b071 100644 --- a/libs/language-server/src/lib/jayvee-module.ts +++ b/libs/language-server/src/lib/jayvee-module.ts @@ -36,6 +36,7 @@ import { import { JayveeImportResolver } from './services/import-resolver'; import { RuntimeParameterProvider } from './services/runtime-parameter-provider'; import { JayveeValidationRegistry } from './validation/validation-registry'; +import { addDynamicFileImport } from './workspace'; import { JayveeWorkspaceManager } from './workspace/jayvee-workspace-manager'; /** @@ -150,5 +151,7 @@ export function createJayveeServices(context: DefaultSharedModuleContext): { ); shared.ServiceRegistry.register(Jayvee); + addDynamicFileImport(Jayvee); + return { shared, Jayvee }; } diff --git a/libs/language-server/src/lib/workspace/dynamic-file-import.ts b/libs/language-server/src/lib/workspace/dynamic-file-import.ts new file mode 100644 index 00000000..c5f7a114 --- /dev/null +++ b/libs/language-server/src/lib/workspace/dynamic-file-import.ts @@ -0,0 +1,71 @@ +import { DocumentState } from 'langium'; +import { type URI } from 'vscode-uri'; + +import { type JayveeModel, isJayveeModel } from '../ast'; +import { type JayveeServices } from '../jayvee-module'; + +export function addDynamicFileImport(services: JayveeServices): void { + const documentBuilder = services.shared.workspace.DocumentBuilder; + const importResolver = services.ImportResolver; + + documentBuilder.onBuildPhase(DocumentState.IndexedContent, async (docs) => { + for (const doc of docs) { + const model = doc.parseResult.value; + if (!isJayveeModel(model)) { + return; + } + const importURIs = importResolver.findUnresolvedImportURIs(model); + + for (const importURI of importURIs) { + await loadDocumentFromFs(importURI, services); + } + } + }); +} + +async function loadDocumentFromFs( + importURI: URI, + services: JayveeServices, +): Promise { + const allowedFileExtensions = services.shared.ServiceRegistry.all.flatMap( + (e) => e.LanguageMetaData.fileExtensions, + ); + const hasAllowedFileExtension = allowedFileExtensions.some((ext) => + importURI.fsPath.endsWith(ext), + ); + if (!hasAllowedFileExtension) { + console.log(importURI.fsPath); + return; + } + + const langiumDocuments = services.shared.workspace.LangiumDocuments; + const documentBuilder = services.shared.workspace.DocumentBuilder; + const documentFactory = services.shared.workspace.LangiumDocumentFactory; + + const file = await loadFileFromUri(importURI, services); + if (file === undefined) { + return; + } + + const document = documentFactory.fromString(file, importURI); + await documentBuilder.build([document], { validation: true }); + + await services.shared.workspace.WorkspaceLock.write(() => { + if (!langiumDocuments.hasDocument(document.uri)) { + langiumDocuments.addDocument(document); + } + }); +} + +async function loadFileFromUri( + uri: URI, + services: JayveeServices, +): Promise { + const fileSystemProvider = services.shared.workspace.FileSystemProvider; + + try { + return await fileSystemProvider.readFile(uri); + } catch (e) { + return undefined; + } +} diff --git a/libs/language-server/src/lib/workspace/index.ts b/libs/language-server/src/lib/workspace/index.ts index c52e9c5d..bc279c44 100644 --- a/libs/language-server/src/lib/workspace/index.ts +++ b/libs/language-server/src/lib/workspace/index.ts @@ -3,4 +3,5 @@ // SPDX-License-Identifier: AGPL-3.0-only export { getStdLib } from './stdlib'; +export { addDynamicFileImport } from './dynamic-file-import'; export * from './jayvee-workspace-manager'; diff --git a/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts b/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts index bf7a766c..2ff6b7bc 100644 --- a/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts @@ -4,7 +4,6 @@ import { DefaultWorkspaceManager, - DocumentState, type LangiumDocument, type LangiumDocumentFactory, } from 'langium'; @@ -12,7 +11,6 @@ import { type LangiumSharedServices } from 'langium/lsp'; import { type WorkspaceFolder } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; -import { type JayveeModel, isJayveeModel } from '../ast'; import { type JayveeServices } from '../jayvee-module'; import { getStdLib } from './stdlib'; @@ -45,75 +43,7 @@ export async function initializeWorkspace( services: JayveeServices, workspaceFolders: WorkspaceFolder[] = [], ): Promise { - addCollectUnresolvedImportHook(services); - await services.shared.workspace.WorkspaceManager.initializeWorkspace( workspaceFolders, ); } - -function addCollectUnresolvedImportHook(services: JayveeServices): void { - const documentBuilder = services.shared.workspace.DocumentBuilder; - const importResolver = services.ImportResolver; - - documentBuilder.onBuildPhase(DocumentState.IndexedContent, async (docs) => { - for (const doc of docs) { - const model = doc.parseResult.value; - if (!isJayveeModel(model)) { - return; - } - const importURIs = importResolver.findUnresolvedImportURIs(model); - - for (const importURI of importURIs) { - await loadDocumentFromFs(importURI, services); - } - } - }); -} - -async function loadDocumentFromFs( - importURI: URI, - services: JayveeServices, -): Promise { - const allowedFileExtensions = services.shared.ServiceRegistry.all.flatMap( - (e) => e.LanguageMetaData.fileExtensions, - ); - const hasAllowedFileExtension = allowedFileExtensions.some((ext) => - importURI.fsPath.endsWith(ext), - ); - if (!hasAllowedFileExtension) { - console.log(importURI.fsPath); - return; - } - - const langiumDocuments = services.shared.workspace.LangiumDocuments; - const documentBuilder = services.shared.workspace.DocumentBuilder; - const documentFactory = services.shared.workspace.LangiumDocumentFactory; - - const file = await loadFileFromUri(importURI, services); - if (file === undefined) { - return; - } - - const document = documentFactory.fromString(file, importURI); - await documentBuilder.build([document], { validation: true }); - - await services.shared.workspace.WorkspaceLock.write(() => { - if (!langiumDocuments.hasDocument(document.uri)) { - langiumDocuments.addDocument(document); - } - }); -} - -async function loadFileFromUri( - uri: URI, - services: JayveeServices, -): Promise { - const fileSystemProvider = services.shared.workspace.FileSystemProvider; - - try { - return await fileSystemProvider.readFile(uri); - } catch (e) { - return undefined; - } -} From 4d4b10c960e290111ce62dfe62b6e4c13d3171d5 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Mon, 29 Jul 2024 15:57:31 +0200 Subject: [PATCH 18/20] Add license text --- .../language-server/src/lib/workspace/dynamic-file-import.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/language-server/src/lib/workspace/dynamic-file-import.ts b/libs/language-server/src/lib/workspace/dynamic-file-import.ts index c5f7a114..7c38dafb 100644 --- a/libs/language-server/src/lib/workspace/dynamic-file-import.ts +++ b/libs/language-server/src/lib/workspace/dynamic-file-import.ts @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + import { DocumentState } from 'langium'; import { type URI } from 'vscode-uri'; @@ -34,7 +38,6 @@ async function loadDocumentFromFs( importURI.fsPath.endsWith(ext), ); if (!hasAllowedFileExtension) { - console.log(importURI.fsPath); return; } From add26e42caa6a5a2e412c1f61d6cbef9fcf3f02b Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 30 Jul 2024 08:32:17 +0200 Subject: [PATCH 19/20] Fix copyright year --- .../src/lib/lsp/import-dynamic-reference.spec.ts | 2 +- libs/language-server/src/lib/workspace/dynamic-file-import.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts b/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts index be549ef7..dc9fbbe7 100644 --- a/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts +++ b/libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// SPDX-FileCopyrightText: 2024 Friedrich-Alexander-Universitat Erlangen-Nurnberg // // SPDX-License-Identifier: AGPL-3.0-only diff --git a/libs/language-server/src/lib/workspace/dynamic-file-import.ts b/libs/language-server/src/lib/workspace/dynamic-file-import.ts index 7c38dafb..9109c904 100644 --- a/libs/language-server/src/lib/workspace/dynamic-file-import.ts +++ b/libs/language-server/src/lib/workspace/dynamic-file-import.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// SPDX-FileCopyrightText: 2024 Friedrich-Alexander-Universitat Erlangen-Nurnberg // // SPDX-License-Identifier: AGPL-3.0-only From 77c608cbfa7fb5cf25d842a042de8f5bada50899 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 30 Jul 2024 08:32:28 +0200 Subject: [PATCH 20/20] Fix typo in test file name --- ...-file-ending.jvm => invalid-to-import-wrong-file-ending.jvm} | 0 .../models/invalid-import-wrong-file-type.jv | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename libs/language-server/src/test/assets/import-dynamic-reference/{invvalid-to-import-wrong-file-ending.jvm => invalid-to-import-wrong-file-ending.jvm} (100%) diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/invvalid-to-import-wrong-file-ending.jvm b/libs/language-server/src/test/assets/import-dynamic-reference/invalid-to-import-wrong-file-ending.jvm similarity index 100% rename from libs/language-server/src/test/assets/import-dynamic-reference/invvalid-to-import-wrong-file-ending.jvm rename to libs/language-server/src/test/assets/import-dynamic-reference/invalid-to-import-wrong-file-ending.jvm diff --git a/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv b/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv index 1ece41a6..4d0c8e12 100644 --- a/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv +++ b/libs/language-server/src/test/assets/import-dynamic-reference/models/invalid-import-wrong-file-type.jv @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -use { PublishedBlockType } from '../invvalid-to-import-wrong-file-ending.jvm'; +use { PublishedBlockType } from '../invalid-to-import-wrong-file-ending.jvm'; pipeline TestPipeline { block UsingBlock oftype PublishedBlockType {}