@likec4/language-server 1.44.0 → 1.45.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.
Files changed (41) hide show
  1. package/dist/LikeC4LanguageServices.d.ts +1 -15
  2. package/dist/LikeC4LanguageServices.js +2 -32
  3. package/dist/Rpc.js +32 -20
  4. package/dist/ast.js +6 -2
  5. package/dist/browser.js +2 -2
  6. package/dist/bundled.js +2 -0
  7. package/dist/bundled.mjs +3184 -3162
  8. package/dist/filesystem/ChokidarWatcher.d.ts +2 -0
  9. package/dist/filesystem/ChokidarWatcher.js +27 -16
  10. package/dist/filesystem/LikeC4FileSystem.js +1 -1
  11. package/dist/index.d.ts +3 -1
  12. package/dist/index.js +5 -3
  13. package/dist/mcp/server/StdioLikeC4MCPServer.js +10 -6
  14. package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -97
  15. package/dist/mcp/server/WithMCPServer.js +5 -5
  16. package/dist/mcp/tools/search-element.js +5 -5
  17. package/dist/mcp/utils.js +1 -1
  18. package/dist/model/deployments-index.js +2 -2
  19. package/dist/model/fqn-index.d.ts +1 -2
  20. package/dist/model/fqn-index.js +13 -16
  21. package/dist/model/model-builder.js +0 -2
  22. package/dist/model/model-parser.js +34 -27
  23. package/dist/model/parser/SpecificationParser.js +4 -0
  24. package/dist/model/parser/ViewsParser.js +3 -1
  25. package/dist/model-change/ModelChanges.d.ts +2 -2
  26. package/dist/model-change/ModelChanges.js +36 -9
  27. package/dist/protocol.d.ts +33 -10
  28. package/dist/protocol.js +13 -4
  29. package/dist/view-utils/manual-layout.js +2 -4
  30. package/dist/views/LikeC4ManualLayouts.d.ts +16 -2
  31. package/dist/views/LikeC4ManualLayouts.js +99 -22
  32. package/dist/views/LikeC4Views.d.ts +26 -5
  33. package/dist/views/LikeC4Views.js +49 -33
  34. package/dist/workspace/AstNodeDescriptionProvider.js +6 -3
  35. package/dist/workspace/IndexManager.js +1 -1
  36. package/dist/workspace/LangiumDocuments.d.ts +3 -2
  37. package/dist/workspace/LangiumDocuments.js +29 -15
  38. package/dist/workspace/ProjectsManager.d.ts +19 -15
  39. package/dist/workspace/ProjectsManager.js +137 -41
  40. package/dist/workspace/WorkspaceManager.js +5 -0
  41. package/package.json +16 -15
@@ -1,4 +1,4 @@
1
- import type { ComputedView, DiagramView, LayoutedView, ProjectId, ViewId } from '@likec4/core';
1
+ import type { ComputedView, DiagramView, LayoutedView, LayoutType, ProjectId, ViewId } from '@likec4/core';
2
2
  import { type QueueGraphvizLayoter, GraphvizLayouter } from '@likec4/layouts';
3
3
  import type { CancellationToken } from 'vscode-languageserver';
4
4
  import type { LikeC4Services } from '../module';
@@ -11,6 +11,18 @@ type GraphvizSvgOut = {
11
11
  readonly dot: string;
12
12
  readonly svg: string;
13
13
  };
