@likec4/language-server 1.24.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { DocumentState, interruptAndCheck } from "langium";
1
+ import { DocumentState } from "langium";
2
2
  import { isLikeC4LangiumDocument, ViewOps } from "../ast.js";
3
3
  import { logger } from "../logger.js";
4
4
  export class LikeC4CodeLensProvider {
@@ -9,13 +9,10 @@ export class LikeC4CodeLensProvider {
9
9
  if (!isLikeC4LangiumDocument(doc)) {
10
10
  return;
11
11
  }
12
- if (doc.state !== DocumentState.Validated) {
13
- logger.debug(`Waiting for document ${doc.uri.path} to be validated`);
14
- await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Validated, doc.uri, cancelToken);
15
- logger.debug(`Document ${doc.uri.path} is validated`);
16
- }
17
- if (cancelToken) {
18
- await interruptAndCheck(cancelToken);
12
+ if (doc.state <= DocumentState.Linked) {
13
+ logger.debug(`Waiting for document ${doc.uri.path} to be Linked`);
14
+ await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Linked, doc.uri, cancelToken);
15
+ logger.debug(`Document is linked`);
19
16
  }
20
17
  const views = doc.parseResult.value.views.flatMap((v) => v.views);
21
18
  return views.flatMap((ast) => {
@@ -1,8 +1,7 @@
1
- import { AstUtils, DocumentState, GrammarUtils, interruptAndCheck } from "langium";
1
+ import { AstUtils, GrammarUtils } from "langium";
2
2
  import { hasLeadingSlash, hasProtocol, isRelative, withoutBase, withoutLeadingSlash } from "ufo";
3
3
  import { ast, isLikeC4LangiumDocument } from "../ast.js";
4
- import { logger, logWarnError } from "../logger.js";
5
- const log = logger.getChild("DocumentLinkProvider");
4
+ import { logWarnError } from "../logger.js";
6
5
  export class LikeC4DocumentLinkProvider {
7
6
  constructor(services) {
8
7
  this.services = services;
@@ -11,19 +10,11 @@ export class LikeC4DocumentLinkProvider {
11
10
  if (!isLikeC4LangiumDocument(doc)) {
12
11
  return [];
13
12
  }
14
- if (doc.state !== DocumentState.Validated) {
15
- log.debug(`Waiting for document ${doc.uri.path} to be validated`);
16
- await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Validated, doc.uri, cancelToken);
17
- log.debug(`Document ${doc.uri.path} is validated`);
18
- }
19
- if (cancelToken) {
20
- await interruptAndCheck(cancelToken);
21
- }
22
13
  return AstUtils.streamAllContents(doc.parseResult.value).filter(ast.isLinkProperty).map((n) => {
23
14
  try {
24
15
  const range = GrammarUtils.findNodeForProperty(n.$cstNode, "value")?.range;
25
- const target = this.resolveLink(doc, n.value);
26
- if (range && hasProtocol(target)) {
16
+ const target = range && this.resolveLink(doc, n.value);
17
+ if (target && hasProtocol(target)) {
27
18
  return {
28
19
  range,
29
20
  target
@@ -1,7 +1,8 @@
1
- import { type AstNode, type MaybePromise } from 'langium';
1
+ import { type AstNode, type LangiumDocument } from 'langium';
2
2
  import type { DocumentSymbolProvider, NodeKindProvider } from 'langium/lsp';
3
+ import type { CancellationToken, DocumentSymbolParams } from 'vscode-languageserver';
3
4
  import { type DocumentSymbol, SymbolKind } from 'vscode-languageserver-types';
4
- import { type LikeC4LangiumDocument, ast } from '../ast';
5
+ import { ast } from '../ast';
5
6
  import type { LikeC4ModelLocator, LikeC4ModelParser } from '../model';
6
7
  import type { LikeC4Services } from '../module';
7
8
  import type { LikeC4NameProvider } from '../references';
@@ -12,7 +13,7 @@ export declare class LikeC4DocumentSymbolProvider implements DocumentSymbolProvi
12
13
  protected readonly parser: LikeC4ModelParser;
13
14
  protected readonly locator: LikeC4ModelLocator;
14
15
  constructor(services: LikeC4Services);
15
- getSymbols({ parseResult: { value: { specifications, models, deployments, views, likec4lib }, }, }: LikeC4LangiumDocument): MaybePromise<DocumentSymbol[]>;
16
+ getSymbols(doc: LangiumDocument, _params: DocumentSymbolParams, cancelToken?: CancellationToken): Promise<DocumentSymbol[]>;
16
17
  protected getLikec4LibSymbol(astLib: ast.LikeC4Lib): DocumentSymbol[];
17
18
  protected getSpecSymbol(astSpec: ast.SpecificationRule): DocumentSymbol[];
18
19
  protected getModelSymbol(astModel: ast.Model): DocumentSymbol[];
@@ -1,10 +1,11 @@
1
1
  import { nonexhaustive } from "@likec4/core";
2
- import { AstUtils, GrammarUtils } from "langium";
2
+ import { AstUtils, DocumentState, GrammarUtils } from "langium";
3
3
  import { filter, isEmpty, isTruthy, map, pipe } from "remeda";
4
4
  import { SymbolKind } from "vscode-languageserver-types";
5
- import { ast } from "../ast.js";
6
- import { logWarnError } from "../logger.js";
5
+ import { ast, isLikeC4LangiumDocument } from "../ast.js";
6
+ import { logger as rootLogger, logWarnError } from "../logger.js";
7
7
  import { readStrictFqn } from "../utils/elementRef.js";
8
+ const logger = rootLogger.getChild("DocumentSymbolProvider");
8
9
  export class LikeC4DocumentSymbolProvider {
9
10
  constructor(services) {
10
11
  this.services = services;
@@ -17,11 +18,20 @@ export class LikeC4DocumentSymbolProvider {
17
18
  nameProvider;
18
19
  parser;
19
20
  locator;
20
- getSymbols({
21
- parseResult: {
22
- value: { specifications, models, deployments, views, likec4lib }
21
+ async getSymbols(doc, _params, cancelToken) {
22
+ if (!isLikeC4LangiumDocument(doc)) {
23
+ return [];
23
24
  }
24
- }) {
25
+ if (doc.state <= DocumentState.Linked) {
26
+ logger.debug(`Waiting for document ${doc.uri.path} to be Linked`);
27
+ await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Linked, doc.uri, cancelToken);
28
+ logger.debug(`document is Linked`);
29
+ }
30
+ const {
31
+ parseResult: {
32
+ value: { specifications, models, deployments, views, likec4lib }
33
+ }
34
+ } = doc;
25
35
  return [
26
36
  ...likec4lib.map((l) => () => this.getLikec4LibSymbol(l)),
27
37
  ...specifications.map((s) => () => this.getSpecSymbol(s)),
@@ -1,11 +1,10 @@
1
- import { type LikeC4LangiumDocument } from '../ast';
1
+ import { type LikeC4LangiumDocument, ast } from '../ast';
2
2
  import type { LikeC4Services } from '../module';
3
3
  import type { LikeC4NameProvider } from '../references';
4
4
  import { DocumentFqnIndex, FqnIndex } from './fqn-index';
5
- export declare class DeploymentsIndex extends FqnIndex {
5
+ export declare class DeploymentsIndex extends FqnIndex<ast.DeploymentElement> {
6
6
  protected services: LikeC4Services;
7
7
  protected Names: LikeC4NameProvider;
8
- protected cachePrefix: string;
9
8
  constructor(services: LikeC4Services);
10
9
  protected createDocumentIndex(document: LikeC4LangiumDocument): DocumentFqnIndex;
11
10
  }
@@ -1,5 +1,5 @@
1
1
  import { ancestorsFqn, AsFqn } from "@likec4/core";
2
- import { MultiMap } from "langium";
2
+ import { MultiMap } from "@likec4/core/utils";
3
3
  import { isDefined, isTruthy } from "remeda";
4
4
  import {
5
5
  ast,
@@ -10,12 +10,11 @@ import { readStrictFqn } from "../utils/elementRef.js";
10
10
  import { DocumentFqnIndex, FqnIndex } from "./fqn-index.js";
11
11
  export class DeploymentsIndex extends FqnIndex {
12
12
  constructor(services) {
13
- super(services);
13
+ super(services, "deployments-index");
14
14
  this.services = services;
15
15
  this.Names = services.references.NameProvider;
16
16
  }
17
17
  Names;
18
- cachePrefix = "deployments-index";
19
18
  createDocumentIndex(document) {
20
19
  const rootNodes = document.parseResult.value.deployments.flatMap((m) => m.elements);
21
20
  if (rootNodes.length === 0) {
@@ -33,7 +32,7 @@ export class DeploymentsIndex extends FqnIndex {
33
32
  id: fqn
34
33
  };
35
34
  ElementOps.writeId(node, fqn);
36
- byfqn.add(fqn, desc);
35
+ byfqn.set(fqn, desc);
37
36
  return desc;
38
37
  };
39
38
  const traverseNode = (node, parentFqn) => {
@@ -50,7 +49,7 @@ export class DeploymentsIndex extends FqnIndex {
50
49
  if (!parentFqn) {
51
50
  root.push(desc);
52
51
  } else {
53
- children.add(parentFqn, desc);
52
+ children.set(parentFqn, desc);
54
53
  }
55
54
  if (ast.isDeployedInstance(node)) {
56
55
  return [];
@@ -68,18 +67,21 @@ export class DeploymentsIndex extends FqnIndex {
68
67
  }
69
68
  }
70
69
  }
71
- const directChildren = children.get(thisFqn);
72
70
  _nested = [
73
- ...directChildren,
71
+ ...children.get(thisFqn) ?? [],
74
72
  ..._nested
75
73
  ];
76
- descendants.addAll(thisFqn, _nested);
74
+ for (const child of _nested) {
75
+ descendants.set(thisFqn, child);
76
+ }
77
77
  if (ast.isExtendDeployment(node)) {
78
- ancestorsFqn(thisFqn).forEach((ancestor) => {
79
- descendants.addAll(ancestor, _nested);
80
- });
78
+ for (const ancestor of ancestorsFqn(thisFqn)) {
79
+ for (const child of _nested) {
80
+ descendants.set(ancestor, child);
81
+ }
82
+ }
81
83
  }
82
- return descendants.get(thisFqn);
84
+ return descendants.get(thisFqn) ?? [];
83
85
  };
84
86
  for (const node of rootNodes) {
85
87
  try {
@@ -1,18 +1,19 @@
1
1
  import { type Fqn } from '@likec4/core/types';
2
- import { DefaultWeakMap } from '@likec4/core/utils';
3
- import { type LangiumDocuments, type Stream, MultiMap } from 'langium';
2
+ import { DefaultWeakMap, MultiMap } from '@likec4/core/utils';
3
+ import { type AstNode, type LangiumDocuments, type Stream, WorkspaceCache } from 'langium';
4
4
  import { type AstNodeDescriptionWithFqn, type LikeC4LangiumDocument, ast } from '../ast';
5
5
  import type { LikeC4Services } from '../module';
6
6
  import { ADisposable } from '../utils';
7
- export declare class FqnIndex extends ADisposable {
7
+ export declare class FqnIndex<AstNd extends AstNode = ast.Element> extends ADisposable {
8
8
  protected services: LikeC4Services;
9
+ private cachePrefix;
9
10
  protected langiumDocuments: LangiumDocuments;
10
11
  protected documentCache: DefaultWeakMap<LikeC4LangiumDocument, DocumentFqnIndex>;
11
- protected cachePrefix: string;
12
- constructor(services: LikeC4Services);
12
+ protected workspaceCache: WorkspaceCache<string, AstNodeDescriptionWithFqn[]>;
13
+ constructor(services: LikeC4Services, cachePrefix?: string);
13
14
  private documents;
14
15
  get(document: LikeC4LangiumDocument): DocumentFqnIndex;
15
- getFqn(el: ast.Element | ast.DeploymentElement): Fqn;
16
+ getFqn(el: AstNd): Fqn;
16
17
  byFqn(fqn: Fqn): Stream<AstNodeDescriptionWithFqn>;
17
18
  directChildrenOf(parent: Fqn): Stream<AstNodeDescriptionWithFqn>;
18
19
  /**
@@ -1,11 +1,11 @@
1
1
  import { invariant, nonNullable } from "@likec4/core";
2
2
  import { AsFqn } from "@likec4/core/types";
3
- import { ancestorsFqn, compareNatural, DefaultWeakMap, sortNaturalByFqn } from "@likec4/core/utils";
3
+ import { ancestorsFqn, compareNatural, DefaultWeakMap, MultiMap, sortNaturalByFqn } from "@likec4/core/utils";
4
4
  import {
5
5
  AstUtils,
6
6
  DocumentState,
7
- MultiMap,
8
- stream
7
+ stream,
8
+ WorkspaceCache
9
9
  } from "langium";
10
10
  import { isDefined, isEmpty, isTruthy } from "remeda";
11
11
  import {
@@ -17,11 +17,13 @@ import { logWarnError } from "../logger.js";
17
17
  import { ADisposable } from "../utils/index.js";
18
18
  import { readStrictFqn } from "../utils/elementRef.js";
19
19
  export class FqnIndex extends ADisposable {
20
- constructor(services) {
20
+ constructor(services, cachePrefix = "fqn-index") {
21
21
  super();
22
22
  this.services = services;
23
+ this.cachePrefix = cachePrefix;
23
24
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
24
25
  this.documentCache = new DefaultWeakMap((doc) => this.createDocumentIndex(doc));
26
+ this.workspaceCache = new WorkspaceCache(services.shared, DocumentState.IndexedContent);
25
27
  this.onDispose(
26
28
  services.shared.workspace.DocumentBuilder.onDocumentPhase(
27
29
  DocumentState.IndexedContent,
@@ -36,7 +38,7 @@ export class FqnIndex extends ADisposable {
36
38
  }
37
39
  langiumDocuments;
38
40
  documentCache;
39
- cachePrefix = "fqn-index";
41
+ workspaceCache;
40
42
  documents() {
41
43
  return this.langiumDocuments.all.filter(
42
44
  (d) => isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
@@ -49,53 +51,64 @@ export class FqnIndex extends ADisposable {
49
51
  return this.documentCache.get(document);
50
52
  }
51
53
  getFqn(el) {
54
+ invariant(ast.isElement(el) || ast.isDeploymentElement(el));
52
55
  let id = ElementOps.readId(el);
53
56
  if (isTruthy(id)) {
54
57
  return id;
55
58
  }
56
59
  const doc = AstUtils.getDocument(el);
57
60
  invariant(isLikeC4LangiumDocument(doc));
61
+ logWarnError(`Document ${doc.uri.path} is not indexed, but getFqn was called`);
58
62
  this.get(doc);
59
63
  return nonNullable(ElementOps.readId(el), "Element fqn must be set, invalid state");
60
64
  }
61
65
  byFqn(fqn) {
62
- return this.documents().flatMap((doc) => {
63
- return this.get(doc).byFqn(fqn);
64
- });
66
+ return stream(this.workspaceCache.get(`${this.cachePrefix}:${fqn}`, () => {
67
+ return this.documents().toArray().flatMap((doc) => {
68
+ return this.get(doc).byFqn(fqn);
69
+ });
70
+ }));
65
71
  }
66
72
  directChildrenOf(parent) {
67
73
  return stream(
68
- this.documents().reduce((map, doc) => {
69
- this.get(doc).children(parent).forEach((desc) => {
70
- map.add(desc.name, desc);
71
- });
72
- return map;
73
- }, new MultiMap()).entriesGroupedByKey().flatMap(([_name, descs]) => descs.length === 1 ? descs : []).toArray().sort((a, b) => compareNatural(a.name, b.name))
74
+ this.workspaceCache.get(`${this.cachePrefix}:directChildrenOf:${parent}`, () => {
75
+ const allchildren = this.documents().reduce((map, doc) => {
76
+ this.get(doc).children(parent).forEach((desc) => {
77
+ map.set(desc.name, desc);
78
+ });
79
+ return map;
80
+ }, new MultiMap());
81
+ return uniqueByName(allchildren).sort((a, b) => compareNatural(a.name, b.name));
82
+ })
74
83
  );
75
84
  }
76
85
  /**
77
86
  * Returns descedant elements with unique names in the scope
78
87
  */
79
88
  uniqueDescedants(parent) {
80
- const { children, descendants } = this.documents().reduce((map, doc) => {
81
- const docIndex = this.get(doc);
82
- docIndex.children(parent).forEach((desc) => {
83
- map.children.add(desc.name, desc);
84
- });
85
- docIndex.descendants(parent).forEach((desc) => {
86
- map.descendants.add(desc.name, desc);
87
- });
88
- return map;
89
- }, {
90
- children: new MultiMap(),
91
- descendants: new MultiMap()
92
- });
93
- const uniqueChildren = children.entriesGroupedByKey().flatMap(([_name, descs]) => descs.length === 1 ? descs : []).toArray().sort((a, b) => compareNatural(a.name, b.name));
94
- const uniqueDescendants = descendants.entriesGroupedByKey().flatMap(([_name, descs]) => descs.length === 1 && !children.has(_name) ? descs : []).toArray();
95
- return stream([
96
- ...uniqueChildren,
97
- ...sortNaturalByFqn(uniqueDescendants)
98
- ]);
89
+ return stream(
90
+ this.workspaceCache.get(`${this.cachePrefix}:uniqueDescedants:${parent}`, () => {
91
+ const { children, descendants } = this.documents().reduce((map, doc) => {
92
+ const docIndex = this.get(doc);
93
+ docIndex.children(parent).forEach((desc) => {
94
+ map.children.set(desc.name, desc);
95
+ });
96
+ docIndex.descendants(parent).forEach((desc) => {
97
+ map.descendants.set(desc.name, desc);
98
+ });
99
+ return map;
100
+ }, {
101
+ children: new MultiMap(),
102
+ descendants: new MultiMap()
103
+ });
104
+ const uniqueChildren = uniqueByName(children).sort((a, b) => compareNatural(a.name, b.name));
105
+ const uniqueDescendants = [...descendants.associations()].flatMap(([_name, descs]) => descs.length === 1 && !children.has(_name) ? descs : []);
106
+ return [
107
+ ...uniqueChildren,
108
+ ...sortNaturalByFqn(uniqueDescendants)
109
+ ];
110
+ })
111
+ );
99
112
  }
100
113
  createDocumentIndex(document) {
101
114
  const rootElements = document.parseResult.value.models.flatMap((m) => m.elements);
@@ -113,7 +126,7 @@ export class FqnIndex extends ADisposable {
113
126
  id: fqn
114
127
  };
115
128
  ElementOps.writeId(node, fqn);
116
- byfqn.add(fqn, desc);
129
+ byfqn.set(fqn, desc);
117
130
  return desc;
118
131
  };
119
132
  const traverseNode = (el, parentFqn) => {
@@ -124,7 +137,7 @@ export class FqnIndex extends ADisposable {
124
137
  if (!parentFqn) {
125
138
  root.push(desc);
126
139
  } else {
127
- children.add(parentFqn, desc);
140
+ children.set(parentFqn, desc);
128
141
  }
129
142
  } else {
130
143
  thisFqn = readStrictFqn(el.element);
@@ -141,18 +154,22 @@ export class FqnIndex extends ADisposable {
141
154
  }
142
155
  }
143
156
  }
144
- const directChildren = children.get(thisFqn);
157
+ const directChildren = children.get(thisFqn) ?? [];
145
158
  _nested = [
146
159
  ...directChildren,
147
160
  ..._nested
148
161
  ];
149
- descendants.addAll(thisFqn, _nested);
162
+ for (const child of _nested) {
163
+ descendants.set(thisFqn, child);
164
+ }
150
165
  if (ast.isExtendElement(el)) {
151
- ancestorsFqn(thisFqn).forEach((ancestor) => {
152
- descendants.addAll(ancestor, _nested);
153
- });
166
+ for (const ancestor of ancestorsFqn(thisFqn)) {
167
+ for (const child of _nested) {
168
+ descendants.set(ancestor, child);
169
+ }
170
+ }
154
171
  }
155
- return descendants.get(thisFqn);
172
+ return descendants.get(thisFqn) ?? [];
156
173
  };
157
174
  for (const node of rootElements) {
158
175
  try {
@@ -167,6 +184,9 @@ export class FqnIndex extends ADisposable {
167
184
  return new DocumentFqnIndex(root, children, descendants, byfqn);
168
185
  }
169
186
  }
187
+ function uniqueByName(multimap) {
188
+ return [...multimap.associations()].flatMap(([_name, descs]) => descs.length === 1 ? descs : []);
189
+ }
170
190
  export class DocumentFqnIndex {
171
191
  constructor(_rootElements, _children, _descendants, _byfqn) {
172
192
  this._rootElements = _rootElements;
@@ -179,12 +199,12 @@ export class DocumentFqnIndex {
179
199
  return this._rootElements;
180
200
  }
181
201
  byFqn(fqn) {
182
- return this._byfqn.get(fqn);
202
+ return this._byfqn.get(fqn) ?? [];
183
203
  }
184
204
  children(parent) {
185
- return this._children.get(parent);
205
+ return this._children.get(parent) ?? [];
186
206
  }
187
207
  descendants(nodeName) {
188
- return this._descendants.get(nodeName);
208
+ return this._descendants.get(nodeName) ?? [];
189
209
  }
190
210
  }
@@ -4,15 +4,12 @@ import { type Cancellation, type URI, Disposable } from 'langium';
4
4
  import type { LikeC4Services } from '../module';
5
5
  import { ADisposable } from '../utils';
6
6
  type ModelParsedListener = (docs: URI[]) => void;
7
- type ParseModelResult = {
8
- model: c4.ParsedLikeC4Model;
9
- computeView: (view: c4.LikeC4View) => c4.ComputeViewResult;
10
- };
11
7
  export declare class LikeC4ModelBuilder extends ADisposable {
12
8
  private parser;
13
9
  private listeners;
14
10
  private cache;
15
11
  private DocumentBuilder;
12
+ private LangiumDocuments;
16
13
  constructor(services: LikeC4Services);
17
14
  /**
18
15
  * WARNING:
@@ -20,7 +17,7 @@ export declare class LikeC4ModelBuilder extends ADisposable {
20
17
  * Otherwise, the model may be incomplete.
21
18
  */
22
19
  private unsafeSyncParseModel;
23
- parseModel(cancelToken?: Cancellation.CancellationToken): Promise<ParseModelResult | null>;
20
+ parseModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null>;
24
21
  private previousViews;
25
22
  /**
26
23
  * WARNING:
@@ -8,30 +8,36 @@ import {
8
8
  Disposable,
9
9
  DocumentState
10
10
  } from "langium";
11
+ import prettyMs from "pretty-ms";
11
12
  import {
12
13
  filter,
13
14
  groupBy,
15
+ isNot,
14
16
  mapToObj,
15
17
  pipe,
18
+ prop,
16
19
  values
17
20
  } from "remeda";
21
+ import { isLikeC4Builtin } from "../likec4lib.js";
18
22
  import { logger as mainLogger, logWarnError } from "../logger.js";
19
23
  import { ADisposable } from "../utils/index.js";
20
24
  import { assignNavigateTo } from "../view-utils/index.js";
21
25
  import { buildModel } from "./builder/buildModel.js";
22
26
  const CACHE_KEY_PARSED_MODEL = "ParsedLikeC4Model";
23
27
  const CACHE_KEY_COMPUTED_MODEL = "ComputedLikeC4Model";
24
- const logger = mainLogger.getChild("model-builder");
28
+ const logger = mainLogger.getChild("ModelBuilder");
25
29
  export class LikeC4ModelBuilder extends ADisposable {
26
30
  parser;
27
31
  listeners = [];
28
32
  cache;
29
33
  DocumentBuilder;
34
+ LangiumDocuments;
30
35
  constructor(services) {
31
36
  super();
32
37
  this.parser = services.likec4.ModelParser;
33
38
  this.cache = services.ValidatedWorkspaceCache;
34
39
  this.DocumentBuilder = services.shared.workspace.DocumentBuilder;
40
+ this.LangiumDocuments = services.shared.workspace.LangiumDocuments;
35
41
  this.onDispose(
36
42
  this.DocumentBuilder.onUpdate((_changed, deleted) => {
37
43
  if (deleted.length > 0) {
@@ -43,8 +49,10 @@ export class LikeC4ModelBuilder extends ADisposable {
43
49
  this.DocumentBuilder.onBuildPhase(
44
50
  DocumentState.Validated,
45
51
  (docs, _cancelToken) => {
46
- logger.debug("onValidated ({docslength} docs)", { docslength: docs.length });
47
- this.notifyListeners(docs.map((d) => d.uri));
52
+ const validated = docs.map(prop("uri")).filter(isNot(isLikeC4Builtin));
53
+ if (validated.length > 0) {
54
+ this.notifyListeners(validated);
55
+ }
48
56
  }
49
57
  )
50
58
  );
@@ -63,20 +71,25 @@ export class LikeC4ModelBuilder extends ADisposable {
63
71
  }
64
72
  const cache = this.cache;
65
73
  return cache.get(CACHE_KEY_PARSED_MODEL, () => {
66
- logger.debug("unsafeSyncParseModel ({docslength} docs)", { docslength: docs.length });
67
- const model = buildModel(docs);
68
- const computeView = LikeC4Model.makeCompute(model);
69
- return { model, computeView };
74
+ return buildModel(docs);
70
75
  });
71
76
  }
72
77
  async parseModel(cancelToken) {
73
78
  const cache = this.cache;
74
79
  const cached = cache.get(CACHE_KEY_PARSED_MODEL);
75
80
  if (cached) {
76
- return await Promise.resolve(cached);
81
+ logger.debug("parseModel from cache");
82
+ return cached;
83
+ }
84
+ if (this.LangiumDocuments.all.some((doc) => doc.state < DocumentState.Validated)) {
85
+ logger.debug("parseModel: waiting for documents to be validated");
86
+ await this.DocumentBuilder.waitUntil(DocumentState.Validated, cancelToken);
87
+ logger.debug("parseModel: documents are validated");
77
88
  }
78
- await this.DocumentBuilder.waitUntil(DocumentState.Validated, cancelToken);
79
- return this.unsafeSyncParseModel();
89
+ const t0 = performance.now();
90
+ const result = this.unsafeSyncParseModel();
91
+ logger.debug(`parseModel in ${prettyMs(performance.now() - t0)}`);
92
+ return result;
80
93
  }
81
94
  previousViews = {};
82
95
  /**
@@ -85,20 +98,18 @@ export class LikeC4ModelBuilder extends ADisposable {
85
98
  * Otherwise, the model may be incomplete.
86
99
  */
87
100
  unsafeSyncBuildModel() {
88
- const parsed = this.unsafeSyncParseModel();
89
- if (!parsed) {
90
- return LikeC4Model.EMPTY;
91
- }
92
101
  const cache = this.cache;
93
102
  const viewsCache = this.cache;
94
103
  return cache.get(CACHE_KEY_COMPUTED_MODEL, () => {
104
+ const parsed = this.unsafeSyncParseModel();
105
+ if (!parsed) {
106
+ return LikeC4Model.EMPTY;
107
+ }
95
108
  const {
96
- model: {
97
- views: parsedViews,
98
- ...model
99
- },
100
- computeView
109
+ views: parsedViews,
110
+ ...model
101
111
  } = parsed;
112
+ const computeView = LikeC4Model.makeCompute(parsed);
102
113
  const allViews = [];
103
114
  for (const view of values(parsedViews)) {
104
115
  const result = computeView(view);
@@ -126,13 +137,18 @@ export class LikeC4ModelBuilder extends ADisposable {
126
137
  const cache = this.cache;
127
138
  const cached = cache.get(CACHE_KEY_COMPUTED_MODEL);
128
139
  if (cached) {
129
- return await Promise.resolve(cached);
140
+ logger.debug("buildLikeC4Model from cache");
141
+ return cached;
130
142
  }
143
+ const t0 = performance.now();
131
144
  const model = await this.parseModel(cancelToken);
132
145
  if (!model) {
146
+ logger.debug("buildLikeC4Model: no model");
133
147
  return LikeC4Model.EMPTY;
134
148
  }
135
- return this.unsafeSyncBuildModel();
149
+ const result = this.unsafeSyncBuildModel();
150
+ logger.debug(`buildLikeC4Model in ${prettyMs(performance.now() - t0)}`);
151
+ return result;
136
152
  }
137
153
  async computeView(viewId, cancelToken) {
138
154
  const cache = this.cache;
@@ -145,19 +161,21 @@ export class LikeC4ModelBuilder extends ADisposable {
145
161
  return null;
146
162
  }
147
163
  return cache.get(cacheKey, () => {
148
- const view = parsed.model.views[viewId];
164
+ const view = parsed.views[viewId];
149
165
  if (!view) {
150
- logger.warn(`[ModelBuilder] Cannot find view ${viewId}`);
166
+ logger.warn`computeView: cant find view ${viewId}`;
151
167
  return null;
152
168
  }
153
- const result = parsed.computeView(view);
169
+ logger.debug`computeView: ${viewId}`;
170
+ const computeView = LikeC4Model.makeCompute(parsed);
171
+ const result = computeView(view);
154
172
  if (!result.isSuccess) {
155
173
  logWarnError(result.error);
156
174
  return null;
157
175
  }
158
176
  let computedView = result.view;
159
177
  const allElementViews = pipe(
160
- parsed.model.views,
178
+ parsed.views,
161
179
  values(),
162
180
  filter(isScopedElementView),
163
181
  filter((v) => v.id !== viewId),
@@ -16,7 +16,7 @@ export declare class LikeC4ModelLocator {
16
16
  locateDeploymentElement(fqn: c4.Fqn, _prop?: string): Location | null;
17
17
  locateRelation(relationId: c4.RelationId): Location | null;
18
18
  locateViewAst(viewId: c4.ViewId): {
19
- doc: any;
19
+ doc: Stream<import("../ast").ParsedLikeC4LangiumDocument>;
20
20
  view: any;
21
21
  viewAst: ast.LikeC4View;
22
22
  } | null;
@@ -16,7 +16,7 @@ export class LikeC4ModelLocator {
16
16
  langiumDocuments;
17
17
  parser;
18
18
  documents() {
19
- return this.parser.documents().toArray();
19
+ return this.parser.documents();
20
20
  }
21
21
  getParsedElement(astNodeOrFqn) {
22
22
  if (isString(astNodeOrFqn)) {