@likec4/language-server 1.20.1 → 1.20.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.
- package/README.md +19 -0
- package/bin/likec4-language-server.mjs +5 -0
- package/dist/LikeC4FileSystem.js +9 -9
- package/dist/Rpc.d.ts +2 -4
- package/dist/Rpc.js +27 -36
- package/dist/ast.d.ts +1 -0
- package/dist/ast.js +5 -1
- package/dist/bundled.mjs +6041 -0
- package/dist/formatting/LikeC4Formatter.d.ts +9 -0
- package/dist/formatting/LikeC4Formatter.js +131 -14
- package/dist/generated/ast.d.ts +13 -2
- package/dist/generated/ast.js +18 -1
- package/dist/generated/grammar.js +1 -1
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +0 -4
- package/dist/lsp/CompletionProvider.js +11 -3
- package/dist/model/deployments-index.d.ts +2 -1
- package/dist/model/deployments-index.js +3 -10
- package/dist/model/fqn-index.d.ts +2 -1
- package/dist/model/fqn-index.js +24 -17
- package/dist/model/model-builder.d.ts +2 -1
- package/dist/model/model-builder.js +32 -30
- package/dist/model/model-parser.d.ts +1 -1
- package/dist/model/model-parser.js +9 -6
- package/dist/model/parser/PredicatesParser.js +7 -1
- package/dist/utils/disposable.d.ts +8 -0
- package/dist/utils/disposable.js +25 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/validation/_shared.js +4 -1
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +4 -1
- package/dist/validation/specification.d.ts +1 -0
- package/dist/validation/specification.js +30 -0
- package/package.json +33 -27
- package/src/LikeC4FileSystem.ts +14 -13
- package/src/Rpc.ts +28 -38
- package/src/ast.ts +6 -1
- package/src/formatting/LikeC4Formatter.ts +198 -17
- package/src/generated/ast.ts +35 -2
- package/src/generated/grammar.ts +1 -1
- package/src/like-c4.langium +14 -3
- package/src/logger.ts +3 -4
- package/src/lsp/CompletionProvider.ts +27 -18
- package/src/model/deployments-index.ts +4 -17
- package/src/model/fqn-index.ts +26 -19
- package/src/model/model-builder.ts +32 -31
- package/src/model/model-parser.ts +14 -11
- package/src/model/parser/PredicatesParser.ts +30 -24
- package/src/utils/disposable.ts +30 -0
- package/src/utils/index.ts +1 -0
- package/src/validation/_shared.ts +5 -2
- package/src/validation/index.ts +6 -2
- package/src/validation/specification.ts +34 -0
- package/contrib/likec4.tmLanguage.json +0 -73
- package/dist/like-c4.langium +0 -852
package/src/like-c4.langium
CHANGED
|
@@ -115,7 +115,11 @@ ElementBody: '{'
|
|
|
115
115
|
;
|
|
116
116
|
|
|
117
117
|
ElementProperty:
|
|
118
|
-
ElementStringProperty |
|
|
118
|
+
ElementStringProperty |
|
|
119
|
+
ElementStyleProperty |
|
|
120
|
+
LinkProperty |
|
|
121
|
+
IconProperty |
|
|
122
|
+
MetadataProperty;
|
|
119
123
|
|
|
120
124
|
ElementStringProperty:
|
|
121
125
|
key=('title' | 'technology' | 'description') ':'? value=String ';'?;
|
|
@@ -711,6 +715,9 @@ ColorProperty:
|
|
|
711
715
|
OpacityProperty:
|
|
712
716
|
key='opacity' ':'? value=Percent ';'?;
|
|
713
717
|
|
|
718
|
+
MultipleProperty:
|
|
719
|
+
key='multiple' ':'? value=Boolean ';'?;
|
|
720
|
+
|
|
714
721
|
// Element properties -------------------------------------
|
|
715
722
|
IconProperty:
|
|
716
723
|
key='icon' ':'? (libicon=[LibIcon:IconId] | value=('none'|Uri)) ';'?;
|
|
@@ -718,7 +725,6 @@ IconProperty:
|
|
|
718
725
|
ShapeProperty:
|
|
719
726
|
key='shape' ':'? value=ElementShape ';'?;
|
|
720
727
|
|
|
721
|
-
|
|
722
728
|
BorderStyleValue returns string:
|
|
723
729
|
LineOptions | 'none';
|
|
724
730
|
|
|
@@ -730,7 +736,8 @@ StyleProperty:
|
|
|
730
736
|
ShapeProperty |
|
|
731
737
|
BorderProperty |
|
|
732
738
|
OpacityProperty |
|
|
733
|
-
IconProperty
|
|
739
|
+
IconProperty |
|
|
740
|
+
MultipleProperty;
|
|
734
741
|
|
|
735
742
|
ElementStyleProperty:
|
|
736
743
|
key='style' '{'
|
|
@@ -748,6 +755,8 @@ ArrowProperty:
|
|
|
748
755
|
RelationshipStyleProperty:
|
|
749
756
|
ColorProperty | LineProperty | ArrowProperty;
|
|
750
757
|
|
|
758
|
+
Boolean returns boolean: 'true' | 'false';
|
|
759
|
+
|
|
751
760
|
LineOptions returns string:
|
|
752
761
|
'solid' |
|
|
753
762
|
'dashed' |
|
|
@@ -825,6 +834,8 @@ hidden terminal NL: /[\r\n]+/;
|
|
|
825
834
|
// -----------------------------------
|
|
826
835
|
// Terminals
|
|
827
836
|
//terminal LineStartWithDash: /(?<=([\r?\n][^\S\r\n]*))-/;
|
|
837
|
+
// terminal Boolean returns boolean: 'true' | 'false';
|
|
838
|
+
|
|
828
839
|
|
|
829
840
|
// LibIcons
|
|
830
841
|
terminal LIB_ICON: /(aws|azure|gcp|tech):[-\w]*/;
|
package/src/logger.ts
CHANGED
|
@@ -10,11 +10,10 @@ export function logError(err: unknown): void {
|
|
|
10
10
|
logger.error(err)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Logs an error as warning (not critical)
|
|
15
|
+
*/
|
|
13
16
|
export function logWarnError(err: unknown): void {
|
|
14
|
-
if (err instanceof Error) {
|
|
15
|
-
logger.warn(err.stack ?? err.message)
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
17
|
logger.warn(err)
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type GrammarAST, type MaybePromise, AstUtils } from 'langium'
|
|
2
2
|
import {
|
|
3
3
|
type CompletionAcceptor,
|
|
4
4
|
type CompletionContext,
|
|
5
5
|
type CompletionProviderOptions,
|
|
6
|
-
DefaultCompletionProvider
|
|
6
|
+
DefaultCompletionProvider,
|
|
7
7
|
} from 'langium/lsp'
|
|
8
8
|
import { anyPass } from 'remeda'
|
|
9
9
|
import { CompletionItemKind, InsertTextFormat } from 'vscode-languageserver-types'
|
|
10
10
|
import { ast } from '../ast'
|
|
11
11
|
|
|
12
|
+
const STYLE_FIELDS = [
|
|
13
|
+
'color',
|
|
14
|
+
'shape',
|
|
15
|
+
'icon',
|
|
16
|
+
'border',
|
|
17
|
+
'opacity',
|
|
18
|
+
'multiple',
|
|
19
|
+
].join(',')
|
|
20
|
+
|
|
12
21
|
export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
13
22
|
override readonly completionOptions = {
|
|
14
|
-
triggerCharacters: ['.']
|
|
23
|
+
triggerCharacters: ['.'],
|
|
15
24
|
} satisfies CompletionProviderOptions
|
|
16
25
|
|
|
17
26
|
protected override completionForKeyword(
|
|
18
27
|
context: CompletionContext,
|
|
19
28
|
keyword: GrammarAST.Keyword,
|
|
20
|
-
acceptor: CompletionAcceptor
|
|
29
|
+
acceptor: CompletionAcceptor,
|
|
21
30
|
): MaybePromise<void> {
|
|
22
31
|
if (!this.filterKeyword(context, keyword)) {
|
|
23
32
|
return
|
|
@@ -33,8 +42,8 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
33
42
|
'\ttitle \'${2:Untitled}\'',
|
|
34
43
|
'\t',
|
|
35
44
|
'\tinclude $0',
|
|
36
|
-
'}'
|
|
37
|
-
].join('\n')
|
|
45
|
+
'}',
|
|
46
|
+
].join('\n'),
|
|
38
47
|
})
|
|
39
48
|
}
|
|
40
49
|
if (['title', 'description', 'technology'].includes(keyword.value)) {
|
|
@@ -42,7 +51,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
42
51
|
label: keyword.value,
|
|
43
52
|
kind: CompletionItemKind.Property,
|
|
44
53
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
45
|
-
insertText: `${keyword.value} '\${0}'
|
|
54
|
+
insertText: `${keyword.value} '\${0}'`,
|
|
46
55
|
})
|
|
47
56
|
}
|
|
48
57
|
if (['views', 'specification', 'model', 'deployment', 'with'].includes(keyword.value)) {
|
|
@@ -51,7 +60,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
51
60
|
detail: `Insert ${keyword.value} block`,
|
|
52
61
|
kind: CompletionItemKind.Module,
|
|
53
62
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
54
|
-
insertText: `${keyword.value} {\n\t$0\n}
|
|
63
|
+
insertText: `${keyword.value} {\n\t$0\n}`,
|
|
55
64
|
})
|
|
56
65
|
}
|
|
57
66
|
if (keyword.value === 'group') {
|
|
@@ -63,8 +72,8 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
63
72
|
insertText: [
|
|
64
73
|
'group \'${1:Title}\' {',
|
|
65
74
|
'\t$0',
|
|
66
|
-
'}'
|
|
67
|
-
].join('\n')
|
|
75
|
+
'}',
|
|
76
|
+
].join('\n'),
|
|
68
77
|
})
|
|
69
78
|
}
|
|
70
79
|
if (keyword.value === 'dynamic' && AstUtils.hasContainerOfType(context.node, ast.isModelViews)) {
|
|
@@ -78,8 +87,8 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
78
87
|
'\ttitle \'${2:Untitled}\'',
|
|
79
88
|
'\t',
|
|
80
89
|
'\t$0',
|
|
81
|
-
'}'
|
|
82
|
-
].join('\n')
|
|
90
|
+
'}',
|
|
91
|
+
].join('\n'),
|
|
83
92
|
})
|
|
84
93
|
}
|
|
85
94
|
if (keyword.value === 'style' && context.node) {
|
|
@@ -89,7 +98,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
89
98
|
detail: `Insert ${keyword.value} block`,
|
|
90
99
|
kind: CompletionItemKind.Module,
|
|
91
100
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
92
|
-
insertText: `${keyword.value} \${1:name} \${2:*} {\n\t\${3|
|
|
101
|
+
insertText: `${keyword.value} \${1:name} \${2:*} {\n\t\${3|${STYLE_FIELDS}|} $0\n}`,
|
|
93
102
|
})
|
|
94
103
|
}
|
|
95
104
|
if (AstUtils.hasContainerOfType(context.node, anyPass([ast.isModelViews, ast.isGlobalStyleGroup]))) {
|
|
@@ -98,7 +107,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
98
107
|
detail: `Insert ${keyword.value} block`,
|
|
99
108
|
kind: CompletionItemKind.Module,
|
|
100
109
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
101
|
-
insertText: `${keyword.value} \${1:*} {\n\t\${2|
|
|
110
|
+
insertText: `${keyword.value} \${1:*} {\n\t\${2|${STYLE_FIELDS}|} $0\n}`,
|
|
102
111
|
})
|
|
103
112
|
}
|
|
104
113
|
return acceptor(context, {
|
|
@@ -106,7 +115,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
106
115
|
detail: `Insert ${keyword.value} block`,
|
|
107
116
|
kind: CompletionItemKind.Module,
|
|
108
117
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
109
|
-
insertText: `${keyword.value} {\n\t\${1|
|
|
118
|
+
insertText: `${keyword.value} {\n\t\${1|${STYLE_FIELDS}|} $0\n}`,
|
|
110
119
|
})
|
|
111
120
|
}
|
|
112
121
|
if (keyword.value === 'extend') {
|
|
@@ -115,7 +124,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
115
124
|
detail: `Extend another view`,
|
|
116
125
|
kind: CompletionItemKind.Class,
|
|
117
126
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
118
|
-
insertText: 'extend ${1:element} {\n\t$0\n}'
|
|
127
|
+
insertText: 'extend ${1:element} {\n\t$0\n}',
|
|
119
128
|
})
|
|
120
129
|
}
|
|
121
130
|
|
|
@@ -124,14 +133,14 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
124
133
|
label: keyword.value,
|
|
125
134
|
kind: CompletionItemKind.Class,
|
|
126
135
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
127
|
-
insertText: 'autoLayout ${1|TopBottom,BottomTop,LeftRight,RightLeft|}$0'
|
|
136
|
+
insertText: 'autoLayout ${1|TopBottom,BottomTop,LeftRight,RightLeft|}$0',
|
|
128
137
|
})
|
|
129
138
|
}
|
|
130
139
|
acceptor(context, {
|
|
131
140
|
label: keyword.value,
|
|
132
141
|
kind: this.getKeywordCompletionItemKind(keyword),
|
|
133
142
|
detail: 'Keyword',
|
|
134
|
-
sortText: '1'
|
|
143
|
+
sortText: '1',
|
|
135
144
|
})
|
|
136
145
|
}
|
|
137
146
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Fqn } from '@likec4/core'
|
|
2
|
-
import type {
|
|
2
|
+
import type { DocumentCache, LangiumDocuments, Stream } from 'langium'
|
|
3
3
|
import { AstUtils, DocumentState, MultiMap } from 'langium'
|
|
4
4
|
import { forEachObj, groupBy, isTruthy, pipe, prop } from 'remeda'
|
|
5
5
|
import {
|
|
@@ -13,28 +13,15 @@ import { logWarnError } from '../logger'
|
|
|
13
13
|
import type { LikeC4Services } from '../module'
|
|
14
14
|
import type { LikeC4NameProvider } from '../references'
|
|
15
15
|
|
|
16
|
-
const DeploymentsIndexKey = Symbol.for('DeploymentsIndex')
|
|
17
|
-
|
|
18
|
-
type IndexedDocument = LangiumDocument & {
|
|
19
|
-
[DeploymentsIndexKey]?: DocumentDeploymentsIndex
|
|
20
|
-
}
|
|
21
|
-
|
|
22
16
|
export class DeploymentsIndex {
|
|
23
17
|
protected Names: LikeC4NameProvider
|
|
24
18
|
protected langiumDocuments: LangiumDocuments
|
|
19
|
+
protected documentCache: DocumentCache<string, DocumentDeploymentsIndex>
|
|
25
20
|
|
|
26
21
|
constructor(private services: LikeC4Services) {
|
|
27
22
|
this.Names = services.references.NameProvider
|
|
28
23
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments
|
|
29
|
-
|
|
30
|
-
services.shared.workspace.DocumentBuilder.onBuildPhase(
|
|
31
|
-
DocumentState.IndexedContent,
|
|
32
|
-
(docs, _cancelToken) => {
|
|
33
|
-
for (const doc of docs) {
|
|
34
|
-
delete (doc as IndexedDocument)[DeploymentsIndexKey]
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
)
|
|
24
|
+
this.documentCache = services.DocumentCache
|
|
38
25
|
}
|
|
39
26
|
|
|
40
27
|
private documents() {
|
|
@@ -47,7 +34,7 @@ export class DeploymentsIndex {
|
|
|
47
34
|
if (document.state < DocumentState.IndexedContent) {
|
|
48
35
|
logWarnError(`Document ${document.uri.path} is not indexed`)
|
|
49
36
|
}
|
|
50
|
-
return (document
|
|
37
|
+
return this.documentCache.get(document.uri, 'DeploymentsIndex', () => this.createDocumentIndex(document))
|
|
51
38
|
}
|
|
52
39
|
/**
|
|
53
40
|
* Nested elements (nodes/artifacts) of the node
|
package/src/model/fqn-index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ast, DocFqnIndexAstNodeDescription, FqnIndexedDocument } from '../
|
|
|
6
6
|
import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from '../ast'
|
|
7
7
|
import { logger, logWarnError } from '../logger'
|
|
8
8
|
import type { LikeC4Services } from '../module'
|
|
9
|
+
import { ADisposable } from '../utils'
|
|
9
10
|
import { computeDocumentFqn } from './fqn-computation'
|
|
10
11
|
|
|
11
12
|
export interface FqnIndexEntry {
|
|
@@ -16,31 +17,37 @@ export interface FqnIndexEntry {
|
|
|
16
17
|
path: string
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export class FqnIndex {
|
|
20
|
+
export class FqnIndex extends ADisposable {
|
|
20
21
|
protected langiumDocuments: LangiumDocuments
|
|
21
22
|
|
|
22
23
|
constructor(private services: LikeC4Services) {
|
|
24
|
+
super()
|
|
23
25
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
this.onDispose(
|
|
28
|
+
services.shared.workspace.DocumentBuilder.onBuildPhase(
|
|
29
|
+
DocumentState.IndexedContent,
|
|
30
|
+
async (docs, _cancelToken) => {
|
|
31
|
+
for (const doc of docs) {
|
|
32
|
+
if (isLikeC4LangiumDocument(doc)) {
|
|
33
|
+
delete doc.c4fqnIndex
|
|
34
|
+
delete doc.c4Elements
|
|
35
|
+
delete doc.c4Specification
|
|
36
|
+
delete doc.c4Relations
|
|
37
|
+
delete doc.c4Deployments
|
|
38
|
+
delete doc.c4DeploymentRelations
|
|
39
|
+
delete doc.c4Globals
|
|
40
|
+
delete doc.c4Views
|
|
41
|
+
try {
|
|
42
|
+
computeDocumentFqn(doc, services)
|
|
43
|
+
} catch (e) {
|
|
44
|
+
logWarnError(e)
|
|
45
|
+
}
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
return await Promise.resolve()
|
|
49
|
+
},
|
|
50
|
+
),
|
|
44
51
|
)
|
|
45
52
|
logger.debug(`[FqnIndex] Created`)
|
|
46
53
|
}
|
|
@@ -125,7 +132,7 @@ export class FqnIndex {
|
|
|
125
132
|
return iterator.next()
|
|
126
133
|
}
|
|
127
134
|
return DONE_RESULT as IteratorResult<AstNodeDescription>
|
|
128
|
-
}
|
|
135
|
+
},
|
|
129
136
|
)
|
|
130
137
|
}
|
|
131
138
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
flatMap,
|
|
20
20
|
groupBy,
|
|
21
21
|
indexBy,
|
|
22
|
+
isBoolean,
|
|
22
23
|
isDefined,
|
|
23
24
|
isEmpty,
|
|
24
25
|
isNonNullish,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
map,
|
|
29
30
|
mapToObj,
|
|
30
31
|
mapValues,
|
|
31
|
-
|
|
32
|
+
omit,
|
|
32
33
|
pipe,
|
|
33
34
|
prop,
|
|
34
35
|
reduce,
|
|
@@ -48,6 +49,7 @@ import type {
|
|
|
48
49
|
import { isParsedLikeC4LangiumDocument } from '../ast'
|
|
49
50
|
import { logger, logWarnError } from '../logger'
|
|
50
51
|
import type { LikeC4Services } from '../module'
|
|
52
|
+
import { ADisposable } from '../utils'
|
|
51
53
|
import { assignNavigateTo, resolveRelativePaths } from '../view-utils'
|
|
52
54
|
|
|
53
55
|
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
|
|
@@ -115,6 +117,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
115
117
|
icon,
|
|
116
118
|
opacity,
|
|
117
119
|
border,
|
|
120
|
+
multiple,
|
|
118
121
|
},
|
|
119
122
|
id,
|
|
120
123
|
kind,
|
|
@@ -136,6 +139,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
136
139
|
opacity ??= __kind.style.opacity
|
|
137
140
|
border ??= __kind.style.border
|
|
138
141
|
technology ??= __kind.technology
|
|
142
|
+
multiple ??= __kind.style.multiple
|
|
139
143
|
return {
|
|
140
144
|
...(color && { color }),
|
|
141
145
|
...(shape && { shape }),
|
|
@@ -144,6 +148,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
144
148
|
...(__kind.notation && { notation: __kind.notation }),
|
|
145
149
|
style: {
|
|
146
150
|
...(border && { border }),
|
|
151
|
+
...(isBoolean(multiple) && { multiple }),
|
|
147
152
|
...(isNumber(opacity) && { opacity }),
|
|
148
153
|
},
|
|
149
154
|
links,
|
|
@@ -449,37 +454,41 @@ const CACHE_KEY_COMPUTED_MODEL = 'ComputedLikeC4Model'
|
|
|
449
454
|
|
|
450
455
|
type ModelParsedListener = (docs: URI[]) => void
|
|
451
456
|
|
|
452
|
-
export class LikeC4ModelBuilder {
|
|
457
|
+
export class LikeC4ModelBuilder extends ADisposable {
|
|
453
458
|
private langiumDocuments: LangiumDocuments
|
|
454
459
|
private listeners: ModelParsedListener[] = []
|
|
455
460
|
|
|
456
461
|
constructor(private services: LikeC4Services) {
|
|
462
|
+
super()
|
|
457
463
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments
|
|
458
464
|
const parser = services.likec4.ModelParser
|
|
459
465
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
466
|
+
this.onDispose(
|
|
467
|
+
services.shared.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
|
|
468
|
+
if (deleted.length > 0) {
|
|
469
|
+
this.notifyListeners(deleted)
|
|
470
|
+
}
|
|
471
|
+
}),
|
|
472
|
+
)
|
|
473
|
+
this.onDispose(
|
|
474
|
+
services.shared.workspace.DocumentBuilder.onBuildPhase(
|
|
475
|
+
DocumentState.Validated,
|
|
476
|
+
async (docs, _cancelToken) => {
|
|
477
|
+
let parsed = [] as URI[]
|
|
471
478
|
logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)`)
|
|
472
479
|
for (const doc of docs) {
|
|
473
|
-
|
|
480
|
+
try {
|
|
481
|
+
parsed.push(parser.parse(doc).uri)
|
|
482
|
+
} catch (e) {
|
|
483
|
+
logWarnError(e)
|
|
484
|
+
}
|
|
474
485
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return await Promise.resolve()
|
|
482
|
-
},
|
|
486
|
+
await interruptAndCheck(_cancelToken)
|
|
487
|
+
if (parsed.length > 0) {
|
|
488
|
+
this.notifyListeners(parsed)
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
),
|
|
483
492
|
)
|
|
484
493
|
logger.debug(`[ModelBuilder] Created`)
|
|
485
494
|
}
|
|
@@ -546,15 +555,7 @@ export class LikeC4ModelBuilder {
|
|
|
546
555
|
})
|
|
547
556
|
this.previousViews = { ...views }
|
|
548
557
|
return {
|
|
549
|
-
...
|
|
550
|
-
pick(model, [
|
|
551
|
-
'specification',
|
|
552
|
-
'elements',
|
|
553
|
-
'relations',
|
|
554
|
-
'globals',
|
|
555
|
-
'deployments',
|
|
556
|
-
]),
|
|
557
|
-
),
|
|
558
|
+
...omit(model, ['views']),
|
|
558
559
|
views,
|
|
559
560
|
}
|
|
560
561
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { invariant } from '@likec4/core'
|
|
2
|
-
import type
|
|
3
|
-
import DefaultWeakMap from 'mnemonist
|
|
2
|
+
import { type LangiumDocument, DocumentCache, DocumentState } from 'langium'
|
|
3
|
+
import { DefaultWeakMap } from 'mnemonist'
|
|
4
4
|
import { pipe } from 'remeda'
|
|
5
5
|
import type { LikeC4DocumentProps, ParsedLikeC4LangiumDocument } from '../ast'
|
|
6
6
|
import { isFqnIndexedDocument } from '../ast'
|
|
@@ -26,18 +26,17 @@ const DocumentParserFromMixins = pipe(
|
|
|
26
26
|
PredicatesParser,
|
|
27
27
|
SpecificationParser,
|
|
28
28
|
ViewsParser,
|
|
29
|
-
GlobalsParser
|
|
29
|
+
GlobalsParser,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
export class DocumentParser extends DocumentParserFromMixins {
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export class LikeC4ModelParser {
|
|
36
|
-
private cachedParsers
|
|
37
|
-
new DocumentParser(this.services, doc as ParsedLikeC4LangiumDocument)
|
|
38
|
-
)
|
|
36
|
+
private cachedParsers: DocumentCache<string, DocumentParser>
|
|
39
37
|
|
|
40
38
|
constructor(private services: LikeC4Services) {
|
|
39
|
+
this.cachedParsers = new DocumentCache(services.shared, DocumentState.Validated)
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
parse(doc: LangiumDocument): ParsedLikeC4LangiumDocument {
|
|
@@ -49,7 +48,7 @@ export class LikeC4ModelParser {
|
|
|
49
48
|
elements: {},
|
|
50
49
|
relationships: {},
|
|
51
50
|
colors: {},
|
|
52
|
-
deployments: {}
|
|
51
|
+
deployments: {},
|
|
53
52
|
},
|
|
54
53
|
c4Elements: [],
|
|
55
54
|
c4Relations: [],
|
|
@@ -58,12 +57,12 @@ export class LikeC4ModelParser {
|
|
|
58
57
|
c4Globals: {
|
|
59
58
|
predicates: {},
|
|
60
59
|
dynamicPredicates: {},
|
|
61
|
-
styles: {}
|
|
60
|
+
styles: {},
|
|
62
61
|
},
|
|
63
|
-
c4Views: []
|
|
62
|
+
c4Views: [],
|
|
64
63
|
}
|
|
65
64
|
doc = Object.assign(doc, props)
|
|
66
|
-
const parser = this.
|
|
65
|
+
const parser = this.forDocument(doc)
|
|
67
66
|
parser.parseSpecification()
|
|
68
67
|
parser.parseModel()
|
|
69
68
|
parser.parseGlobals()
|
|
@@ -77,6 +76,10 @@ export class LikeC4ModelParser {
|
|
|
77
76
|
|
|
78
77
|
forDocument(doc: LangiumDocument): DocumentParser {
|
|
79
78
|
invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
|
|
80
|
-
return this.cachedParsers.get(
|
|
79
|
+
return this.cachedParsers.get(
|
|
80
|
+
doc.uri,
|
|
81
|
+
'DocumentParser',
|
|
82
|
+
() => new DocumentParser(this.services, doc as ParsedLikeC4LangiumDocument),
|
|
83
|
+
)
|
|
81
84
|
}
|
|
82
85
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core'
|
|
2
2
|
import { invariant, nonexhaustive } from '@likec4/core'
|
|
3
|
-
import { isDefined, isTruthy } from 'remeda'
|
|
3
|
+
import { isBoolean, isDefined, isTruthy } from 'remeda'
|
|
4
4
|
import { ast, parseAstOpacityProperty, toColor } from '../../ast'
|
|
5
5
|
import { logWarnError } from '../../logger'
|
|
6
6
|
import { elementRef } from '../../utils/elementRef'
|
|
@@ -53,14 +53,14 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
53
53
|
parseElementExpression(astNode: ast.ElementExpression): c4.ElementExpression {
|
|
54
54
|
if (ast.isWildcardExpression(astNode)) {
|
|
55
55
|
return {
|
|
56
|
-
wildcard: true
|
|
56
|
+
wildcard: true,
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
if (ast.isElementKindExpression(astNode)) {
|
|
60
60
|
invariant(astNode.kind?.ref, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
|
|
61
61
|
return {
|
|
62
62
|
elementKind: astNode.kind.ref.name as c4.ElementKind,
|
|
63
|
-
isEqual: astNode.isEqual
|
|
63
|
+
isEqual: astNode.isEqual,
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
if (ast.isElementTagExpression(astNode)) {
|
|
@@ -71,7 +71,7 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
71
71
|
}
|
|
72
72
|
return {
|
|
73
73
|
elementTag: elementTag as c4.Tag,
|
|
74
|
-
isEqual: astNode.isEqual
|
|
74
|
+
isEqual: astNode.isEqual,
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
if (ast.isExpandElementExpression(astNode)) {
|
|
@@ -79,7 +79,7 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
79
79
|
invariant(elementNode, 'Element not found ' + astNode.expand.$cstNode?.text)
|
|
80
80
|
const expanded = this.resolveFqn(elementNode)
|
|
81
81
|
return {
|
|
82
|
-
expanded
|
|
82
|
+
expanded,
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
if (ast.isElementDescedantsExpression(astNode)) {
|
|
@@ -89,7 +89,7 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
89
89
|
return {
|
|
90
90
|
element,
|
|
91
91
|
isChildren: astNode.suffix === '.*',
|
|
92
|
-
isDescendants: astNode.suffix === '.**'
|
|
92
|
+
isDescendants: astNode.suffix === '.**',
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
if (ast.isElementRef(astNode)) {
|
|
@@ -97,7 +97,7 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
97
97
|
invariant(elementNode, 'Element not found ' + astNode.$cstNode?.text)
|
|
98
98
|
const element = this.resolveFqn(elementNode)
|
|
99
99
|
return {
|
|
100
|
-
element
|
|
100
|
+
element,
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
nonexhaustive(astNode)
|
|
@@ -109,9 +109,9 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
109
109
|
where: {
|
|
110
110
|
expr,
|
|
111
111
|
condition: astNode.where ? parseWhereClause(astNode.where) : {
|
|
112
|
-
kind: { neq: '--always-true--' }
|
|
113
|
-
}
|
|
114
|
-
}
|
|
112
|
+
kind: { neq: '--always-true--' },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -174,13 +174,19 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
174
174
|
}
|
|
175
175
|
return acc
|
|
176
176
|
}
|
|
177
|
+
if (ast.isMultipleProperty(prop)) {
|
|
178
|
+
if (isBoolean(prop.value)) {
|
|
179
|
+
acc.custom[prop.key] = prop.value
|
|
180
|
+
}
|
|
181
|
+
return acc
|
|
182
|
+
}
|
|
177
183
|
nonexhaustive(prop)
|
|
178
184
|
},
|
|
179
185
|
{
|
|
180
186
|
custom: {
|
|
181
|
-
expr
|
|
182
|
-
}
|
|
183
|
-
} as c4.CustomElementExpr
|
|
187
|
+
expr,
|
|
188
|
+
},
|
|
189
|
+
} as c4.CustomElementExpr,
|
|
184
190
|
)
|
|
185
191
|
}
|
|
186
192
|
|
|
@@ -207,15 +213,15 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
207
213
|
where: {
|
|
208
214
|
expr,
|
|
209
215
|
condition: astNode.where ? parseWhereClause(astNode.where) : {
|
|
210
|
-
kind: { neq: '--always-true--' }
|
|
211
|
-
}
|
|
212
|
-
}
|
|
216
|
+
kind: { neq: '--always-true--' },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
parseRelationPredicateWith(
|
|
217
223
|
astNode: ast.RelationPredicateWith,
|
|
218
|
-
relation: c4.RelationExpression | c4.RelationWhereExpr
|
|
224
|
+
relation: c4.RelationExpression | c4.RelationWhereExpr,
|
|
219
225
|
): c4.CustomRelationExpr {
|
|
220
226
|
const props = astNode.custom?.props ?? []
|
|
221
227
|
return props.reduce(
|
|
@@ -256,9 +262,9 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
256
262
|
},
|
|
257
263
|
{
|
|
258
264
|
customRelation: {
|
|
259
|
-
relation
|
|
260
|
-
}
|
|
261
|
-
} as c4.CustomRelationExpr
|
|
265
|
+
relation,
|
|
266
|
+
},
|
|
267
|
+
} as c4.CustomRelationExpr,
|
|
262
268
|
)
|
|
263
269
|
}
|
|
264
270
|
|
|
@@ -267,22 +273,22 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
267
273
|
return {
|
|
268
274
|
source: this.parseElementExpression(astNode.source.from),
|
|
269
275
|
target: this.parseElementExpression(astNode.target),
|
|
270
|
-
isBidirectional: astNode.source.isBidirectional
|
|
276
|
+
isBidirectional: astNode.source.isBidirectional,
|
|
271
277
|
}
|
|
272
278
|
}
|
|
273
279
|
if (ast.isInOutRelationExpression(astNode)) {
|
|
274
280
|
return {
|
|
275
|
-
inout: this.parseElementExpression(astNode.inout.to)
|
|
281
|
+
inout: this.parseElementExpression(astNode.inout.to),
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
if (ast.isOutgoingRelationExpression(astNode)) {
|
|
279
285
|
return {
|
|
280
|
-
outgoing: this.parseElementExpression(astNode.from)
|
|
286
|
+
outgoing: this.parseElementExpression(astNode.from),
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
if (ast.isIncomingRelationExpression(astNode)) {
|
|
284
290
|
return {
|
|
285
|
-
incoming: this.parseElementExpression(astNode.to)
|
|
291
|
+
incoming: this.parseElementExpression(astNode.to),
|
|
286
292
|
}
|
|
287
293
|
}
|
|
288
294
|
nonexhaustive(astNode)
|