14
+ type LayoutViewParams = {
15
+ viewId: ViewId;
16
+ /**
17
+ * Type of layout to apply
18
+ * - 'manual' - applies manual layout if any
19
+ * - 'auto' - returns latest version with drifts from manual layout if any
20
+ * - undefined - returns latest layout as is
21
+ */
22
+ layoutType?: LayoutType | undefined;
23
+ projectId?: ProjectId | undefined;
24
+ cancelToken?: CancellationToken | undefined;
25
+ };
14
26
  export interface LikeC4Views {
15
27
  readonly layouter: GraphvizLayouter;
16
28
  /**
@@ -18,14 +30,18 @@ export interface LikeC4Views {
18
30
  */
19
31
  computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
20
32
  /**
21
- * Returns all layouted views (without manual layout)
33
+ * Layouts all views (ignoring any manual snapshots)
22
34
  */
23
35
  layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut[]>;
24
36
  /**
25
- * Layouts a view (from sources, i.e. without manual layout)
37
+ * Layouts a view.
38
+ * If layoutType is 'manual' - applies manual layout if any.
39
+ * If layoutType is 'auto' - returns latest version with drifts from manual layout if any
40
+ * If not specified - returns latest layout as is
41
+ *
26
42
  * If view not found in model, but there is a snapshot - it will be returned (with empty DOT)
27
43
  */
28
- layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
44
+ layoutView(params: LayoutViewParams): Promise<GraphvizOut | null>;
29
45
  /**
30
46
  * Returns diagrams.
31
47
  * If diagram has manual layout, it will be used.
@@ -55,7 +71,7 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
55
71
  computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
56
72
  private _layoutAllViews;
57
73
  layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut[]>;
58
- layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
74
+ layoutView({ viewId, layoutType, projectId, cancelToken, }: LayoutViewParams): Promise<GraphvizOut | null>;
59
75
  diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<LayoutedView>>;
60
76
  viewsAsGraphvizOut(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<GraphvizSvgOut>>;
61
77
  /**
@@ -63,6 +79,11 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
63
79
  */
64
80
  openView(viewId: ViewId, projectId: ProjectId): Promise<void>;
65
81
  private reportViewError;
82
+ /**
83
+ * Applies manual layout or calculates drifts from snapshot
84
+ * if layoutType is specified
85
+ */
86
+ private withLayoutType;
66
87
  private viewSucceed;
67
88
  }
68
89
  export {};
@@ -1,8 +1,8 @@
1
- import { applyLayoutDriftReasons, applyManualLayout } from '@likec4/core/model';
1
+ import { _layout, applyManualLayout, calcDriftsFromSnapshot, invariant } from '@likec4/core';
2
2
  import { GraphvizLayouter } from '@likec4/layouts';
3
3
  import { loggable } from '@likec4/log';
4
4
  import { interruptAndCheck } from 'langium';
5
- import { unique, values } from 'remeda';
5
+ import { isTruthy, unique, values } from 'remeda';
6
6
  import { logger as rootLogger, logWarnError } from '../logger';
7
7
  import { performanceMark } from '../utils';
8
8
  const viewsLogger = rootLogger.getChild('views');
@@ -78,47 +78,48 @@ export class DefaultLikeC4Views {
78
78
  const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
79
79
  return await this._layoutAllViews(likeC4Model, cancelToken);
80
80
  }
