@likec4/language-server 1.2.0 → 1.2.1

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 (173) hide show
  1. package/package.json +8 -10
  2. package/src/Rpc.ts +108 -0
  3. package/src/ast.ts +443 -0
  4. package/src/browser/index.ts +30 -0
  5. package/src/elementRef.ts +26 -0
  6. package/src/generated/ast.ts +1632 -0
  7. package/src/generated/grammar.ts +10 -0
  8. package/src/generated/module.ts +32 -0
  9. package/src/index.ts +4 -0
  10. package/src/like-c4.langium +395 -0
  11. package/src/logger.ts +54 -0
  12. package/src/lsp/CodeLensProvider.ts +51 -0
  13. package/src/lsp/DocumentHighlightProvider.ts +12 -0
  14. package/src/lsp/DocumentLinkProvider.test.ts +66 -0
  15. package/src/lsp/DocumentLinkProvider.ts +53 -0
  16. package/src/lsp/DocumentSymbolProvider.ts +201 -0
  17. package/src/lsp/HoverProvider.ts +58 -0
  18. package/{dist/lsp/SemanticTokenProvider.js → src/lsp/SemanticTokenProvider.ts} +57 -42
  19. package/src/lsp/index.ts +6 -0
  20. package/src/model/fqn-computation.ts +47 -0
  21. package/src/model/fqn-index.ts +161 -0
  22. package/src/model/index.ts +5 -0
  23. package/src/model/model-builder.ts +447 -0
  24. package/src/model/model-locator.ts +130 -0
  25. package/src/model/model-parser.ts +580 -0
  26. package/src/model-change/ModelChanges.ts +120 -0
  27. package/src/model-change/changeElementStyle.ts +176 -0
  28. package/src/model-change/changeViewLayout.ts +41 -0
  29. package/src/module.ts +197 -0
  30. package/src/node/index.ts +20 -0
  31. package/src/protocol.ts +87 -0
  32. package/src/references/index.ts +2 -0
  33. package/src/references/scope-computation.ts +142 -0
  34. package/src/references/scope-provider.ts +166 -0
  35. package/src/shared/NodeKindProvider.ts +67 -0
  36. package/src/shared/WorkspaceManager.ts +39 -0
  37. package/src/shared/WorkspaceSymbolProvider.ts +3 -0
  38. package/src/shared/index.ts +3 -0
  39. package/src/test/index.ts +1 -0
  40. package/src/test/testServices.ts +119 -0
  41. package/src/utils/index.ts +1 -0
  42. package/src/utils/printDocs.ts +3 -0
  43. package/src/utils/stringHash.ts +6 -0
  44. package/{dist/validation/dynamic-view-rule.js → src/validation/dynamic-view-rule.ts} +14 -11
  45. package/src/validation/dynamic-view-step.ts +39 -0
  46. package/src/validation/element.ts +52 -0
  47. package/{dist/validation/index.js → src/validation/index.ts} +22 -18
  48. package/src/validation/property-checks.ts +17 -0
  49. package/src/validation/relation.ts +57 -0
  50. package/src/validation/specification.ts +118 -0
  51. package/src/validation/view-predicates/custom-element-expr.ts +21 -0
  52. package/{dist/validation/view-predicates/expanded-element.js → src/validation/view-predicates/expanded-element.ts} +18 -13
  53. package/src/validation/view-predicates/incoming.ts +19 -0
  54. package/src/validation/view-predicates/index.ts +4 -0
  55. package/src/validation/view-predicates/outgoing.ts +19 -0
  56. package/src/validation/view.ts +26 -0
  57. package/src/view-utils/assignNavigateTo.ts +30 -0
  58. package/src/view-utils/index.ts +3 -0
  59. package/src/view-utils/resolve-extended-views.ts +57 -0
  60. package/src/view-utils/resolve-relative-paths.ts +84 -0
  61. package/dist/Rpc.d.ts +0 -10
  62. package/dist/Rpc.js +0 -98
  63. package/dist/ast.d.ts +0 -149
  64. package/dist/ast.js +0 -271
  65. package/dist/browser/index.d.ts +0 -9
  66. package/dist/browser/index.js +0 -16
  67. package/dist/elementRef.d.ts +0 -12
  68. package/dist/elementRef.js +0 -15
  69. package/dist/generated/ast.d.ts +0 -615
  70. package/dist/generated/ast.js +0 -957
  71. package/dist/generated/grammar.d.ts +0 -7
  72. package/dist/generated/grammar.js +0 -3
  73. package/dist/generated/module.d.ts +0 -14
  74. package/dist/generated/module.js +0 -22
  75. package/dist/index.d.ts +0 -5
  76. package/dist/index.js +0 -2
  77. package/dist/logger.d.ts +0 -12
  78. package/dist/logger.js +0 -51
  79. package/dist/lsp/CodeLensProvider.d.ts +0 -10
  80. package/dist/lsp/CodeLensProvider.js +0 -40
  81. package/dist/lsp/DocumentHighlightProvider.d.ts +0 -10
  82. package/dist/lsp/DocumentHighlightProvider.js +0 -10
  83. package/dist/lsp/DocumentLinkProvider.d.ts +0 -11
  84. package/dist/lsp/DocumentLinkProvider.js +0 -41
  85. package/dist/lsp/DocumentLinkProvider.test.d.ts +0 -2
  86. package/dist/lsp/DocumentLinkProvider.test.js +0 -54
  87. package/dist/lsp/DocumentSymbolProvider.d.ts +0 -22
  88. package/dist/lsp/DocumentSymbolProvider.js +0 -189
  89. package/dist/lsp/HoverProvider.d.ts +0 -10
  90. package/dist/lsp/HoverProvider.js +0 -36
  91. package/dist/lsp/SemanticTokenProvider.d.ts +0 -8
  92. package/dist/lsp/index.d.ts +0 -7
  93. package/dist/lsp/index.js +0 -6
  94. package/dist/model/fqn-computation.d.ts +0 -4
  95. package/dist/model/fqn-computation.js +0 -43
  96. package/dist/model/fqn-index.d.ts +0 -26
  97. package/dist/model/fqn-index.js +0 -114
  98. package/dist/model/index.d.ts +0 -6
  99. package/dist/model/index.js +0 -5
  100. package/dist/model/model-builder.d.ts +0 -20
  101. package/dist/model/model-builder.js +0 -365
  102. package/dist/model/model-locator.d.ts +0 -22
  103. package/dist/model/model-locator.js +0 -115
  104. package/dist/model/model-parser.d.ts +0 -29
  105. package/dist/model/model-parser.js +0 -520
  106. package/dist/model-change/ModelChanges.d.ts +0 -16
  107. package/dist/model-change/ModelChanges.js +0 -106
  108. package/dist/model-change/changeElementStyle.d.ts +0 -18
  109. package/dist/model-change/changeElementStyle.js +0 -141
  110. package/dist/model-change/changeViewLayout.d.ts +0 -13
  111. package/dist/model-change/changeViewLayout.js +0 -29
  112. package/dist/module.d.ts +0 -59
  113. package/dist/module.js +0 -121
  114. package/dist/node/index.d.ts +0 -6
  115. package/dist/node/index.js +0 -13
  116. package/dist/protocol.d.ts +0 -58
  117. package/dist/protocol.js +0 -14
  118. package/dist/references/index.d.ts +0 -3
  119. package/dist/references/index.js +0 -2
  120. package/dist/references/scope-computation.d.ts +0 -11
  121. package/dist/references/scope-computation.js +0 -111
  122. package/dist/references/scope-provider.d.ts +0 -18
  123. package/dist/references/scope-provider.js +0 -136
  124. package/dist/shared/NodeKindProvider.d.ts +0 -16
  125. package/dist/shared/NodeKindProvider.js +0 -60
  126. package/dist/shared/WorkspaceManager.d.ts +0 -17
  127. package/dist/shared/WorkspaceManager.js +0 -29
  128. package/dist/shared/WorkspaceSymbolProvider.d.ts +0 -4
  129. package/dist/shared/WorkspaceSymbolProvider.js +0 -3
  130. package/dist/shared/index.d.ts +0 -4
  131. package/dist/shared/index.js +0 -3
  132. package/dist/test/index.d.ts +0 -2
  133. package/dist/test/index.js +0 -1
  134. package/dist/test/testServices.d.ts +0 -23
  135. package/dist/test/testServices.js +0 -102
  136. package/dist/utils/index.d.ts +0 -2
  137. package/dist/utils/index.js +0 -1
  138. package/dist/utils/printDocs.d.ts +0 -3
  139. package/dist/utils/printDocs.js +0 -1
  140. package/dist/utils/stringHash.d.ts +0 -2
  141. package/dist/utils/stringHash.js +0 -5
  142. package/dist/validation/dynamic-view-rule.d.ts +0 -5
  143. package/dist/validation/dynamic-view-step.d.ts +0 -5
  144. package/dist/validation/dynamic-view-step.js +0 -33
  145. package/dist/validation/element.d.ts +0 -6
  146. package/dist/validation/element.js +0 -38
  147. package/dist/validation/index.d.ts +0 -3
  148. package/dist/validation/property-checks.d.ts +0 -5
  149. package/dist/validation/property-checks.js +0 -11
  150. package/dist/validation/relation.d.ts +0 -5
  151. package/dist/validation/relation.js +0 -50
  152. package/dist/validation/specification.d.ts +0 -10
  153. package/dist/validation/specification.js +0 -97
  154. package/dist/validation/view-predicates/custom-element-expr.d.ts +0 -5
  155. package/dist/validation/view-predicates/custom-element-expr.js +0 -16
  156. package/dist/validation/view-predicates/expanded-element.d.ts +0 -5
  157. package/dist/validation/view-predicates/incoming.d.ts +0 -5
  158. package/dist/validation/view-predicates/incoming.js +0 -14
  159. package/dist/validation/view-predicates/index.d.ts +0 -5
  160. package/dist/validation/view-predicates/index.js +0 -4
  161. package/dist/validation/view-predicates/outgoing.d.ts +0 -5
  162. package/dist/validation/view-predicates/outgoing.js +0 -14
  163. package/dist/validation/view.d.ts +0 -5
  164. package/dist/validation/view.js +0 -16
  165. package/dist/view-utils/assignNavigateTo.d.ts +0 -3
  166. package/dist/view-utils/assignNavigateTo.js +0 -24
  167. package/dist/view-utils/index.d.ts +0 -4
  168. package/dist/view-utils/index.js +0 -3
  169. package/dist/view-utils/resolve-extended-views.d.ts +0 -7
  170. package/dist/view-utils/resolve-extended-views.js +0 -41
  171. package/dist/view-utils/resolve-relative-paths.d.ts +0 -3
  172. package/dist/view-utils/resolve-relative-paths.js +0 -75
  173. /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.0",
4
+ "version": "1.2.1",
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 -b",
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.0",
78
- "@likec4/graph": "1.2.0",
75
+ "@likec4/core": "1.2.1",
76
+ "@likec4/graph": "1.2.1",
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
- "@types/node": "^20.13.0",
93
+ "@likec4/tsconfig": "1.2.1",
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
- "unbuild": "^2.0.0",
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
+ }