@likec4/language-server 1.46.2 → 1.46.3

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.
@@ -15,85 +15,86 @@ export class LikeC4ModelChanges {
15
15
  }
16
16
  async applyChange(changeView) {
17
17
  const lspConnection = this.services.shared.lsp.Connection;
18
- let result = null;
19
18
  try {
20
- await this.services.shared.workspace.WorkspaceLock.write(async () => {
21
- let { viewId, projectId: _projectId, change } = changeView;
22
- const project = this.services.shared.workspace.ProjectsManager.ensureProject(_projectId);
23
- logger.debug `Applying model change ${change.op} to view ${viewId} in project ${project.id}`;
24
- const lookup = this.locator.locateViewAst(viewId, project.id);
25
- if (!lookup) {
26
- throw new Error(`View ${viewId} not found in project ${project.id}`);
19
+ let { viewId, projectId: _projectId, change } = changeView;
20
+ const project = this.services.shared.workspace.ProjectsManager.ensureProject(_projectId);
21
+ logger.debug `Applying model change ${change.op} to view ${viewId} in project ${project.id}`;
22
+ const lookup = this.locator.locateViewAst(viewId, project.id);
23
+ if (!lookup) {
24
+ throw new Error(`View ${viewId} not found in project ${project.id}`);
25
+ }
26
+ const textDocument = {
27
+ uri: lookup.doc.textDocument.uri,
28
+ version: lookup.doc.textDocument.version,
29
+ };
30
+ // TODO refactor to use separate methods for save/reset operations
31
+ if (change.op === 'save-view-snapshot') {
32
+ invariant(viewId === change.layout.id, 'View ID does not match, expected ' + viewId + ', got ' + change.layout.id);
33
+ // If there is an existing manual layout v1
34
+ if (lookup.view.manualLayout) {
35
+ // We clean it up
36
+ await removeManualLayoutV1(this.services, { lookup }).catch(err => {
37
+ logger.warn(`Failed to remove manual layout v1 for view ${viewId} in project ${project.id}`, { err });
38
+ });
27
39
  }
28
- const textDocument = {
29
- uri: lookup.doc.textDocument.uri,
30
- version: lookup.doc.textDocument.version,
40
+ const location = await this.services.likec4.ManualLayouts.write(project, change.layout);
41
+ return {
42
+ success: true,
43
+ location,
31
44
  };
32
- // TODO refactor to use separate methods for save/reset operations
33
- if (change.op === 'save-view-snapshot') {
34
- invariant(viewId === change.layout.id, 'View ID does not match, expected ' + viewId + ', got ' + change.layout.id);
35
- // If there is an existing manual layout v1
36
- if (lookup.view.manualLayout) {
37
- // We clean it up
38
- await removeManualLayoutV1(this.services, { lookup }).catch(err => {
39
- logger.warn(`Failed to remove manual layout v1 for view ${viewId} in project ${project.id}`, { err });
40
- });
41
- }
42
- const location = await this.services.likec4.ManualLayouts.write(project, change.layout);
43
- result = {
44
- success: true,
45
- location,
46
- };
47
- return;
48
- }
49
- if (change.op === 'reset-manual-layout') {
50
- // If there is an existing manual layout v1
51
- if (lookup.view.manualLayout) {
52
- // We clean it up
53
- await removeManualLayoutV1(this.services, { lookup }).catch(err => {
54
- logger.warn(`Failed to remove manual layout v1 for view ${viewId} in project ${project.id}`, { err });
55
- });
56
- }
57
- const location = await this.services.likec4.ManualLayouts.remove(project, viewId);
58
- result = {
59
- success: true,
60
- location,
61
- };
62
- return;
63
- }
64
- invariant(lspConnection, 'This change only supported in IDE (running as Extension)');
65
- const { edits, modifiedRange } = this.convertToTextEdit({
66
- lookup,
67
- change,
68
- });
69
- if (!edits.length) {
70
- return;
71
- }
72
- const applyResult = await lspConnection.workspace.applyEdit({
73
- label: `LikeC4 - change view ${changeView.viewId}`,
74
- edit: {
75
- changes: {
76
- [textDocument.uri]: edits,
77
- },
78
- },
79
- });
80
- if (!applyResult.applied) {
81
- lspConnection.window.showErrorMessage(`Failed to apply changes ${applyResult.failureReason}`);
82
- return;
45
+ }
46
+ if (change.op === 'reset-manual-layout') {
47
+ // If there is an existing manual layout v1
48
+ if (lookup.view.manualLayout) {
49
+ // We clean it up
50
+ await removeManualLayoutV1(this.services, { lookup }).catch(err => {
51
+ logger.warn(`Failed to remove manual layout v1 for view ${viewId} in project ${project.id}`, { err });
52
+ });
83
53
  }
84
- result = {
54
+ const location = await this.services.likec4.ManualLayouts.remove(project, viewId);
55
+ return {
85
56
  success: true,
86
- location: {
87
- uri: textDocument.uri,
88
- range: modifiedRange,
89
- },
57
+ location,
90
58
  };
59
+ }
60
+ invariant(lspConnection, 'This change only supported in IDE (running as Extension)');
61
+ const { edits, modifiedRange } = this.convertToTextEdit({
62
+ lookup,
63
+ change,
91
64
  });
65
+ if (!edits.length) {
66
+ return {
67
+ success: false,
68
+ error: 'No changes to apply',
69
+ };
70
+ }
71
+ const applyResult = await lspConnection.workspace.applyEdit({
72
+ label: `LikeC4 - change view ${changeView.viewId}`,
73
+ edit: {
74
+ changes: {
75
+ [textDocument.uri]: edits,
76
+ },
77
+ },
78
+ });
79
+ if (!applyResult.applied) {
80
+ lspConnection.window.showErrorMessage(`Failed to apply changes ${applyResult.failureReason}`);
81
+ return {
82
+ success: false,
83
+ error: `Failed to apply changes ${applyResult.failureReason}`,
84
+ };
85
+ }
86
+ return {
87
+ success: true,
88
+ location: {
89
+ uri: textDocument.uri,
90
+ range: modifiedRange,
91
+ },
92
+ };
92
93
  }
93
94
  catch (err) {
94
95
  const error = loggable(wrapError(err, `Failed to apply change ${changeView.change.op} ${changeView.viewId}`));
95
96
  logger.error(error);
96
- result = {
97
+ return {
97
98
  success: false,
98
99
  error,
99
100
  };
@@ -101,10 +102,6 @@ export class LikeC4ModelChanges {
101
102
  finally {
102
103
  this.services.likec4.ModelBuilder.clearCache();
103
104
  }
104
- return result ?? {
105
- success: false,
106
- error: 'Unknown error applying model change',
107
- };
108
105
  }
109
106
  convertToTextEdit({ lookup, change }) {
110
107
  switch (change.op) {
@@ -13,17 +13,11 @@ export class IndexManager extends DefaultIndexManager {
13
13
  }
14
14
  projectElements(projectId, nodeType, uris) {
15
15
  const projects = this.services.workspace.ProjectsManager;
16
- const project = projects.getProject(projectId);
17
- const includePathStrings = project.includePaths?.map(uri => {
18
- const path = uri.toString();
19
- return path.endsWith('/') ? path : path + '/';
20
- }) ?? [];
21
16
  let documentUris = stream(this.symbolIndex.keys());
22
17
  return documentUris
23
18
  .filter(uri => {
24
- const belongsToProject = projects.belongsTo(uri) === projectId;
25
- const inIncludePath = includePathStrings.some(includePath => uri.startsWith(includePath));
26
- return (belongsToProject || inIncludePath) && (!uris || uris.has(uri));
19
+ return (!uris || uris.has(uri)) && (projects.belongsTo(uri) === projectId ||
20
+ projects.isIncluded(projectId, uri));
27
21
  })
28
22
  .flatMap(uri => this.getFileDescriptions(uri, nodeType));
29
23
  }
@@ -5,15 +5,21 @@ import { type LikeC4LangiumDocument } from '../ast';
5
5
  import type { LikeC4SharedServices } from '../module';
6
6
  export declare class LangiumDocuments extends DefaultLangiumDocuments {
7
7
  protected services: LikeC4SharedServices;
8
- protected compare: (a: string | undefined, b: string | undefined) => number;
9
8
  constructor(services: LikeC4SharedServices);
10
9
  addDocument(document: LangiumDocument): void;
11
10
  getDocument(uri: URI): LikeC4LangiumDocument | undefined;
12
11
  get all(): Stream<LikeC4LangiumDocument>;
13
12
  /**
14
- * Returns all user documents, excluding built-in documents.
13
+ * Returns all documents, excluding built-in documents and documents excluded by ProjectsManager.
15
14
  */
16
15
  get allExcludingBuiltin(): Stream<LikeC4LangiumDocument>;
16
+ /**
17
+ * Returns all documents for a project, including both project documents and documents included by the project.
18
+ */
17
19
  projectDocuments(projectId: ProjectId): Stream<LikeC4LangiumDocument>;
18
20
  groupedByProject(): Record<ProjectId, NonEmptyArray<LikeC4LangiumDocument>>;
21
+ /**
22
+ * Reset the project IDs of all documents.
23
+ */
24
+ resetProjectIds(): void;
19
25
  }
@@ -14,7 +14,6 @@ const exclude = (doc) => {
14
14
  };
15
15
  export class LangiumDocuments extends DefaultLangiumDocuments {
16
16
  services;
17
- compare = compareNaturalHierarchically('/', true);
18
17
  constructor(services) {
19
18
  super(services);
20
19
  this.services = services;
@@ -34,7 +33,7 @@ export class LangiumDocuments extends DefaultLangiumDocuments {
34
33
  getDocument(uri) {
35
34
  const doc = super.getDocument(uri);
36
35
  if (doc && !exclude(doc)) {
37
- doc.likec4ProjectId ??= this.services.workspace.ProjectsManager.belongsTo(doc);
36
+ doc.likec4ProjectId = this.services.workspace.ProjectsManager.belongsTo(doc);
38
37
  }
39
38
  if (doc && !isLikeC4LangiumDocument(doc)) {
40
39
  throw new Error(`Document ${doc.uri.path} is not a LikeC4 document`);
@@ -46,7 +45,7 @@ export class LangiumDocuments extends DefaultLangiumDocuments {
46
45
  .filter((doc) => {
47
46
  if (doc.textDocument.languageId === LikeC4LanguageMetaData.languageId) {
48
47
  if (!isLikeC4Builtin(doc.uri)) {
49
- doc.likec4ProjectId ??= this.services.workspace.ProjectsManager.belongsTo(doc);
48
+ doc.likec4ProjectId = this.services.workspace.ProjectsManager.belongsTo(doc);
50
49
  }
51
50
  return true;
52
51
  }
@@ -54,37 +53,35 @@ export class LangiumDocuments extends DefaultLangiumDocuments {
54
53
  });
55
54
  }
56
55
  /**
57
- * Returns all user documents, excluding built-in documents.
56
+ * Returns all documents, excluding built-in documents and documents excluded by ProjectsManager.
58
57
  */
59
58
  get allExcludingBuiltin() {
60
59
  const projects = this.services.workspace.ProjectsManager;
61
- return super.all.filter((doc) => {
62
- // Exclude built-in and non-LikeC4 documents, and also documents excluded by ProjectsManager
63
- return !exclude(doc) && !projects.isExcluded(doc);
60
+ return this.all.filter((doc) => {
61
+ return !(isLikeC4Builtin(doc.uri) || projects.isExcluded(doc));
64
62
  });
65
63
  }
64
+ /**
65
+ * Returns all documents for a project, including both project documents and documents included by the project.
66
+ */
66
67
  projectDocuments(projectId) {
67
68
  const projects = this.services.workspace.ProjectsManager;
68
- const project = projects.getProject(projectId);
69
- const projectFolder = project.folderUri.toString() + (project.folderUri.path.endsWith('/') ? '' : '/');
70
- const includePathStrings = project.includePaths?.map(uri => {
71
- const path = uri.toString();
72
- return path.endsWith('/') ? path : path + '/';
73
- }) ?? [];
74
69
  return this.allExcludingBuiltin.filter(doc => {
75
- const docUri = doc.uri.toString();
76
- // Always include documents from the project's own folder
77
- if (docUri.startsWith(projectFolder)) {
78
- return true;
79
- }
80
- // Check for addtional documents when the config has the `include:paths` property set.
81
- if (includePathStrings.length > 0) {
82
- return includePathStrings.some(includePath => docUri.startsWith(includePath));
83
- }
84
- return false;
70
+ return doc.likec4ProjectId === projectId || projects.isIncluded(projectId, doc.uri);
85
71
  });
86
72
  }
87
73
  groupedByProject() {
88
74
  return groupBy(this.allExcludingBuiltin.toArray(), prop('likec4ProjectId'));
89
75
  }
76
+ /**
77
+ * Reset the project IDs of all documents.
78
+ */
79
+ resetProjectIds() {
80
+ super.all.forEach(doc => {
81
+ if (exclude(doc)) {
82
+ return;
83
+ }
84
+ delete doc.likec4ProjectId;
85
+ });
86
+ }
90
87
  }
@@ -1,5 +1,5 @@
1
1
  import { type IncludeConfig, type LikeC4ProjectConfig, type LikeC4ProjectConfigInput } from '@likec4/config';
2
- import type { NonEmptyReadonlyArray } from '@likec4/core';
2
+ import type { NonEmptyArray, NonEmptyReadonlyArray } from '@likec4/core';
3
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';
@@ -20,7 +20,7 @@ interface ProjectData {
20
20
  * Resolved include paths with both URI and folder string representations.
21
21
  * These are additional directories that are part of this project.
22
22
  */
23
- includePaths?: Array<{
23
+ includePaths?: NonEmptyArray<{
24
24
  uri: URI;
25
25
  folder: ProjectFolder;
26
26
  }>;
@@ -36,7 +36,7 @@ export interface Project {
36
36
  /**
37
37
  * Resolved include paths as URIs (if configured).
38
38
  */
39
- includePaths?: URI[];
39
+ includePaths?: NonEmptyReadonlyArray<URI>;
40
40
  }
41
41
  export declare class ProjectsManager {
42
42
  #private;
@@ -74,6 +74,11 @@ export declare class ProjectsManager {
74
74
  * Checks if the specified document should be excluded from processing.
75
75
  */
76
76
  isExcluded(document: LangiumDocument | URI | string): boolean;
77
+ /**
78
+ * Checks if the specified document is included by the project.
79
+ */
80
+ isIncluded(projectId: ProjectId, document: LangiumDocument | URI | string): boolean;
81
+ includedInProjects(document: LangiumDocument | URI | string): ProjectId[];
77
82
  /**
78
83
  * Checks if it is a config file and it is not excluded by default exclude pattern
79
84
  *
@@ -83,7 +88,7 @@ export declare class ProjectsManager {
83
88
  /**
84
89
  * Registers likec4 project by config file.
85
90
  */
86
- registerConfigFile(configFile: URI): Promise<ProjectData>;
91
+ registerConfigFile(configFile: URI, cancelToken?: Cancellation.CancellationToken): Promise<ProjectData>;
87
92
  /**
88
93
  * Registers (or reloads) likec4 project by config file or config object.
89
94
  * If there is some project registered at same folder, it will be reloaded.
@@ -91,14 +96,14 @@ export declare class ProjectsManager {
91
96
  registerProject(opts: {
92
97
  config: LikeC4ProjectConfig | LikeC4ProjectConfigInput;
93
98
  folderUri: URI | string;
94
- }): Promise<ProjectData>;
99
+ }, cancelToken?: Cancellation.CancellationToken): Promise<ProjectData>;
95
100
  /**
96
101
  * Determines which project the given document belongs to.
97
102
  * If the document does not belong to any project, returns the default project ID.
98
103
  */
99
104
  belongsTo(document: LangiumDocument | URI | string): scalar.ProjectId;
100
- reloadProjects(): Promise<void>;
101
- protected _reloadProjects(): Promise<void>;
105
+ reloadProjects(cancelToken?: Cancellation.CancellationToken): Promise<void>;
106
+ protected _reloadProjects(cancelToken?: Cancellation.CancellationToken): Promise<void>;
102
107
  protected uniqueProjectId(name: string): scalar.ProjectId;
103
108
  protected reset(): void;
104
109
  rebuidProject(projectId: ProjectId, cancelToken?: Cancellation.CancellationToken): Promise<void>;