@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
@@ -0,0 +1,120 @@
1
+ import { invariant, nonexhaustive } from '@likec4/core'
2
+ import { Location, Range, TextEdit } from 'vscode-languageserver-protocol'
3
+ import { type ParsedLikeC4LangiumDocument } from '../ast'
4
+ import type { LikeC4ModelLocator } from '../model'
5
+ import type { LikeC4Services } from '../module'
6
+ import type { ChangeViewRequestParams } from '../protocol'
7
+ import { changeElementStyle } from './changeElementStyle'
8
+ import { changeViewLayout } from './changeViewLayout'
9
+
10
+ function unionRangeOfAllEdits(ranges: Range[]): Range {
11
+ let startLine = Number.MAX_SAFE_INTEGER
12
+ let endLine = Number.MIN_SAFE_INTEGER
13
+
14
+ let startCharacter = Number.MAX_SAFE_INTEGER
15
+ let endCharacter = Number.MIN_SAFE_INTEGER
16
+
17
+ for (const { start, end } of ranges) {
18
+ if (start.line <= startLine) {
19
+ if (startLine == start.line) {
20
+ startCharacter = Math.min(start.character, startCharacter)
21
+ } else {
22
+ startLine = start.line
23
+ startCharacter = start.character
24
+ }
25
+ }
26
+ if (endLine <= end.line) {
27
+ if (endLine == end.line) {
28
+ endCharacter = Math.max(end.character, endCharacter)
29
+ } else {
30
+ endLine = end.line
31
+ endCharacter = end.character
32
+ }
33
+ }
34
+ }
35
+ return Range.create(startLine, startCharacter, endLine, endCharacter)
36
+ }
37
+
38
+ export class LikeC4ModelChanges {
39
+ private locator: LikeC4ModelLocator
40
+
41
+ constructor(private services: LikeC4Services) {
42
+ this.locator = services.likec4.ModelLocator
43
+ }
44
+
45
+ public async applyChange(changeView: ChangeViewRequestParams): Promise<Location | null> {
46
+ const lspConnection = this.services.shared.lsp.Connection
47
+ invariant(lspConnection, 'LSP Connection not available')
48
+ let result: Location | null = null
49
+ await this.services.shared.workspace.WorkspaceLock.write(async () => {
50
+ const { doc, edits } = this.convertToTextEdit(changeView)
51
+ const textDocument = {
52
+ uri: doc.textDocument.uri,
53
+ version: doc.textDocument.version
54
+ }
55
+ if (!edits.length) {
56
+ return
57
+ }
58
+ const applyResult = await lspConnection.workspace.applyEdit({
59
+ label: `LikeC4 - change view ${changeView.viewId}`,
60
+ edit: {
61
+ changes: {
62
+ [textDocument.uri]: edits
63
+ }
64
+ }
65
+ })
66
+ if (!applyResult.applied) {
67
+ lspConnection.window.showErrorMessage(`Failed to apply changes ${applyResult.failureReason}`)
68
+ return
69
+ }
70
+ result = {
71
+ uri: textDocument.uri,
72
+ range: unionRangeOfAllEdits(edits.map(edit => edit.range))
73
+ }
74
+ })
75
+ return result
76
+ }
77
+
78
+ protected convertToTextEdit({ viewId, changes }: ChangeViewRequestParams): {
79
+ doc: ParsedLikeC4LangiumDocument
80
+ ranges: Range[]
81
+ edits: TextEdit[]
82
+ } {
83
+ const lookup = this.locator.locateViewAst(viewId)
84
+ if (!lookup) {
85
+ throw new Error(`View not found: ${viewId}`)
86
+ }
87
+ const ranges = [] as Range[]
88
+ const edits = [] as TextEdit[]
89
+ for (const change of changes) {
90
+ switch (change.op) {
91
+ case 'change-element-style': {
92
+ const { edits: elementEdits, modifiedRange } = changeElementStyle(this.services, {
93
+ ...lookup,
94
+ targets: change.targets,
95
+ style: change.style
96
+ })
97
+ ranges.push(modifiedRange)
98
+ edits.push(...elementEdits)
99
+ break
100
+ }
101
+ case 'change-autolayout': {
102
+ const edit = changeViewLayout(this.services, {
103
+ ...lookup,
104
+ layout: change.layout
105
+ })
106
+ edits.push(edit)
107
+ ranges.push(edit.range)
108
+ break
109
+ }
110
+ default:
111
+ nonexhaustive(change)
112
+ }
113
+ }
114
+ return {
115
+ doc: lookup.doc,
116
+ ranges,
117
+ edits
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,176 @@
1
+ import { type Fqn, invariant, isAncestor, type NonEmptyArray, nonNullable } from '@likec4/core'
2
+ import { GrammarUtils } from 'langium'
3
+ import { entries, filter, findLast, isNumber, last, partition, toPairs } from 'remeda'
4
+ import { type Range, TextEdit } from 'vscode-languageserver-protocol'
5
+ import { ast, type ParsedAstElementView, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast'
6
+ import type { FqnIndex } from '../model'
7
+ import type { LikeC4Services } from '../module'
8
+ import type { ChangeView } from '../protocol'
9
+
10
+ const { findNodeForKeyword, findNodeForProperty } = GrammarUtils
11
+
12
+ const asViewStyleRule = (target: string, style: ChangeView.ChangeElementStyle['style'], indent = 0) => {
13
+ const indentStr = indent > 0 ? ' '.repeat(indent) : ''
14
+ return [
15
+ indentStr + `style ${target} {`,
16
+ ...entries.strict(style).map(([key, value]) =>
17
+ indentStr + ` ${key} ${key === 'opacity' ? value.toString() + '%' : value}`
18
+ ),
19
+ indentStr + `}`
20
+ ]
21
+ }
22
+
23
+ type ChangeElementStyleArg = {
24
+ view: ParsedAstView
25
+ doc: ParsedLikeC4LangiumDocument
26
+ viewAst: ast.LikeC4View
27
+ targets: NonEmptyArray<Fqn>
28
+ style: ChangeView.ChangeElementStyle['style']
29
+ }
30
+
31
+ /**
32
+ * - is ViewRuleStyle
33
+ * - has exactly one target
34
+ * - the target is an ElementRef to the given fqn
35
+ */
36
+ const isMatchingViewRule =
37
+ (fqn: string, index: FqnIndex) => (rule: ast.ViewRule | ast.DynamicViewRule): rule is ast.ViewRuleStyle => {
38
+ if (!ast.isViewRuleStyle(rule)) {
39
+ return false
40
+ }
41
+ const [target, ...rest] = rule.targets
42
+ if (!target || rest.length > 0 || !ast.isElementRef(target)) {
43
+ return false
44
+ }
45
+ const ref = target.el.ref
46
+ const _fqn = ref ? index.getFqn(ref) : null
47
+ return _fqn === fqn
48
+ }
49
+
50
+ export function changeElementStyle(services: LikeC4Services, {
51
+ view,
52
+ viewAst,
53
+ targets,
54
+ style
55
+ }: ChangeElementStyleArg): {
56
+ modifiedRange: Range
57
+ edits: TextEdit[]
58
+ } {
59
+ const viewCstNode = viewAst.$cstNode
60
+ invariant(viewCstNode, 'viewCstNode')
61
+ const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end
62
+ ?? viewAst.body.$cstNode?.range.end
63
+ invariant(insertPos, 'insertPos is not defined')
64
+ const indent = viewCstNode.range.start.character + 2
65
+ const fqnIndex = services.likec4.FqnIndex
66
+ const styleRules = filter(viewAst.body.rules, ast.isViewRuleStyle)
67
+ const viewOf = view.__ === 'element' ? view.viewOf : null
68
+ // Find existing rules
69
+ const existing = [] as Array<{ fqn: Fqn; rule: ast.ViewRuleStyle }>
70
+ const insert = [] as Array<{ fqn: Fqn }>
71
+ // const existingRules = [] as Array<{ fqn: Fqn, rule: ast.ViewRuleStyle }>
72
+ targets.forEach(target => {
73
+ const rule = findLast(styleRules, isMatchingViewRule(target, fqnIndex))
74
+ // remove viewOf from the target to shorten the fqn
75
+ const fqn = (viewOf && isAncestor(viewOf, target) ? target.substring(viewOf.length + 1) : target) as Fqn
76
+ if (rule) {
77
+ existing.push({ fqn, rule })
78
+ } else {
79
+ insert.push({ fqn })
80
+ }
81
+ })
82
+
83
+ const modifiedRange = {
84
+ start: insertPos,
85
+ end: insertPos
86
+ }
87
+
88
+ const includeRange = (range: Range) => {
89
+ if (range.start.line <= modifiedRange.start.line) {
90
+ if (range.start.line == modifiedRange.start.line) {
91
+ modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character)
92
+ } else {
93
+ modifiedRange.start = range.start
94
+ }
95
+ }
96
+ if (range.end.line >= modifiedRange.end.line) {
97
+ if (range.end.line == modifiedRange.end.line) {
98
+ modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character)
99
+ } else {
100
+ modifiedRange.end = range.end
101
+ }
102
+ }
103
+ }
104
+
105
+ const edits = [] as TextEdit[]
106
+
107
+ if (insert.length > 0) {
108
+ const linesToInsert = insert.flatMap(({ fqn }) => asViewStyleRule(fqn, style, indent))
109
+ edits.push(
110
+ TextEdit.insert(
111
+ insertPos,
112
+ '\n' + linesToInsert.join('\n')
113
+ )
114
+ )
115
+ modifiedRange.start = {
116
+ line: insertPos.line + 1,
117
+ character: indent + 1
118
+ }
119
+ modifiedRange.end = {
120
+ line: insertPos.line + linesToInsert.length,
121
+ character: (last(linesToInsert)?.length ?? 0)
122
+ }
123
+ }
124
+
125
+ if (existing.length > 0) {
126
+ for (const { rule } of existing) {
127
+ const ruleCstNode = rule.$cstNode
128
+ invariant(ruleCstNode, 'RuleCstNode not found')
129
+ for (const [key, _value] of entries.strict(style)) {
130
+ const value = key === 'opacity' ? _value.toString() + '%' : _value
131
+ const ruleProp = rule.styleprops.find(p => p.key === key)
132
+ // replace existing property
133
+ if (ruleProp && ruleProp.$cstNode) {
134
+ const { range: { start, end } } = nonNullable(
135
+ findNodeForProperty(ruleProp.$cstNode, 'value'),
136
+ 'cant find value cst node'
137
+ )
138
+ includeRange({
139
+ start,
140
+ end: {
141
+ line: start.line,
142
+ character: start.character + value.length
143
+ }
144
+ })
145
+ edits.push(TextEdit.replace({ start, end }, value))
146
+ continue
147
+ }
148
+ // insert new style property right after the opening brace
149
+ const insertPos = findNodeForKeyword(ruleCstNode, '{')?.range.end
150
+ invariant(insertPos, 'Opening brace not found')
151
+ const indentStr = ' '.repeat(2 + ruleCstNode.range.start.character)
152
+ const insertKeyValue = indentStr + key + ' ' + value
153
+ edits.push(
154
+ TextEdit.insert(
155
+ insertPos,
156
+ '\n' + insertKeyValue
157
+ )
158
+ )
159
+ includeRange({
160
+ start: {
161
+ line: insertPos.line + 1,
162
+ character: indentStr.length
163
+ },
164
+ end: {
165
+ line: insertPos.line + 1,
166
+ character: insertKeyValue.length
167
+ }
168
+ })
169
+ }
170
+ }
171
+ }
172
+ return {
173
+ modifiedRange,
174
+ edits
175
+ }
176
+ }
@@ -0,0 +1,41 @@
1
+ import { type AutoLayoutDirection, invariant } from '@likec4/core'
2
+ import { GrammarUtils } from 'langium'
3
+ import { last } from 'remeda'
4
+ import { TextEdit } from 'vscode-languageserver-protocol'
5
+ import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument, toAstViewLayoutDirection } from '../ast'
6
+ import type { LikeC4Services } from '../module'
7
+
8
+ const { findNodeForProperty } = GrammarUtils
9
+
10
+ type ChangeViewLayoutArg = {
11
+ view: ParsedAstView
12
+ doc: ParsedLikeC4LangiumDocument
13
+ viewAst: ast.LikeC4View
14
+ layout: AutoLayoutDirection
15
+ }
16
+
17
+ export function changeViewLayout(_services: LikeC4Services, {
18
+ viewAst,
19
+ layout
20
+ }: ChangeViewLayoutArg): TextEdit {
21
+ const viewCstNode = viewAst.$cstNode
22
+ invariant(viewCstNode, 'viewCstNode')
23
+ const newlayout = toAstViewLayoutDirection(layout)
24
+ const existingRule = viewAst.body.rules.findLast(ast.isViewRuleAutoLayout) as ast.ViewRuleAutoLayout | undefined
25
+
26
+ if (existingRule && existingRule.$cstNode) {
27
+ const directionCstNode = findNodeForProperty(existingRule.$cstNode, 'direction')
28
+ if (directionCstNode) {
29
+ return TextEdit.replace(directionCstNode.range, newlayout)
30
+ }
31
+ return TextEdit.replace(existingRule.$cstNode.range, `autoLayout ${newlayout}`)
32
+ }
33
+
34
+ const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end
35
+ ?? viewAst.body.$cstNode?.range.end
36
+ invariant(insertPos, 'insertPos is not defined')
37
+ const indent = ' '.repeat(2 + viewCstNode.range.start.character)
38
+ const insert = `\n\n${indent}autoLayout ${newlayout}`
39
+
40
+ return TextEdit.insert(insertPos, insert)
41
+ }
package/src/module.ts ADDED
@@ -0,0 +1,197 @@
1
+ import { normalizeError } from '@likec4/core'
2
+ import { EmptyFileSystem, inject, type Module, WorkspaceCache } from 'langium'
3
+ import {
4
+ createDefaultModule,
5
+ createDefaultSharedModule,
6
+ type DefaultSharedModuleContext,
7
+ type LangiumServices,
8
+ type LangiumSharedServices,
9
+ type PartialLangiumServices,
10
+ type PartialLangiumSharedServices
11
+ } from 'langium/lsp'
12
+ import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from './generated/module'
13
+ import { logger } from './logger'
14
+ import {
15
+ LikeC4CodeLensProvider,
16
+ LikeC4DocumentHighlightProvider,
17
+ LikeC4DocumentLinkProvider,
18
+ LikeC4DocumentSymbolProvider,
19
+ LikeC4HoverProvider,
20
+ LikeC4SemanticTokenProvider
21
+ } from './lsp'
22
+ import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model'
23
+ import { LikeC4ModelChanges } from './model-change/ModelChanges'
24
+ import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references'
25
+ import { Rpc } from './Rpc'
26
+ import { LikeC4WorkspaceManager, NodeKindProvider, WorkspaceSymbolProvider } from './shared'
27
+ import { registerValidationChecks } from './validation'
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ type Constructor<T, Arguments extends unknown[] = any[]> = new(...arguments_: Arguments) => T
31
+
32
+ interface LikeC4AddedSharedServices {
33
+ lsp: {
34
+ NodeKindProvider: NodeKindProvider
35
+ WorkspaceSymbolProvider: WorkspaceSymbolProvider
36
+ }
37
+ workspace: {
38
+ WorkspaceManager: LikeC4WorkspaceManager
39
+ }
40
+ }
41
+
42
+ export type LikeC4SharedServices = LangiumSharedServices & LikeC4AddedSharedServices
43
+
44
+ const LikeC4SharedModule: Module<
45
+ LikeC4SharedServices,
46
+ PartialLangiumSharedServices & LikeC4AddedSharedServices
47
+ > = {
48
+ lsp: {
49
+ NodeKindProvider: services => new NodeKindProvider(services),
50
+ WorkspaceSymbolProvider: services => new WorkspaceSymbolProvider(services)
51
+ },
52
+ workspace: {
53
+ WorkspaceManager: services => new LikeC4WorkspaceManager(services)
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Declaration of custom services - add your own service classes here.
59
+ */
60
+ export interface LikeC4AddedServices {
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ WorkspaceCache: WorkspaceCache<string, any>
63
+ Rpc: Rpc
64
+ likec4: {
65
+ FqnIndex: FqnIndex
66
+ ModelParser: LikeC4ModelParser
67
+ ModelBuilder: LikeC4ModelBuilder
68
+ ModelLocator: LikeC4ModelLocator
69
+ ModelChanges: LikeC4ModelChanges
70
+ }
71
+ lsp: {
72
+ DocumentHighlightProvider: LikeC4DocumentHighlightProvider
73
+ DocumentSymbolProvider: LikeC4DocumentSymbolProvider
74
+ SemanticTokenProvider: LikeC4SemanticTokenProvider
75
+ HoverProvider: LikeC4HoverProvider
76
+ CodeLensProvider: LikeC4CodeLensProvider
77
+ DocumentLinkProvider: LikeC4DocumentLinkProvider
78
+ }
79
+ references: {
80
+ ScopeComputation: LikeC4ScopeComputation
81
+ ScopeProvider: LikeC4ScopeProvider
82
+ }
83
+ shared?: LikeC4SharedServices
84
+ }
85
+
86
+ export type LikeC4Services = LangiumServices & LikeC4AddedServices
87
+
88
+ function bind<T>(Type: Constructor<T, [LikeC4Services]>) {
89
+ return (services: LikeC4Services) => new Type(services)
90
+ }
91
+
92
+ export const LikeC4Module: Module<LikeC4Services, PartialLangiumServices & LikeC4AddedServices> = {
93
+ WorkspaceCache: (services: LikeC4Services) => new WorkspaceCache(services.shared),
94
+ Rpc: bind(Rpc),
95
+ likec4: {
96
+ ModelChanges: bind(LikeC4ModelChanges),
97
+ FqnIndex: bind(FqnIndex),
98
+ ModelParser: bind(LikeC4ModelParser),
99
+ ModelBuilder: bind(LikeC4ModelBuilder),
100
+ ModelLocator: bind(LikeC4ModelLocator)
101
+ },
102
+ lsp: {
103
+ DocumentHighlightProvider: bind(LikeC4DocumentHighlightProvider),
104
+ DocumentSymbolProvider: bind(LikeC4DocumentSymbolProvider),
105
+ SemanticTokenProvider: bind(LikeC4SemanticTokenProvider),
106
+ HoverProvider: bind(LikeC4HoverProvider),
107
+ CodeLensProvider: bind(LikeC4CodeLensProvider),
108
+ DocumentLinkProvider: bind(LikeC4DocumentLinkProvider)
109
+ },
110
+ references: {
111
+ ScopeComputation: bind(LikeC4ScopeComputation),
112
+ ScopeProvider: bind(LikeC4ScopeProvider)
113
+ }
114
+ }
115
+
116
+ export type LanguageServicesContext = Partial<DefaultSharedModuleContext>
117
+
118
+ export function createCustomLanguageServices<I1, I2, I3, I extends I1 & I2 & I3 & LikeC4Services>(
119
+ context: LanguageServicesContext,
120
+ module: Module<I, I1>,
121
+ module2?: Module<I, I2>,
122
+ module3?: Module<I, I3>
123
+ ): { shared: LikeC4SharedServices; likec4: I } {
124
+ const shared = createSharedServices(context)
125
+ const modules = [
126
+ createDefaultModule({ shared }),
127
+ LikeC4GeneratedModule,
128
+ LikeC4Module,
129
+ module,
130
+ module2,
131
+ module3
132
+ ].reduce(_merge, {}) as Module<I>
133
+
134
+ const likec4 = inject(modules)
135
+ shared.ServiceRegistry.register(likec4)
136
+ registerValidationChecks(likec4)
137
+ likec4.Rpc.init()
138
+ return { shared, likec4 }
139
+ }
140
+
141
+ export function createSharedServices(context: LanguageServicesContext = {}): LikeC4SharedServices {
142
+ const connection = context.connection
143
+ if (connection) {
144
+ // eslint-disable-next-line @typescript-eslint/unbound-method
145
+ const original = logger.error.bind(logger)
146
+ logger.error = (arg: unknown) => {
147
+ if (typeof arg === 'string') {
148
+ original(arg)
149
+ connection.telemetry.logEvent({ eventName: 'error', error: arg })
150
+ return
151
+ }
152
+ const error = normalizeError(arg)
153
+ original(error)
154
+ connection.telemetry.logEvent({ eventName: 'error', error: error.stack ?? error.message })
155
+ }
156
+ }
157
+
158
+ const moduleContext: DefaultSharedModuleContext = {
159
+ ...EmptyFileSystem,
160
+ ...context
161
+ }
162
+
163
+ return inject(
164
+ createDefaultSharedModule(moduleContext),
165
+ LikeC4GeneratedSharedModule,
166
+ LikeC4SharedModule
167
+ )
168
+ }
169
+
170
+ export function createLanguageServices(context: LanguageServicesContext = {}): {
171
+ shared: LikeC4SharedServices
172
+ likec4: LikeC4Services
173
+ } {
174
+ const shared = createSharedServices(context)
175
+ const likec4 = inject(createDefaultModule({ shared }), LikeC4GeneratedModule, LikeC4Module)
176
+ shared.ServiceRegistry.register(likec4)
177
+ registerValidationChecks(likec4)
178
+ likec4.Rpc.init()
179
+ return { shared, likec4 }
180
+ }
181
+
182
+ // Copied from langium/src/dependency-injection.ts as it is not exported
183
+ function _merge(target: Module<any>, source?: Module<any>): Module<unknown> {
184
+ if (source) {
185
+ for (const [key, value2] of Object.entries(source)) {
186
+ if (value2 !== undefined) {
187
+ const value1 = target[key]
188
+ if (value1 !== null && value2 !== null && typeof value1 === 'object' && typeof value2 === 'object') {
189
+ target[key] = _merge(value1, value2)
190
+ } else {
191
+ target[key] = value2
192
+ }
193
+ }
194
+ }
195
+ }
196
+ return target
197
+ }
@@ -0,0 +1,20 @@
1
+ import { startLanguageServer as startLanguim } from 'langium/lsp'
2
+ import { NodeFileSystem } from 'langium/node'
3
+ import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'
4
+ import { createLanguageServices } from '../module'
5
+
6
+ export function startLanguageServer() {
7
+ /* browser specific setup code */
8
+ const connection = createConnection(ProposedFeatures.all)
9
+
10
+ // Inject the shared services and language-specific services
11
+ const services = createLanguageServices({ connection, ...NodeFileSystem })
12
+
13
+ // Start the language server with the shared services
14
+ startLanguim(services.shared)
15
+
16
+ return {
17
+ ...services,
18
+ connection
19
+ }
20
+ }
@@ -0,0 +1,87 @@
1
+ import type {
2
+ AutoLayoutDirection,
3
+ BorderStyle,
4
+ ComputedView,
5
+ ElementShape,
6
+ Fqn,
7
+ LikeC4ComputedModel,
8
+ LikeC4Model,
9
+ NonEmptyArray,
10
+ RelationID,
11
+ ThemeColor,
12
+ ViewID
13
+ } from '@likec4/core'
14
+ import type { DocumentUri, Location } from 'vscode-languageserver-protocol'
15
+ import { NotificationType, RequestType, RequestType0 } from 'vscode-languageserver-protocol'
16
+
17
+ // #region From server
18
+ export const onDidChangeModel = new NotificationType<string>('likec4/onDidChangeModel')
19
+ export type OnDidChangeModelNotification = typeof onDidChangeModel
20
+ // #endregion
21
+
22
+ // #region To server
23
+ export const fetchModel = new RequestType0<{ model: LikeC4Model | null }, void>(
24
+ 'likec4/fetchModel'
25
+ )
26
+ export type FetchModelRequest = typeof fetchModel
27
+
28
+ export const fetchComputedModel = new RequestType0<{ model: LikeC4ComputedModel | null }, void>(
29
+ 'likec4/fetchComputedModel'
30
+ )
31
+ export type FetchComputedModelRequest = typeof fetchComputedModel
32
+
33
+ export const computeView = new RequestType<{ viewId: ViewID }, { view: ComputedView | null }, void>(
34
+ 'likec4/computeView'
35
+ )
36
+ export type ComputeViewRequest = typeof computeView
37
+
38
+ export interface BuildDocumentsParams {
39
+ docs: DocumentUri[]
40
+ }
41
+ export const buildDocuments = new RequestType<BuildDocumentsParams, void, void>('likec4/build')
42
+ export type BuildDocumentsRequest = typeof buildDocuments
43
+
44
+ export type LocateParams =
45
+ | {
46
+ element: Fqn
47
+ property?: string
48
+ }
49
+ | {
50
+ relation: RelationID
51
+ }
52
+ | {
53
+ view: ViewID
54
+ }
55
+ export const locate = new RequestType<LocateParams, Location | null, void>('likec4/locate')
56
+ export type LocateRequest = typeof locate
57
+ // #endregion
58
+
59
+ export namespace ChangeView {
60
+
61
+ export interface ChangeAutoLayout {
62
+ op: 'change-autolayout'
63
+ layout: AutoLayoutDirection
64
+ }
65
+
66
+ export interface ChangeElementStyle {
67
+ op: 'change-element-style'
68
+ style: {
69
+ border?: BorderStyle
70
+ opacity?: number
71
+ shape?: ElementShape
72
+ color?: ThemeColor
73
+ }
74
+ targets: NonEmptyArray<Fqn>
75
+ }
76
+ }
77
+
78
+ export type ChangeView =
79
+ | ChangeView.ChangeAutoLayout
80
+ | ChangeView.ChangeElementStyle
81
+
82
+ export interface ChangeViewRequestParams {
83
+ viewId: ViewID
84
+ changes: NonEmptyArray<ChangeView>
85
+ }
86
+ export const changeView = new RequestType<ChangeViewRequestParams, Location | null, void>('likec4/change-view')
87
+ export type ChangeViewRequest = typeof changeView
@@ -0,0 +1,2 @@
1
+ export * from './scope-computation'
2
+ export * from './scope-provider'