import { type Module, inject, DocumentState } from 'langium'; import type { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, PartialLangiumServices, PartialLangiumSharedServices } from 'langium/lsp'; import { createDefaultModule, createDefaultSharedModule } from 'langium/lsp'; import { DomainLangGeneratedModule, DomainLangGeneratedSharedModule } from './generated/module.js'; import { registerValidationChecks } from './validation/domain-lang-validator.js'; import { setInferredRelationshipTypes } from './services/relationship-inference.js'; import { isModel } from './generated/ast.js'; import { QualifiedNameProvider } from './services/naming.js'; import { DomainLangScopeComputation } from './lsp/domain-lang-scope.js'; import { DomainLangScopeProvider } from './lsp/domain-lang-scope-provider.js'; import { DomainLangFormatter } from './lsp/domain-lang-formatter.js'; import { DomainLangHoverProvider } from './lsp/hover/domain-lang-hover.js'; import { DomainLangCompletionProvider } from './lsp/domain-lang-completion.js'; import { DomainLangCodeActionProvider } from './lsp/domain-lang-code-actions.js'; import { DomainLangCodeLensProvider } from './lsp/domain-lang-code-lens-provider.js'; import { DomainLangNodeKindProvider } from './lsp/domain-lang-node-kind-provider.js'; import { DomainLangDocumentSymbolProvider } from './lsp/domain-lang-document-symbol-provider.js'; import { SprottyDefaultModule, SprottySharedModule } from 'langium-sprotty'; import type { IDiagramGenerator, IModelLayoutEngine } from 'sprotty-protocol'; import { ImportResolver } from './services/import-resolver.js'; import { ManifestManager } from './services/workspace-manager.js'; import { PackageBoundaryDetector } from './services/package-boundary-detector.js'; import { DomainLangWorkspaceManager } from './lsp/domain-lang-workspace-manager.js'; import { DomainLangIndexManager } from './lsp/domain-lang-index-manager.js'; import { DomainLangContextMapDiagramGenerator } from './diagram/context-map-diagram-generator.js'; import { createElkLayoutEngine } from './diagram/elk-layout-factory.js'; /** * Declaration of custom services - add your own service classes here. */ export type DomainLangAddedServices = { imports: { ImportResolver: ImportResolver, ManifestManager: ManifestManager, PackageBoundaryDetector: PackageBoundaryDetector }, references: { QualifiedNameProvider: QualifiedNameProvider }, lsp: { Formatter: DomainLangFormatter, HoverProvider: DomainLangHoverProvider, CompletionProvider: DomainLangCompletionProvider, CodeLensProvider: DomainLangCodeLensProvider, CodeActionProvider: DomainLangCodeActionProvider }, diagram: { DiagramGenerator: IDiagramGenerator, ModelLayoutEngine: IModelLayoutEngine }, } /** * Union of Langium default services and your custom services - use this as constructor parameter * of custom service classes. */ export type DomainLangServices = LangiumServices & DomainLangAddedServices const DomainLangSharedModule: Module = { lsp: { NodeKindProvider: () => new DomainLangNodeKindProvider() }, workspace: { WorkspaceManager: (services: LangiumSharedServices) => new DomainLangWorkspaceManager(services), IndexManager: (services: LangiumSharedServices) => new DomainLangIndexManager(services) } }; /** * Dependency injection module that overrides Langium default services and contributes the * declared custom services. The Langium defaults can be partially specified to override only * selected services, while the custom services must be fully specified. */ export const DomainLangModule: Module = { imports: { ImportResolver: (services) => new ImportResolver(services), ManifestManager: () => new ManifestManager({ autoResolve: false, allowNetwork: false }), PackageBoundaryDetector: () => new PackageBoundaryDetector() }, references: { ScopeComputation: (services) => new DomainLangScopeComputation(services), ScopeProvider: (services) => new DomainLangScopeProvider(services), QualifiedNameProvider: () => new QualifiedNameProvider() }, lsp: { Formatter: () => new DomainLangFormatter(), HoverProvider: (services) => new DomainLangHoverProvider(services), CompletionProvider: (services) => new DomainLangCompletionProvider(services), CodeLensProvider: () => new DomainLangCodeLensProvider(), CodeActionProvider: () => new DomainLangCodeActionProvider(), DocumentSymbolProvider: (services) => new DomainLangDocumentSymbolProvider(services) }, diagram: { // SAFETY: LangiumDiagramGenerator (from langium-sprotty) expects a services type that // includes Sprotty-specific extensions not present in DomainLangServices at compile time. // The services object is structurally compatible at runtime because SprottyDefaultModule // (injected above) augments the container with the required Sprotty services. DiagramGenerator: (services) => new DomainLangContextMapDiagramGenerator(services as never), ModelLayoutEngine: () => createElkLayoutEngine(), }, }; /** * Create the full set of services required by Langium. * * First inject the shared services by merging two modules: * - Langium default shared services * - Services generated by langium-cli * * Then inject the language-specific services by merging three modules: * - Langium default language-specific services * - Services generated by langium-cli * - Services specified in this file * * @param context Optional module context with the LSP connection * @returns An object wrapping the shared services and the language-specific services */ export function createDomainLangServices(context: DefaultSharedModuleContext): { shared: LangiumSharedServices, DomainLang: DomainLangServices } { const shared = inject( createDefaultSharedModule(context), DomainLangGeneratedSharedModule, DomainLangSharedModule, // SAFETY: SprottySharedModule's type parameter is not directly compatible with // LangiumSharedServices at the DI container level. The module is structurally // compatible at runtime; this cast bridges the compile-time type gap. SprottySharedModule as never ); const DomainLang = inject( createDefaultModule({ shared }), DomainLangGeneratedModule, // SAFETY: SprottyDefaultModule's type parameter is not directly compatible with // PartialLangiumServices at the DI container level. The module is structurally // compatible at runtime; this cast bridges the compile-time type gap. SprottyDefaultModule as never, DomainLangModule ); shared.ServiceRegistry.register(DomainLang); registerValidationChecks(DomainLang); // Run relationship inference after linking (so cross-references are resolved) // but before validation. This keeps validators as pure observers. shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.Linked, async (documents) => { for (const doc of documents) { const model = doc.parseResult?.value; if (isModel(model)) { setInferredRelationshipTypes(model); } } }); // Late-bind language services into shared module services. // IndexManager and WorkspaceManager are in the shared module (created first), // but need access to ImportResolver from the language module (created second). const indexManager = shared.workspace.IndexManager; if (indexManager instanceof DomainLangIndexManager) { indexManager.setLanguageServices(DomainLang); } else { throw new Error( `Expected DomainLangIndexManager but got ${indexManager?.constructor.name ?? 'undefined'}. ` + `Cannot initialize language services.` ); } const workspaceManager = shared.workspace.WorkspaceManager; if (workspaceManager instanceof DomainLangWorkspaceManager) { workspaceManager.setLanguageServices(DomainLang); } else { throw new Error( `Expected DomainLangWorkspaceManager but got ${workspaceManager?.constructor.name ?? 'undefined'}. ` + `Cannot initialize language services.` ); } if (!context.connection) { // We don't run inside a language server // Therefore, initialize the configuration provider instantly shared.workspace.ConfigurationProvider.initialized({}); } return { shared, DomainLang }; }