81
- async layoutView(viewId, projectId, cancelToken) {
81
+ async layoutView({ viewId, layoutType, projectId, cancelToken, }) {
82
82
  const model = await this.ModelBuilder.computeModel(projectId, cancelToken);
83
83
  const view = model.findView(viewId)?.$view;
84
84
  projectId = model.project.id;
85
85
  const logger = viewsLogger.getChild(projectId);
86
86
  if (!view) {
87
87
  logger.warn `layoutView ${viewId} not found`;
88
- const project = this.services.shared.workspace.ProjectsManager.getProject(projectId);
89
- const manualLayouts = await this.services.likec4.ManualLayouts.read(project);
90
- const snapshot = manualLayouts?.[viewId];
88
+ const snapshot = model.findManualLayout(viewId);
91
89
  if (snapshot) {
92
90
  logger.debug `found manual layout for ${viewId}`;
91
+ let diagram = { ...snapshot };
92
+ diagram.drifts = [
93
+ 'not-exists',
94
+ ];
95
+ diagram._layout = 'manual';
93
96
  return {
94
- diagram: {
95
- ...snapshot,
96
- _layout: 'manual',
97
- drifts: snapshot.drifts
98
- ? unique([
99
- ...snapshot.drifts,
100
- 'not-exists',
101
- ])
102
- : ['not-exists'],
103
- },
97
+ diagram: diagram,
104
98
  dot: '# manual layout',
105
99
  };
106
100
  }
107
101
  return null;
108
102
  }
109
- let cached = this.cache.get(view);
110
- if (cached) {
111
- logger.debug `layout ${viewId} from cache`;
112
- return await Promise.resolve().then(() => cached);
113
- }
114
103
  try {
115
104
  const m0 = performanceMark();
116
- const result = await this.layouter.layout({
105
+ const out = this.cache.get(view) ?? await this.layouter.layout({
117
106
  view,
118
107
  styles: model.$styles,
119
108
  });
120
- logger.debug(`layout {viewId} ready in ${m0.pretty}`, { viewId });
121
- return this.viewSucceed(view, model, result);
109
+ if (this.cache.has(view)) {
110
+ logger.debug `layout ${viewId} from cache`;
111
+ }
112
+ else {
113
+ this.viewSucceed(view, model, out);
114
+ logger.debug(`layout {viewId} in ${m0.pretty}`, { viewId });
115
+ }
116
+ if (isTruthy(layoutType)) {
117
+ return {
118
+ dot: out.dot,
119
+ diagram: this.withLayoutType(out.diagram, model, layoutType),
120
+ };
121
+ }
122
+ return out;
122
123
  }
123
124
  catch (e) {
124
125
  const errMessage = loggable(e);
@@ -131,11 +132,8 @@ export class DefaultLikeC4Views {
131
132
  const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
132
133
  const layouted = await this._layoutAllViews(likeC4Model, cancelToken);
133
134
  return layouted.map(({ diagram }) => {
134
- const manualLayout = likeC4Model.$data.manualLayouts?.[diagram.id];
135
- if (manualLayout) {
136
- return applyManualLayout(diagram, manualLayout);
137
- }
138
- return diagram;
135
+ // Apply manual layout if any
136
+ return this.withLayoutType(diagram, likeC4Model, 'manual');
139
137
  });
140
138
  }
141
139
  async viewsAsGraphvizOut(projectId, cancelToken) {
@@ -181,18 +179,36 @@ export class DefaultLikeC4Views {
181
179
  }
182
180
  reportViewError(view, projectId, error) {
183
181
  const key = `${projectId}-${view.id}`;
182
+ this.cache.delete(view);
184
183
  if (!this.viewsWithReportedErrors.has(key)) {
185
184
  this.services.shared.lsp.Connection?.window.showErrorMessage(`LikeC4: ${error}`);
186
185
  this.viewsWithReportedErrors.add(key);
187
186
  }
188
187
  }
188
+ /**
189
+ * Applies manual layout or calculates drifts from snapshot
190
+ * if layoutType is specified
191
+ */
192
+ withLayoutType(layouted, likec4model, layoutType) {
193
+ if (!layoutType) {
194
+ return layouted;
195
+ }
196
+ const snapshot = likec4model.findManualLayout(layouted.id);
197
+ if (!snapshot) {
198
+ return layouted;
199
+ }
200
+ if (layoutType === 'manual') {
201
+ if (layouted[_layout] === 'manual') {
202
+ viewsLogger.error(`View ${layouted.id} already has manual layout, this should not happen`);
203
+ return layouted;
204
+ }
205
+ return applyManualLayout(layouted, snapshot);
206
+ }
207
+ return calcDriftsFromSnapshot(layouted, snapshot);
208
+ }
189
209
  viewSucceed(view, likec4model, result) {
190
210
  const projectId = likec4model.project.id;
191
211
  const key = `${projectId}-${view.id}`;
192
- const snapshot = likec4model.$data.manualLayouts?.[view.id];
193
- if (snapshot) {
194
- result.diagram = applyLayoutDriftReasons(result.diagram, snapshot);
195
- }
196
212
  this.viewsWithReportedErrors.delete(key);
197
213
  this.cache.set(view, result);
198
214
  return result;
@@ -1,4 +1,5 @@
1
1
  import { AstUtils, DefaultAstNodeDescriptionProvider, } from 'langium';
2
+ import { isLikeC4Builtin } from '../likec4lib';
2
3
  export class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
3
4
  services;
4
5
  constructor(services) {
@@ -6,10 +7,12 @@ export class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvide
6
7
  this.services = services;
7
8
  }
8
9
  createDescription(node, name, document) {
9
- const doc = document ?? AstUtils.getDocument(node);
10
+ document ??= AstUtils.getDocument(node);
10
11
  const description = super.createDescription(node, name, document);
11
- doc.likec4ProjectId ??= this.services.shared.workspace.ProjectsManager.belongsTo(doc.uri);
12
- description.likec4ProjectId = doc.likec4ProjectId;
12
+ if (!isLikeC4Builtin(document.uri)) {
13
+ document.likec4ProjectId ??= this.services.shared.workspace.ProjectsManager.belongsTo(document);
14
+ description.likec4ProjectId = document.likec4ProjectId;
15
+ }
13
16
  return description;
14
17
  }
15
18
  }
@@ -8,7 +8,7 @@ export class IndexManager extends DefaultIndexManager {
8
8
  async updateContent(document, cancelToken) {
9
9
  const projects = this.services.workspace.ProjectsManager;
10
10
  // Ensure the document is assigned to a project
11
- document.likec4ProjectId = projects.belongsTo(document.uri);
11
+ document.likec4ProjectId = projects.belongsTo(document);
12
12
  await super.updateContent(document, cancelToken);
13
13
  }
14
14
  projectElements(projectId, nodeType, uris) {
@@ -1,5 +1,5 @@
1
1
  import type { NonEmptyArray, ProjectId } from '@likec4/core';
2
- import type { LangiumDocument, Stream } from 'langium';
2
+ import type { LangiumDocument, Stream, URI } from 'langium';
3
3
  import { DefaultLangiumDocuments } from 'langium';
4
4
  import { type LikeC4LangiumDocument } from '../ast';
5
5
  import type { LikeC4SharedServices } from '../module';
@@ -8,11 +8,12 @@ export declare class LangiumDocuments extends DefaultLangiumDocuments {
8
8
  protected compare: (a: string | undefined, b: string | undefined) => number;
9
9
  constructor(services: LikeC4SharedServices);
10
10
  addDocument(document: LangiumDocument): void;
11
+ getDocument(uri: URI): LikeC4LangiumDocument | undefined;
12
+ get all(): Stream<LikeC4LangiumDocument>;
11
13
  /**
12
14
  * Returns all user documents, excluding built-in documents.
13
15
  */
14
16
  get allExcludingBuiltin(): Stream<LikeC4LangiumDocument>;
15
17
  projectDocuments(projectId: ProjectId): Stream<LikeC4LangiumDocument>;
16
18
  groupedByProject(): Record<ProjectId, NonEmptyArray<LikeC4LangiumDocument>>;
17
- resetProjectIds(): void;
18
19
  }
@@ -1,13 +1,17 @@
1
1
  import { compareNaturalHierarchically } from '@likec4/core/utils';
2
- import { DefaultLangiumDocuments } from 'langium';
2
+ import { DefaultLangiumDocuments, stream } from 'langium';
3
3
  import { groupBy, prop } from 'remeda';
4
4
  import { isLikeC4LangiumDocument } from '../ast';
5
+ import { LikeC4LanguageMetaData } from '../generated/module';
5
6
  import { isLikeC4Builtin } from '../likec4lib';
6
7
  /**
7
8
  * Compare function for document paths to ensure consistent order
8
9
  */
9
10
  const compare = compareNaturalHierarchically('/', true);
10
11
  const ensureOrder = (a, b) => compare(a.uri.path, b.uri.path);
12
+ const exclude = (doc) => {
13
+ return doc.textDocument.languageId !== LikeC4LanguageMetaData.languageId || isLikeC4Builtin(doc.uri);
14
+ };
11
15
  export class LangiumDocuments extends DefaultLangiumDocuments {
12
16
  services;
13
17
  compare = compareNaturalHierarchically('/', true);
@@ -27,17 +31,36 @@ export class LangiumDocuments extends DefaultLangiumDocuments {
27
31
  this.documentMap.set(doc.uri.toString(), doc);
28
32
  }
29
33
  }
34
+ getDocument(uri) {
35
+ const doc = super.getDocument(uri);
36
+ if (doc && !exclude(doc)) {
37
+ doc.likec4ProjectId ??= this.services.workspace.ProjectsManager.belongsTo(doc);
38
+ }
39
+ if (doc && !isLikeC4LangiumDocument(doc)) {
40
+ throw new Error(`Document ${doc.uri.path} is not a LikeC4 document`);
41
+ }
42
+ return doc;
43
+ }
44
+ get all() {
45
+ return stream(this.documentMap.values())
46
+ .filter((doc) => {
47
+ if (doc.textDocument.languageId === LikeC4LanguageMetaData.languageId) {
48
+ if (!isLikeC4Builtin(doc.uri)) {
49
+ doc.likec4ProjectId ??= this.services.workspace.ProjectsManager.belongsTo(doc);
50
+ }
51
+ return true;
52
+ }
53
+ return false;
54
+ });
55
+ }
30
56
  /**
31
57
  * Returns all user documents, excluding built-in documents.
32
58
  */
33
59
  get allExcludingBuiltin() {
34
60
  const projects = this.services.workspace.ProjectsManager;
35
61
  return super.all.filter((doc) => {
36
- if (!isLikeC4LangiumDocument(doc) || isLikeC4Builtin(doc.uri)) {
37
- return false;
38
- }
39
- doc.likec4ProjectId = projects.belongsTo(doc.uri);
40
- return !projects.isExcluded(doc);
62
+ // Exclude built-in and non-LikeC4 documents, and also documents excluded by ProjectsManager
63
+ return !exclude(doc) && !projects.isExcluded(doc);
41
64
  });
42
65
  }
43
66
  projectDocuments(projectId) {
@@ -46,13 +69,4 @@ export class LangiumDocuments extends DefaultLangiumDocuments {
46
69
  groupedByProject() {
47
70
  return groupBy(this.allExcludingBuiltin.toArray(), prop('likec4ProjectId'));
48
71
  }
49
- resetProjectIds() {
50
- const projects = this.services.workspace.ProjectsManager;
51
- this.all.forEach(doc => {
52
- if (!isLikeC4LangiumDocument(doc) || isLikeC4Builtin(doc.uri)) {
53
- return;
54
- }
55
- doc.likec4ProjectId = projects.belongsTo(doc);
56
- });
57
- }
58
72
  }
@@ -1,6 +1,6 @@
1
1
  import { type LikeC4ProjectConfig, type LikeC4ProjectConfigInput } from '@likec4/config';
2
2
  import type { NonEmptyReadonlyArray } from '@likec4/core';
3
- import type { scalar } from '@likec4/core/types';
3
+ import type { ProjectId, scalar } from '@likec4/core/types';
4
4
  import { type Cancellation, type LangiumDocument, URI, WorkspaceCache } from 'langium';
5
5
  import type { Tagged } from 'type-fest';
6
6
  import type { LikeC4SharedServices } from '../module';
@@ -29,7 +29,7 @@ export declare class ProjectsManager {
29
29
  * The global project ID used for all documents
30
30
  * that are not part of a specific project.
31
31
  */
32
- static readonly DefaultProjectId: scalar.ProjectId<string>;
32
+ static readonly DefaultProjectId: ProjectId<string>;
33
33
  constructor(services: LikeC4SharedServices);
34
34
  /**
35
35
  * Returns:
@@ -40,6 +40,7 @@ export declare class ProjectsManager {
40
40
  */
41
41
  get defaultProjectId(): scalar.ProjectId | undefined;
42
42
  set defaultProjectId(id: string | scalar.ProjectId | undefined);
43
+ get default(): ProjectData;
43
44
  get all(): NonEmptyReadonlyArray<scalar.ProjectId>;
44
45
  getProject(arg: scalar.ProjectId | LangiumDocument): Project;
45
46
  /**
@@ -64,31 +65,34 @@ export declare class ProjectsManager {
64
65
  */
65
66
  isConfigFile(entry: URI): boolean;
66
67
  /**
67
- * Checks if the provided file system entry is a valid project config file.
68
- *
69
- * @param entry The file system entry to check
68
+ * Registers likec4 project by config file.
70
69
  */
71
- registerConfigFile(configFile: URI): Promise<ProjectData | undefined>;
70
+ registerConfigFile(configFile: URI): Promise<ProjectData>;
72
71
  /**
73
72
  * Registers (or reloads) likec4 project by config file or config object.
74
73
  * If there is some project registered at same folder, it will be reloaded.
75
74
  */
76
- registerProject(opts: URI | {
77
- config: LikeC4ProjectConfigInput;
75
+ registerProject(opts: {
76
+ config: LikeC4ProjectConfig | LikeC4ProjectConfigInput;
78
77
  folderUri: URI | string;
79
78
  }): Promise<ProjectData>;
80
79
  /**
81
- * Registers (or reloads) likec4 project by config file or config object.
82
- * If there is some project registered at same folder, it will be reloaded.
80
+ * Determines which project the given document belongs to.
81
+ * If the document does not belong to any project, returns the default project ID.
83
82
  */
84
- private _registerProject;
85
83
  belongsTo(document: LangiumDocument | URI | string): scalar.ProjectId;
86
84
  reloadProjects(): Promise<void>;
87
85
  protected _reloadProjects(): Promise<void>;
88
86
  protected uniqueProjectId(name: string): scalar.ProjectId;
89
- protected resetProjectIds(): void;
90
- protected rebuidDocuments(cancelToken?: Cancellation.CancellationToken): Promise<void>;
91
- protected findProjectForDocument(documentUri: string): Pick<ProjectData, "id" | "exclude" | "config">;
92
- protected get mappingsToProject(): WorkspaceCache<string, Pick<ProjectData, 'id' | 'config' | 'exclude'>>;
87
+ protected reset(): void;
88
+ rebuidProject(projectId: ProjectId, cancelToken?: Cancellation.CancellationToken): Promise<void>;
89
+ protected findProjectForDocument(documentUri: string): ProjectData;
90
+ protected get mappingsToProject(): WorkspaceCache<string, ProjectData>;
91
+ /**
92
+ * The mapping between documents and projects they belong to.
93
+ * Lazy-created due to initialization order of the LanguageServer
94
+ */
95
+ protected get documentBelongsTo(): WorkspaceCache<LangiumDocument, ProjectData>;
96
+ private getWorkspaceFolder;
93
97
  }
94
98
  export {};