Skip to content

Commit

Permalink
Ensure implicit attributes are properly re-calculated
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-fleck-at committed Feb 12, 2024
1 parent 87e5f77 commit 74281a4
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 35 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
ClientSession,
ClientSessionListener,
ClientSessionManager,
Disposable,
DisposableCollection,
GLSPServerError,
Logger,
Expand Down Expand Up @@ -48,20 +47,31 @@ export class CrossModelStorage implements SourceModelStorage, ClientSessionListe
// load semantic model from document in language model service
const sourceUri = this.getSourceUri(action);
const rootUri = URI.file(sourceUri).toString();
await this.state.modelService.open({ uri: rootUri, clientId: this.state.clientId });
this.toDispose.push(Disposable.create(() => this.state.modelService.close({ uri: rootUri, clientId: this.state.clientId })));
const root = await this.state.modelService.request(rootUri, isCrossModelRoot);
this.state.setSemanticRoot(rootUri, root);
const root = await this.update(rootUri);
if (!root) {
return;
}
this.toDispose.push(await this.state.modelService.open({ uri: rootUri, clientId: this.state.clientId }));
this.toDispose.push(
this.state.modelService.onUpdate<CrossModelRoot>(rootUri, async event => {
if (this.state.clientId !== event.sourceClientId || event.reason !== 'changed') {
this.state.setSemanticRoot(rootUri, event.model);
await this.update(rootUri, event.model);
this.actionDispatcher.dispatchAll(await this.submissionHandler.submitModel('external'));
}
})
);
}

protected async update(uri: string, root?: CrossModelRoot): Promise<CrossModelRoot | undefined> {
const newRoot = root ?? (await this.state.modelService.request(uri, isCrossModelRoot));
if (newRoot) {
this.state.setSemanticRoot(uri, newRoot);
} else {
this.logger.error('Could not find model for ' + uri);
}
return newRoot;
}

saveSourceModel(action: SaveModelAction): MaybePromise<void> {
const saveUri = this.getFileUri(action);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { CrossModelGeneratedModule, CrossModelGeneratedSharedModule } from './ge
import { createCrossModelCompletionParser } from './lexer/cross-model-completion-parser.js';
import { CrossModelLexer } from './lexer/cross-model-lexer.js';
import { CrossModelTokenBuilder } from './lexer/cross-model-token-generator.js';
import { CrossModelLinker } from './references/cross-model-linker.js';

/***************************
* Shared Module
Expand Down Expand Up @@ -128,6 +129,7 @@ export interface CrossModelModuleContext {
export interface CrossModelAddedServices {
references: {
IdProvider: DefaultIdProvider;
Linker: CrossModelLinker;
};
validation: {
CrossModelValidator: CrossModelValidator;
Expand Down Expand Up @@ -158,7 +160,8 @@ export function createCrossModelModule(
ScopeComputation: services => new CrossModelScopeComputation(services),
ScopeProvider: services => new CrossModelScopeProvider(services),
IdProvider: services => new DefaultIdProvider(services),
NameProvider: services => services.references.IdProvider
NameProvider: services => services.references.IdProvider,
Linker: services => new CrossModelLinker(services)
},
validation: {
CrossModelValidator: services => new CrossModelValidator(services)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/********************************************************************************
* Copyright (c) 2024 CrossBreeze.
********************************************************************************/

import { AstNode, DefaultLinker, DocumentState, LangiumDocument } from 'langium';
import { isMapping, isSystemDiagram } from '../generated/ast.js';
import { hasSemanticRoot } from '../util/ast-util.js';

export class CrossModelLinker extends DefaultLinker {
override unlink(document: LangiumDocument<AstNode>): void {
super.unlink(document);
if (hasSemanticRoot(document, isMapping) || hasSemanticRoot(document, isSystemDiagram)) {
// we want to re-compute the implicit attributes for our nodes
document.state = Math.min(document.state, DocumentState.IndexedContent);
}
}
}
11 changes: 8 additions & 3 deletions extensions/crossmodel-lang/src/language-server/util/ast-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,11 @@ export function createAttributeMappingTarget(container: AttributeMapping, target
/**
* Retrieve the document in which the given AST node is contained. A reference to the document is
* usually held by the root node of the AST.
*
* @throws an error if the node is not contained in a document.
*/
export function findDocument<T extends AstNode = AstNode>(node: AstNode): LangiumDocument<T> | undefined {
export function findDocument<T extends AstNode = AstNode>(node?: AstNode): LangiumDocument<T> | undefined {
if (!node) {
return undefined;
}
const rootNode = findRootNode(node);
const result = rootNode.$document;
return result ? <LangiumDocument<T>>result : undefined;
Expand All @@ -198,3 +199,7 @@ export function findSemanticRoot(document: LangiumDocument<CrossModelRoot>): Ent
const root = document.parseResult.value;
return root.entity ?? root.mapping ?? root.relationship ?? root.systemDiagram;
}

export function hasSemanticRoot<T>(document: LangiumDocument<any>, guard: (item: unknown) => item is T): boolean {
return guard(findSemanticRoot(document));
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class ModelService {
*
* @param uri document URI
*/
async open(args: OpenModelArgs): Promise<void> {
async open(args: OpenModelArgs): Promise<Disposable> {
return this.documentManager.open(args);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export class OpenTextDocumentManager {
return this.documentBuilder.onBuildPhase(DocumentState.Validated, (allChangedDocuments, _token) => {
const changedDocument = allChangedDocuments.find(document => document.uri.toString() === uri);
if (changedDocument) {
const sourceClientId = this.getSourceClientId(changedDocument, allChangedDocuments);
const buildTrigger = allChangedDocuments.find(document => document.uri.toString() === this.lastUpdate?.changed?.[0].toString());
const sourceClientId = this.getSourceClientId(buildTrigger ?? changedDocument, allChangedDocuments);
const event: ModelUpdatedEvent<T> = {
model: changedDocument.parseResult.value as T,
sourceClientId,
Expand Down Expand Up @@ -109,12 +110,13 @@ export class OpenTextDocumentManager {
);
}

async open(args: OpenModelArgs): Promise<void> {
async open(args: OpenModelArgs): Promise<Disposable> {
// only create a dummy document if it is already open as we use the synced state anyway
const textDocument = this.isOpen(args.uri)
? this.createDummyDocument(args.uri)
: await this.createDocumentFromFileSystem(args.uri, args.languageId);
this.textDocuments.notifyDidOpenTextDocument({ textDocument }, args.clientId);
return Disposable.create(() => this.close(args));
}

async close(args: CloseModelArgs): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,10 @@ export class OpenableTextDocuments<T extends TextDocument> extends TextDocuments
}
}

getChangeSource(uri: string, version: number): string | undefined {
return this.__changeHistory.get(uri)?.[version];
getChangeSource(uri: string, version?: number): string | undefined {
const history = this.__changeHistory.get(uri);
// given version or last entry
return version ? history?.[version] : history?.at(-1);
}

isOpen(uri: string): boolean {
Expand Down

0 comments on commit 74281a4

Please sign in to comment.