@likec4/language-server 1.2.0 → 1.2.2
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/package.json +8 -10
- package/src/Rpc.ts +108 -0
- package/src/ast.ts +443 -0
- package/src/browser/index.ts +30 -0
- package/src/elementRef.ts +26 -0
- package/src/generated/ast.ts +1632 -0
- package/src/generated/grammar.ts +10 -0
- package/src/generated/module.ts +32 -0
- package/src/index.ts +4 -0
- package/src/like-c4.langium +395 -0
- package/src/logger.ts +54 -0
- package/src/lsp/CodeLensProvider.ts +51 -0
- package/src/lsp/DocumentHighlightProvider.ts +12 -0
- package/src/lsp/DocumentLinkProvider.test.ts +66 -0
- package/src/lsp/DocumentLinkProvider.ts +53 -0
- package/src/lsp/DocumentSymbolProvider.ts +201 -0
- package/src/lsp/HoverProvider.ts +58 -0
- package/{dist/lsp/SemanticTokenProvider.js → src/lsp/SemanticTokenProvider.ts} +57 -42
- package/src/lsp/index.ts +6 -0
- package/src/model/fqn-computation.ts +47 -0
- package/src/model/fqn-index.ts +161 -0
- package/src/model/index.ts +5 -0
- package/src/model/model-builder.ts +447 -0
- package/src/model/model-locator.ts +130 -0
- package/src/model/model-parser.ts +580 -0
- package/src/model-change/ModelChanges.ts +120 -0
- package/src/model-change/changeElementStyle.ts +176 -0
- package/src/model-change/changeViewLayout.ts +41 -0
- package/src/module.ts +197 -0
- package/src/node/index.ts +20 -0
- package/src/protocol.ts +87 -0
- package/src/references/index.ts +2 -0
- package/src/references/scope-computation.ts +142 -0
- package/src/references/scope-provider.ts +166 -0
- package/src/shared/NodeKindProvider.ts +67 -0
- package/src/shared/WorkspaceManager.ts +39 -0
- package/src/shared/WorkspaceSymbolProvider.ts +3 -0
- package/src/shared/index.ts +3 -0
- package/src/test/index.ts +1 -0
- package/src/test/testServices.ts +119 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/printDocs.ts +3 -0
- package/src/utils/stringHash.ts +6 -0
- package/{dist/validation/dynamic-view-rule.js → src/validation/dynamic-view-rule.ts} +14 -11
- package/src/validation/dynamic-view-step.ts +39 -0
- package/src/validation/element.ts +52 -0
- package/{dist/validation/index.js → src/validation/index.ts} +22 -18
- package/src/validation/property-checks.ts +17 -0
- package/src/validation/relation.ts +57 -0
- package/src/validation/specification.ts +118 -0
- package/src/validation/view-predicates/custom-element-expr.ts +21 -0
- package/{dist/validation/view-predicates/expanded-element.js → src/validation/view-predicates/expanded-element.ts} +18 -13
- package/src/validation/view-predicates/incoming.ts +19 -0
- package/src/validation/view-predicates/index.ts +4 -0
- package/src/validation/view-predicates/outgoing.ts +19 -0
- package/src/validation/view.ts +26 -0
- package/src/view-utils/assignNavigateTo.ts +30 -0
- package/src/view-utils/index.ts +3 -0
- package/src/view-utils/resolve-extended-views.ts +57 -0
- package/src/view-utils/resolve-relative-paths.ts +84 -0
- package/dist/Rpc.d.ts +0 -10
- package/dist/Rpc.js +0 -98
- package/dist/ast.d.ts +0 -149
- package/dist/ast.js +0 -271
- package/dist/browser/index.d.ts +0 -9
- package/dist/browser/index.js +0 -16
- package/dist/elementRef.d.ts +0 -12
- package/dist/elementRef.js +0 -15
- package/dist/generated/ast.d.ts +0 -615
- package/dist/generated/ast.js +0 -957
- package/dist/generated/grammar.d.ts +0 -7
- package/dist/generated/grammar.js +0 -3
- package/dist/generated/module.d.ts +0 -14
- package/dist/generated/module.js +0 -22
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -2
- package/dist/logger.d.ts +0 -12
- package/dist/logger.js +0 -51
- package/dist/lsp/CodeLensProvider.d.ts +0 -10
- package/dist/lsp/CodeLensProvider.js +0 -40
- package/dist/lsp/DocumentHighlightProvider.d.ts +0 -10
- package/dist/lsp/DocumentHighlightProvider.js +0 -10
- package/dist/lsp/DocumentLinkProvider.d.ts +0 -11
- package/dist/lsp/DocumentLinkProvider.js +0 -41
- package/dist/lsp/DocumentLinkProvider.test.d.ts +0 -2
- package/dist/lsp/DocumentLinkProvider.test.js +0 -54
- package/dist/lsp/DocumentSymbolProvider.d.ts +0 -22
- package/dist/lsp/DocumentSymbolProvider.js +0 -189
- package/dist/lsp/HoverProvider.d.ts +0 -10
- package/dist/lsp/HoverProvider.js +0 -36
- package/dist/lsp/SemanticTokenProvider.d.ts +0 -8
- package/dist/lsp/index.d.ts +0 -7
- package/dist/lsp/index.js +0 -6
- package/dist/model/fqn-computation.d.ts +0 -4
- package/dist/model/fqn-computation.js +0 -43
- package/dist/model/fqn-index.d.ts +0 -26
- package/dist/model/fqn-index.js +0 -114
- package/dist/model/index.d.ts +0 -6
- package/dist/model/index.js +0 -5
- package/dist/model/model-builder.d.ts +0 -20
- package/dist/model/model-builder.js +0 -365
- package/dist/model/model-locator.d.ts +0 -22
- package/dist/model/model-locator.js +0 -115
- package/dist/model/model-parser.d.ts +0 -29
- package/dist/model/model-parser.js +0 -520
- package/dist/model-change/ModelChanges.d.ts +0 -16
- package/dist/model-change/ModelChanges.js +0 -106
- package/dist/model-change/changeElementStyle.d.ts +0 -18
- package/dist/model-change/changeElementStyle.js +0 -141
- package/dist/model-change/changeViewLayout.d.ts +0 -13
- package/dist/model-change/changeViewLayout.js +0 -29
- package/dist/module.d.ts +0 -59
- package/dist/module.js +0 -121
- package/dist/node/index.d.ts +0 -6
- package/dist/node/index.js +0 -13
- package/dist/protocol.d.ts +0 -58
- package/dist/protocol.js +0 -14
- package/dist/references/index.d.ts +0 -3
- package/dist/references/index.js +0 -2
- package/dist/references/scope-computation.d.ts +0 -11
- package/dist/references/scope-computation.js +0 -111
- package/dist/references/scope-provider.d.ts +0 -18
- package/dist/references/scope-provider.js +0 -136
- package/dist/shared/NodeKindProvider.d.ts +0 -16
- package/dist/shared/NodeKindProvider.js +0 -60
- package/dist/shared/WorkspaceManager.d.ts +0 -17
- package/dist/shared/WorkspaceManager.js +0 -29
- package/dist/shared/WorkspaceSymbolProvider.d.ts +0 -4
- package/dist/shared/WorkspaceSymbolProvider.js +0 -3
- package/dist/shared/index.d.ts +0 -4
- package/dist/shared/index.js +0 -3
- package/dist/test/index.d.ts +0 -2
- package/dist/test/index.js +0 -1
- package/dist/test/testServices.d.ts +0 -23
- package/dist/test/testServices.js +0 -102
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -1
- package/dist/utils/printDocs.d.ts +0 -3
- package/dist/utils/printDocs.js +0 -1
- package/dist/utils/stringHash.d.ts +0 -2
- package/dist/utils/stringHash.js +0 -5
- package/dist/validation/dynamic-view-rule.d.ts +0 -5
- package/dist/validation/dynamic-view-step.d.ts +0 -5
- package/dist/validation/dynamic-view-step.js +0 -33
- package/dist/validation/element.d.ts +0 -6
- package/dist/validation/element.js +0 -38
- package/dist/validation/index.d.ts +0 -3
- package/dist/validation/property-checks.d.ts +0 -5
- package/dist/validation/property-checks.js +0 -11
- package/dist/validation/relation.d.ts +0 -5
- package/dist/validation/relation.js +0 -50
- package/dist/validation/specification.d.ts +0 -10
- package/dist/validation/specification.js +0 -97
- package/dist/validation/view-predicates/custom-element-expr.d.ts +0 -5
- package/dist/validation/view-predicates/custom-element-expr.js +0 -16
- package/dist/validation/view-predicates/expanded-element.d.ts +0 -5
- package/dist/validation/view-predicates/incoming.d.ts +0 -5
- package/dist/validation/view-predicates/incoming.js +0 -14
- package/dist/validation/view-predicates/index.d.ts +0 -5
- package/dist/validation/view-predicates/index.js +0 -4
- package/dist/validation/view-predicates/outgoing.d.ts +0 -5
- package/dist/validation/view-predicates/outgoing.js +0 -14
- package/dist/validation/view.d.ts +0 -5
- package/dist/validation/view.js +0 -16
- package/dist/view-utils/assignNavigateTo.d.ts +0 -3
- package/dist/view-utils/assignNavigateTo.js +0 -24
- package/dist/view-utils/index.d.ts +0 -4
- package/dist/view-utils/index.js +0 -3
- package/dist/view-utils/resolve-extended-views.d.ts +0 -7
- package/dist/view-utils/resolve-extended-views.js +0 -41
- package/dist/view-utils/resolve-relative-paths.d.ts +0 -3
- package/dist/view-utils/resolve-relative-paths.js +0 -75
- /package/{dist → src}/reset.d.ts +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/language-server",
|
|
3
3
|
"description": "LikeC4 Language Server",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"contrib",
|
|
12
|
+
"src",
|
|
12
13
|
"!**/__mocks__/",
|
|
13
14
|
"!**/__tests__/",
|
|
14
15
|
"!**/*.spec.*",
|
|
@@ -61,21 +62,18 @@
|
|
|
61
62
|
}
|
|
62
63
|
},
|
|
63
64
|
"scripts": {
|
|
64
|
-
"typecheck": "tsc
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
65
66
|
"watch:langium": "langium generate --watch",
|
|
66
67
|
"watch:ts": "tsc --watch",
|
|
67
68
|
"generate": "langium generate",
|
|
68
|
-
"prepack": "unbuild",
|
|
69
|
-
"build:turbo": "run -T turbo build --filter='language-server'",
|
|
70
|
-
"build": "unbuild",
|
|
71
69
|
"dev": "run-p 'watch:*'",
|
|
72
70
|
"lint": "run -T eslint src/ --fix",
|
|
73
71
|
"clean": "run -T rimraf dist contrib",
|
|
74
72
|
"test": "vitest run"
|
|
75
73
|
},
|
|
76
74
|
"dependencies": {
|
|
77
|
-
"@likec4/core": "1.2.
|
|
78
|
-
"@likec4/graph": "1.2.
|
|
75
|
+
"@likec4/core": "1.2.2",
|
|
76
|
+
"@likec4/graph": "1.2.2",
|
|
79
77
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
80
78
|
"fast-equals": "^5.0.1",
|
|
81
79
|
"langium": "^3.0.0",
|
|
@@ -92,15 +90,15 @@
|
|
|
92
90
|
"vscode-uri": "3.0.8"
|
|
93
91
|
},
|
|
94
92
|
"devDependencies": {
|
|
95
|
-
"@
|
|
93
|
+
"@likec4/tsconfig": "1.2.2",
|
|
94
|
+
"@types/node": "^20.14.2",
|
|
96
95
|
"@types/object-hash": "^3.0.6",
|
|
97
96
|
"@types/string-hash": "^1",
|
|
98
97
|
"execa": "^9.1.0",
|
|
99
98
|
"langium-cli": "3.0.3",
|
|
100
99
|
"npm-run-all2": "^6.1.2",
|
|
101
100
|
"typescript": "^5.4.5",
|
|
102
|
-
"
|
|
103
|
-
"vitest": "~1.5.2"
|
|
101
|
+
"vitest": "~1.5.3"
|
|
104
102
|
},
|
|
105
103
|
"packageManager": "yarn@4.3.0"
|
|
106
104
|
}
|
package/src/Rpc.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { debounce } from 'remeda'
|
|
2
|
+
import { logError, logger } from './logger'
|
|
3
|
+
import type { LikeC4Services } from './module'
|
|
4
|
+
|
|
5
|
+
import { nonexhaustive } from '@likec4/core'
|
|
6
|
+
import { Disposable, URI, UriUtils } from 'langium'
|
|
7
|
+
import { isLikeC4LangiumDocument } from './ast'
|
|
8
|
+
import {
|
|
9
|
+
buildDocuments,
|
|
10
|
+
changeView,
|
|
11
|
+
computeView,
|
|
12
|
+
fetchComputedModel,
|
|
13
|
+
fetchModel,
|
|
14
|
+
locate,
|
|
15
|
+
onDidChangeModel
|
|
16
|
+
} from './protocol'
|
|
17
|
+
|
|
18
|
+
export class Rpc implements Disposable {
|
|
19
|
+
private disposables = [] as Array<Disposable>
|
|
20
|
+
|
|
21
|
+
constructor(private services: LikeC4Services) {}
|
|
22
|
+
|
|
23
|
+
init() {
|
|
24
|
+
const modelBuilder = this.services.likec4.ModelBuilder
|
|
25
|
+
const modelLocator = this.services.likec4.ModelLocator
|
|
26
|
+
const modelEditor = this.services.likec4.ModelChanges
|
|
27
|
+
const connection = this.services.shared.lsp.Connection
|
|
28
|
+
if (!connection) {
|
|
29
|
+
logger.warn(`[ServerRpc] no connection, not initializing`)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
logger.info(`[ServerRpc] init`)
|
|
33
|
+
const LangiumDocuments = this.services.shared.workspace.LangiumDocuments
|
|
34
|
+
const DocumentBuilder = this.services.shared.workspace.DocumentBuilder
|
|
35
|
+
|
|
36
|
+
const notifyModelParsed = debounce(
|
|
37
|
+
() =>
|
|
38
|
+
void connection.sendNotification(onDidChangeModel, '').catch(e => {
|
|
39
|
+
logger.error(`[ServerRpc] error sending onDidChangeModel: ${e}`)
|
|
40
|
+
return Promise.resolve()
|
|
41
|
+
}),
|
|
42
|
+
{
|
|
43
|
+
timing: 'both',
|
|
44
|
+
waitMs: 350,
|
|
45
|
+
maxWaitMs: 1000
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
this.disposables.push(
|
|
50
|
+
Disposable.create(() => {
|
|
51
|
+
notifyModelParsed.cancel()
|
|
52
|
+
}),
|
|
53
|
+
modelBuilder.onModelParsed(() => notifyModelParsed.call()),
|
|
54
|
+
connection.onRequest(fetchComputedModel, async cancelToken => {
|
|
55
|
+
const model = await modelBuilder.buildComputedModel(cancelToken)
|
|
56
|
+
return { model }
|
|
57
|
+
}),
|
|
58
|
+
connection.onRequest(fetchModel, async cancelToken => {
|
|
59
|
+
const model = await modelBuilder.buildModel(cancelToken)
|
|
60
|
+
return { model }
|
|
61
|
+
}),
|
|
62
|
+
connection.onRequest(computeView, async ({ viewId }, cancelToken) => {
|
|
63
|
+
const view = await modelBuilder.computeView(viewId, cancelToken)
|
|
64
|
+
return { view }
|
|
65
|
+
}),
|
|
66
|
+
connection.onRequest(buildDocuments, async ({ docs }, cancelToken) => {
|
|
67
|
+
const changed = docs.map(d => URI.parse(d))
|
|
68
|
+
const notChanged = (uri: URI) => changed.every(c => !UriUtils.equals(c, uri))
|
|
69
|
+
const deleted = LangiumDocuments.all
|
|
70
|
+
.filter(d => isLikeC4LangiumDocument(d) && notChanged(d.uri))
|
|
71
|
+
.map(d => d.uri)
|
|
72
|
+
.toArray()
|
|
73
|
+
logger.debug(
|
|
74
|
+
`[ServerRpc] received request to build:
|
|
75
|
+
changed (total ${changed.length}):${docs.map(d => '\n - ' + d).join('')}
|
|
76
|
+
deleted (total ${deleted.length}):${deleted.map(d => '\n - ' + d.toString()).join('\n')}`
|
|
77
|
+
)
|
|
78
|
+
await DocumentBuilder.update(changed, deleted, cancelToken)
|
|
79
|
+
}),
|
|
80
|
+
connection.onRequest(locate, params => {
|
|
81
|
+
if ('element' in params) {
|
|
82
|
+
return modelLocator.locateElement(params.element, params.property ?? 'name')
|
|
83
|
+
}
|
|
84
|
+
if ('relation' in params) {
|
|
85
|
+
return modelLocator.locateRelation(params.relation)
|
|
86
|
+
}
|
|
87
|
+
if ('view' in params) {
|
|
88
|
+
return modelLocator.locateView(params.view)
|
|
89
|
+
}
|
|
90
|
+
nonexhaustive(params)
|
|
91
|
+
}),
|
|
92
|
+
connection.onRequest(changeView, async (request, _cancelToken) => {
|
|
93
|
+
return await modelEditor.applyChange(request)
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
dispose() {
|
|
99
|
+
let item
|
|
100
|
+
while (item = this.disposables.pop()) {
|
|
101
|
+
try {
|
|
102
|
+
item.dispose()
|
|
103
|
+
} catch (e) {
|
|
104
|
+
logError(e)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/ast.ts
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type c4,
|
|
3
|
+
DefaultArrowType,
|
|
4
|
+
DefaultLineStyle,
|
|
5
|
+
DefaultRelationshipColor,
|
|
6
|
+
nonexhaustive,
|
|
7
|
+
RelationRefError
|
|
8
|
+
} from '@likec4/core'
|
|
9
|
+
import type { AstNode, DiagnosticInfo, LangiumDocument, MultiMap } from 'langium'
|
|
10
|
+
import { AstUtils, DocumentState } from 'langium'
|
|
11
|
+
import { clamp, isNullish } from 'remeda'
|
|
12
|
+
import type { ConditionalPick, SetRequired, ValueOf } from 'type-fest'
|
|
13
|
+
import type { Diagnostic } from 'vscode-languageserver-protocol'
|
|
14
|
+
import { DiagnosticSeverity } from 'vscode-languageserver-protocol'
|
|
15
|
+
import { elementRef } from './elementRef'
|
|
16
|
+
import type { LikeC4Grammar } from './generated/ast'
|
|
17
|
+
import * as ast from './generated/ast'
|
|
18
|
+
import { LikeC4LanguageMetaData } from './generated/module'
|
|
19
|
+
|
|
20
|
+
export { ast }
|
|
21
|
+
|
|
22
|
+
const idattr = Symbol.for('idattr')
|
|
23
|
+
|
|
24
|
+
declare module './generated/ast' {
|
|
25
|
+
export interface Element {
|
|
26
|
+
[idattr]?: c4.Fqn | undefined
|
|
27
|
+
}
|
|
28
|
+
export interface ElementView {
|
|
29
|
+
[idattr]?: c4.ViewID | undefined
|
|
30
|
+
}
|
|
31
|
+
export interface DynamicView {
|
|
32
|
+
[idattr]?: c4.ViewID | undefined
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ParsedElementStyle = {
|
|
37
|
+
shape?: c4.ElementShape
|
|
38
|
+
icon?: c4.IconUrl
|
|
39
|
+
color?: c4.ThemeColor
|
|
40
|
+
border?: c4.BorderStyle
|
|
41
|
+
opacity?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ParsedAstSpecification {
|
|
45
|
+
kinds: Record<c4.ElementKind, ParsedElementStyle>
|
|
46
|
+
relationships: Record<
|
|
47
|
+
c4.RelationshipKind,
|
|
48
|
+
{
|
|
49
|
+
color?: c4.ThemeColor
|
|
50
|
+
line?: c4.RelationshipLineType
|
|
51
|
+
head?: c4.RelationshipArrowType
|
|
52
|
+
tail?: c4.RelationshipArrowType
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ParsedAstElement {
|
|
58
|
+
id: c4.Fqn
|
|
59
|
+
astPath: string
|
|
60
|
+
kind: c4.ElementKind
|
|
61
|
+
title: string
|
|
62
|
+
description?: string
|
|
63
|
+
technology?: string
|
|
64
|
+
tags?: c4.NonEmptyArray<c4.Tag>
|
|
65
|
+
links?: c4.NonEmptyArray<string>
|
|
66
|
+
style: ParsedElementStyle
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ParsedAstRelation {
|
|
70
|
+
id: c4.RelationID
|
|
71
|
+
astPath: string
|
|
72
|
+
source: c4.Fqn
|
|
73
|
+
target: c4.Fqn
|
|
74
|
+
kind?: c4.RelationshipKind
|
|
75
|
+
tags?: c4.NonEmptyArray<c4.Tag>
|
|
76
|
+
title: string
|
|
77
|
+
color?: c4.ThemeColor
|
|
78
|
+
line?: c4.RelationshipLineType
|
|
79
|
+
head?: c4.RelationshipArrowType
|
|
80
|
+
tail?: c4.RelationshipArrowType
|
|
81
|
+
links?: c4.NonEmptyArray<string>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ParsedAstElementView {
|
|
85
|
+
__: 'element'
|
|
86
|
+
id: c4.ViewID
|
|
87
|
+
viewOf?: c4.Fqn
|
|
88
|
+
extends?: c4.ViewID
|
|
89
|
+
astPath: string
|
|
90
|
+
title: string | null
|
|
91
|
+
description: string | null
|
|
92
|
+
tags: c4.NonEmptyArray<c4.Tag> | null
|
|
93
|
+
links: c4.NonEmptyArray<string> | null
|
|
94
|
+
rules: c4.ViewRule[]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ParsedAstDynamicView {
|
|
98
|
+
__: 'dynamic'
|
|
99
|
+
id: c4.ViewID
|
|
100
|
+
astPath: string
|
|
101
|
+
title: string | null
|
|
102
|
+
description: string | null
|
|
103
|
+
tags: c4.NonEmptyArray<c4.Tag> | null
|
|
104
|
+
links: c4.NonEmptyArray<string> | null
|
|
105
|
+
steps: c4.DynamicViewStep[]
|
|
106
|
+
rules: Array<c4.DynamicViewRule>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type ParsedAstView = ParsedAstElementView | ParsedAstDynamicView
|
|
110
|
+
export const ViewOps = {
|
|
111
|
+
writeId<T extends ast.LikeC4View>(node: T, id: c4.ViewID): T {
|
|
112
|
+
node[idattr] = id
|
|
113
|
+
return node
|
|
114
|
+
},
|
|
115
|
+
readId(node: ast.LikeC4View): c4.ViewID | undefined {
|
|
116
|
+
return node[idattr]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const ElementOps = {
|
|
121
|
+
writeId(node: ast.Element, id: c4.Fqn | null) {
|
|
122
|
+
if (isNullish(id)) {
|
|
123
|
+
node[idattr] = undefined
|
|
124
|
+
} else {
|
|
125
|
+
node[idattr] = id
|
|
126
|
+
}
|
|
127
|
+
return node
|
|
128
|
+
},
|
|
129
|
+
readId(node: ast.Element) {
|
|
130
|
+
return node[idattr]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface DocFqnIndexEntry {
|
|
135
|
+
name: string
|
|
136
|
+
el: WeakRef<ast.Element>
|
|
137
|
+
path: string
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// export type LikeC4AstNode = ast.LikeC4AstType[keyof ast.LikeC4AstType]
|
|
141
|
+
export type LikeC4AstNode = ValueOf<ConditionalPick<ast.LikeC4AstType, AstNode>>
|
|
142
|
+
type LikeC4DocumentDiagnostic = Diagnostic & DiagnosticInfo<LikeC4AstNode>
|
|
143
|
+
|
|
144
|
+
export interface LikeC4DocumentProps {
|
|
145
|
+
diagnostics?: Array<LikeC4DocumentDiagnostic>
|
|
146
|
+
c4Specification?: ParsedAstSpecification
|
|
147
|
+
c4Elements?: ParsedAstElement[]
|
|
148
|
+
c4Relations?: ParsedAstRelation[]
|
|
149
|
+
c4Views?: ParsedAstView[]
|
|
150
|
+
// Fqn -> Element
|
|
151
|
+
c4fqns?: MultiMap<c4.Fqn, DocFqnIndexEntry>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface LikeC4LangiumDocument
|
|
155
|
+
extends Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>, LikeC4DocumentProps
|
|
156
|
+
{}
|
|
157
|
+
export interface FqnIndexedDocument
|
|
158
|
+
extends Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>, SetRequired<LikeC4DocumentProps, 'c4fqns'>
|
|
159
|
+
{}
|
|
160
|
+
|
|
161
|
+
// export type ParsedLikeC4LangiumDocument = SetRequired<FqnIndexedDocument, keyof LikeC4DocumentProps>
|
|
162
|
+
export interface ParsedLikeC4LangiumDocument
|
|
163
|
+
extends Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>, Required<LikeC4DocumentProps>
|
|
164
|
+
{}
|
|
165
|
+
|
|
166
|
+
export function cleanParsedModel(doc: LikeC4LangiumDocument) {
|
|
167
|
+
const props: Required<Omit<LikeC4DocumentProps, 'c4fqns' | 'diagnostics'>> = {
|
|
168
|
+
c4Specification: {
|
|
169
|
+
kinds: {},
|
|
170
|
+
relationships: {}
|
|
171
|
+
},
|
|
172
|
+
c4Elements: [],
|
|
173
|
+
c4Relations: [],
|
|
174
|
+
c4Views: []
|
|
175
|
+
}
|
|
176
|
+
return Object.assign(doc, props) as ParsedLikeC4LangiumDocument
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function isFqnIndexedDocument(doc: LangiumDocument): doc is FqnIndexedDocument {
|
|
180
|
+
return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !!doc.c4fqns
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function isLikeC4LangiumDocument(doc: LangiumDocument): doc is LikeC4LangiumDocument {
|
|
184
|
+
return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function isParsedLikeC4LangiumDocument(
|
|
188
|
+
doc: LangiumDocument
|
|
189
|
+
): doc is ParsedLikeC4LangiumDocument {
|
|
190
|
+
return (
|
|
191
|
+
isLikeC4LangiumDocument(doc)
|
|
192
|
+
&& doc.state == DocumentState.Validated
|
|
193
|
+
&& !!doc.c4Specification
|
|
194
|
+
&& !!doc.c4Elements
|
|
195
|
+
&& !!doc.c4Relations
|
|
196
|
+
&& !!doc.c4Views
|
|
197
|
+
&& !!doc.c4fqns
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type Guard<N extends AstNode> = (n: AstNode) => n is N
|
|
202
|
+
type Guarded<G> = G extends Guard<infer N> ? N : never
|
|
203
|
+
|
|
204
|
+
function validatableAstNodeGuards<const Predicates extends Guard<AstNode>[]>(
|
|
205
|
+
predicates: Predicates
|
|
206
|
+
) {
|
|
207
|
+
return (n: AstNode): n is Guarded<Predicates[number]> => predicates.some(p => p(n))
|
|
208
|
+
}
|
|
209
|
+
const isValidatableAstNode = validatableAstNodeGuards([
|
|
210
|
+
ast.isCustomElementExprBody,
|
|
211
|
+
ast.isViewRulePredicateExpr,
|
|
212
|
+
ast.isDynamicViewRulePredicate,
|
|
213
|
+
ast.isViewProperty,
|
|
214
|
+
ast.isStyleProperty,
|
|
215
|
+
ast.isTags,
|
|
216
|
+
ast.isViewRule,
|
|
217
|
+
ast.isDynamicViewRule,
|
|
218
|
+
ast.isDynamicViewStep,
|
|
219
|
+
ast.isElementViewBody,
|
|
220
|
+
ast.isDynamicViewBody,
|
|
221
|
+
ast.isLikeC4View,
|
|
222
|
+
ast.isRelationProperty,
|
|
223
|
+
ast.isRelationBody,
|
|
224
|
+
ast.isRelation,
|
|
225
|
+
ast.isElementProperty,
|
|
226
|
+
ast.isElementBody,
|
|
227
|
+
ast.isElement,
|
|
228
|
+
ast.isExtendElementBody,
|
|
229
|
+
ast.isExtendElement,
|
|
230
|
+
ast.isSpecificationElementKind,
|
|
231
|
+
ast.isSpecificationRelationshipKind,
|
|
232
|
+
ast.isSpecificationTag,
|
|
233
|
+
ast.isSpecificationRule,
|
|
234
|
+
ast.isModelViews,
|
|
235
|
+
ast.isModel
|
|
236
|
+
])
|
|
237
|
+
type ValidatableAstNode = Guarded<typeof isValidatableAstNode>
|
|
238
|
+
|
|
239
|
+
export function checksFromDiagnostics(doc: LikeC4LangiumDocument) {
|
|
240
|
+
const errors = doc.diagnostics?.filter(d => d.severity === DiagnosticSeverity.Error) ?? []
|
|
241
|
+
const invalidNodes = new WeakSet(
|
|
242
|
+
errors.flatMap(d => {
|
|
243
|
+
return AstUtils.getContainerOfType(d.node, isValidatableAstNode) ?? []
|
|
244
|
+
}) ?? []
|
|
245
|
+
)
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
|
+
const isValid = (n: ValidatableAstNode) => !invalidNodes.has(n)
|
|
248
|
+
return {
|
|
249
|
+
isValid,
|
|
250
|
+
invalidNodes
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
export type ChecksFromDiagnostics = ReturnType<typeof checksFromDiagnostics>
|
|
254
|
+
|
|
255
|
+
export function* streamModel(doc: LikeC4LangiumDocument, isValid: ChecksFromDiagnostics['isValid']) {
|
|
256
|
+
const traverseStack = doc.parseResult.value.models.flatMap(m => (isValid(m) ? m.elements : []))
|
|
257
|
+
const relations = [] as ast.Relation[]
|
|
258
|
+
let el
|
|
259
|
+
while ((el = traverseStack.shift())) {
|
|
260
|
+
if (!isValid(el)) {
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
if (ast.isRelation(el)) {
|
|
264
|
+
relations.push(el)
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
if (ast.isExtendElement(el)) {
|
|
268
|
+
if (el.body && el.body.elements.length > 0) {
|
|
269
|
+
traverseStack.push(...el.body.elements)
|
|
270
|
+
}
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
if (el.body && el.body.elements.length > 0) {
|
|
274
|
+
for (const nested of el.body.elements) {
|
|
275
|
+
if (ast.isRelation(nested)) {
|
|
276
|
+
relations.push(nested)
|
|
277
|
+
} else {
|
|
278
|
+
traverseStack.push(nested)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
yield el
|
|
283
|
+
}
|
|
284
|
+
for (const relation of relations) {
|
|
285
|
+
yield relation
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function resolveRelationPoints(node: ast.Relation): {
|
|
290
|
+
source: ast.Element
|
|
291
|
+
target: ast.Element
|
|
292
|
+
} {
|
|
293
|
+
const target = elementRef(node.target)
|
|
294
|
+
if (!target) {
|
|
295
|
+
throw new RelationRefError('Invalid reference to target')
|
|
296
|
+
}
|
|
297
|
+
if (ast.isExplicitRelation(node)) {
|
|
298
|
+
const source = elementRef(node.source)
|
|
299
|
+
if (!source) {
|
|
300
|
+
throw new RelationRefError('Invalid reference to source')
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
source,
|
|
304
|
+
target
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
source: node.$container.$container,
|
|
309
|
+
target
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function parseAstOpacityProperty({ value }: ast.OpacityProperty): number {
|
|
314
|
+
const opacity = parseFloat(value)
|
|
315
|
+
return isNaN(opacity) ? 100 : clamp(opacity, { min: 0, max: 100 })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function toElementStyle(props?: Array<ast.StyleProperty>) {
|
|
319
|
+
const result = {} as ParsedElementStyle
|
|
320
|
+
if (!props || props.length === 0) {
|
|
321
|
+
return result
|
|
322
|
+
}
|
|
323
|
+
for (const prop of props) {
|
|
324
|
+
switch (true) {
|
|
325
|
+
case ast.isBorderProperty(prop): {
|
|
326
|
+
result.border = prop.value
|
|
327
|
+
break
|
|
328
|
+
}
|
|
329
|
+
case ast.isColorProperty(prop): {
|
|
330
|
+
result.color = prop.value
|
|
331
|
+
break
|
|
332
|
+
}
|
|
333
|
+
case ast.isShapeProperty(prop): {
|
|
334
|
+
result.shape = prop.value
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
case ast.isIconProperty(prop): {
|
|
338
|
+
result.icon = prop.value as c4.IconUrl
|
|
339
|
+
break
|
|
340
|
+
}
|
|
341
|
+
case ast.isOpacityProperty(prop): {
|
|
342
|
+
result.opacity = parseAstOpacityProperty(prop)
|
|
343
|
+
break
|
|
344
|
+
}
|
|
345
|
+
default:
|
|
346
|
+
// @ts-expect-error
|
|
347
|
+
nonexhaustive(prop.$type)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return result
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function toRelationshipStyle(props?: ast.SpecificationRelationshipKind['props']) {
|
|
354
|
+
const result = {} as {
|
|
355
|
+
color?: c4.ThemeColor
|
|
356
|
+
line?: c4.RelationshipLineType
|
|
357
|
+
head?: c4.RelationshipArrowType
|
|
358
|
+
tail?: c4.RelationshipArrowType
|
|
359
|
+
}
|
|
360
|
+
if (!props || props.length === 0) {
|
|
361
|
+
return result
|
|
362
|
+
}
|
|
363
|
+
for (const prop of props) {
|
|
364
|
+
if (ast.isColorProperty(prop)) {
|
|
365
|
+
result.color = prop.value
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
if (ast.isLineProperty(prop)) {
|
|
369
|
+
result.line = prop.value
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
if (ast.isArrowProperty(prop)) {
|
|
373
|
+
switch (prop.key) {
|
|
374
|
+
case 'head': {
|
|
375
|
+
result.head = prop.value
|
|
376
|
+
break
|
|
377
|
+
}
|
|
378
|
+
case 'tail': {
|
|
379
|
+
result.tail = prop.value
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
default: {
|
|
383
|
+
nonexhaustive(prop)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
nonexhaustive(prop)
|
|
389
|
+
}
|
|
390
|
+
return result
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function toRelationshipStyleExcludeDefaults(
|
|
394
|
+
props?: ast.SpecificationRelationshipKind['props']
|
|
395
|
+
) {
|
|
396
|
+
const { color, line, head, tail } = toRelationshipStyle(props)
|
|
397
|
+
return {
|
|
398
|
+
...(color && color !== DefaultRelationshipColor ? { color } : {}),
|
|
399
|
+
...(line && line !== DefaultLineStyle ? { line } : {}),
|
|
400
|
+
...(head && head !== DefaultArrowType ? { head } : {}),
|
|
401
|
+
...(tail ? { tail } : {})
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function toAutoLayout(
|
|
406
|
+
direction: ast.ViewLayoutDirection
|
|
407
|
+
): c4.ViewRuleAutoLayout['autoLayout'] {
|
|
408
|
+
switch (direction) {
|
|
409
|
+
case 'TopBottom': {
|
|
410
|
+
return 'TB'
|
|
411
|
+
}
|
|
412
|
+
case 'BottomTop': {
|
|
413
|
+
return 'BT'
|
|
414
|
+
}
|
|
415
|
+
case 'LeftRight': {
|
|
416
|
+
return 'LR'
|
|
417
|
+
}
|
|
418
|
+
case 'RightLeft': {
|
|
419
|
+
return 'RL'
|
|
420
|
+
}
|
|
421
|
+
default:
|
|
422
|
+
nonexhaustive(direction)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function toAstViewLayoutDirection(c4: c4.ViewRuleAutoLayout['autoLayout']): ast.ViewLayoutDirection {
|
|
427
|
+
switch (c4) {
|
|
428
|
+
case 'TB': {
|
|
429
|
+
return 'TopBottom'
|
|
430
|
+
}
|
|
431
|
+
case 'BT': {
|
|
432
|
+
return 'BottomTop'
|
|
433
|
+
}
|
|
434
|
+
case 'LR': {
|
|
435
|
+
return 'LeftRight'
|
|
436
|
+
}
|
|
437
|
+
case 'RL': {
|
|
438
|
+
return 'RightLeft'
|
|
439
|
+
}
|
|
440
|
+
default:
|
|
441
|
+
nonexhaustive(c4)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { startLanguageServer as startLanguim } from 'langium/lsp'
|
|
2
|
+
import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser'
|
|
3
|
+
import { createLanguageServices } from '../module'
|
|
4
|
+
|
|
5
|
+
// This is an example copied as is from here:
|
|
6
|
+
// https://github.com/microsoft/vscode-extension-samples/blob/main/lsp-web-extension-sample/server/src/browserServerMain.ts
|
|
7
|
+
// the only addition is the following line:
|
|
8
|
+
declare const self: DedicatedWorkerGlobalScope
|
|
9
|
+
|
|
10
|
+
export function startLanguageServer() {
|
|
11
|
+
/* browser specific setup code */
|
|
12
|
+
|
|
13
|
+
const messageReader = new BrowserMessageReader(self)
|
|
14
|
+
const messageWriter = new BrowserMessageWriter(self)
|
|
15
|
+
|
|
16
|
+
const connection = createConnection(messageReader, messageWriter)
|
|
17
|
+
|
|
18
|
+
// Inject the shared services and language-specific services
|
|
19
|
+
const services = createLanguageServices({ connection })
|
|
20
|
+
|
|
21
|
+
// Start the language server with the shared services
|
|
22
|
+
startLanguim(services.shared)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...services,
|
|
26
|
+
connection,
|
|
27
|
+
messageReader,
|
|
28
|
+
messageWriter
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type c4 } from '@likec4/core'
|
|
2
|
+
import type { ast } from './ast'
|
|
3
|
+
/**
|
|
4
|
+
* Returns referenced AST Element
|
|
5
|
+
*/
|
|
6
|
+
export function elementRef(node: ast.ElementRef | ast.FqnElementRef) {
|
|
7
|
+
return node.el.ref
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns FQN of FqnElementRef
|
|
12
|
+
* a.b.c.d - for c node returns a.b.c
|
|
13
|
+
*/
|
|
14
|
+
export function getFqnElementRef(node: ast.FqnElementRef): c4.Fqn {
|
|
15
|
+
// invariant(isElementRefHead(node), 'Expected head StrictElementRef')
|
|
16
|
+
const name = [node.el.$refText]
|
|
17
|
+
let parent = node.parent
|
|
18
|
+
while (parent) {
|
|
19
|
+
name.push(parent.el.$refText)
|
|
20
|
+
parent = parent.parent
|
|
21
|
+
}
|
|
22
|
+
if (name.length === 1) {
|
|
23
|
+
return name[0] as c4.Fqn
|
|
24
|
+
}
|
|
25
|
+
return name.reverse().join('.') as c4.Fqn
|
|
26
|
+
}
|