@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.
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/src/logger.ts ADDED
@@ -0,0 +1,54 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { normalizeError } from '@likec4/core'
3
+
4
+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */
5
+ let isSilent = false
6
+
7
+ export const logger = {
8
+ trace(message: string) {
9
+ if (isSilent) return
10
+ console.trace(message)
11
+ },
12
+ debug(message: string) {
13
+ if (isSilent) return
14
+ console.debug(message)
15
+ },
16
+ info(message: string) {
17
+ if (isSilent) return
18
+ console.info(message)
19
+ },
20
+ warn(message: string) {
21
+ if (isSilent) return
22
+ console.warn(message)
23
+ },
24
+ error(message: any) {
25
+ if (isSilent) return
26
+ if (typeof message === 'string') {
27
+ console.error(message)
28
+ return
29
+ }
30
+ console.error(normalizeError(message))
31
+ },
32
+ silent(silent = true) {
33
+ isSilent = silent
34
+ }
35
+ }
36
+
37
+ export type Logger = typeof logger
38
+
39
+ export function logError(error: unknown): void {
40
+ logger.error(error)
41
+ }
42
+
43
+ export function logWarnError(err: unknown): void {
44
+ if (typeof err === 'string') {
45
+ logger.warn(err)
46
+ return
47
+ }
48
+ if (err instanceof Error) {
49
+ logger.warn(err.stack ?? err.message)
50
+ return
51
+ }
52
+ const error = normalizeError(err)
53
+ logger.warn(`${error.name}: ${error.message}`)
54
+ }
@@ -0,0 +1,51 @@
1
+ import { DocumentState, type LangiumDocument, type MaybePromise } from 'langium'
2
+ import type { CodeLensProvider } from 'langium/lsp'
3
+ import type { CancellationToken, CodeLens, CodeLensParams } from 'vscode-languageserver'
4
+ import { isParsedLikeC4LangiumDocument, ViewOps } from '../ast'
5
+ import { logger } from '../logger'
6
+ import type { LikeC4Services } from '../module'
7
+
8
+ export class LikeC4CodeLensProvider implements CodeLensProvider {
9
+ constructor(private services: LikeC4Services) {
10
+ //
11
+ }
12
+
13
+ async provideCodeLens(
14
+ doc: LangiumDocument,
15
+ _params: CodeLensParams,
16
+ cancelToken?: CancellationToken
17
+ ): Promise<CodeLens[] | undefined> {
18
+ if (doc.state !== DocumentState.Validated) {
19
+ logger.debug(`Waiting for document ${doc.uri.path} to be validated`)
20
+ await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Validated, doc.uri, cancelToken)
21
+ logger.debug(`Document ${doc.uri.path} is validated`)
22
+ }
23
+ if (!isParsedLikeC4LangiumDocument(doc)) {
24
+ return
25
+ }
26
+ const views = doc.parseResult.value.views.flatMap(v => v.views)
27
+ return views.flatMap<CodeLens>(ast => {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ const viewId = ViewOps.readId(ast)
30
+ const range = ast.$cstNode?.range
31
+ if (!range || !viewId) {
32
+ return []
33
+ }
34
+
35
+ return {
36
+ range: {
37
+ start: range.start,
38
+ end: {
39
+ line: range.start.line,
40
+ character: range.start.character + 4
41
+ }
42
+ },
43
+ command: {
44
+ command: 'likec4.open-preview',
45
+ arguments: [viewId],
46
+ title: 'open preview'
47
+ }
48
+ }
49
+ })
50
+ }
51
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReferenceDescription } from 'langium'
2
+ import { DefaultDocumentHighlightProvider } from 'langium/lsp'
3
+ import { DocumentHighlight, DocumentHighlightKind } from 'vscode-languageserver'
4
+
5
+ export class LikeC4DocumentHighlightProvider extends DefaultDocumentHighlightProvider {
6
+ /**
7
+ * Override this method to determine the highlight kind of the given reference.
8
+ */
9
+ protected override createDocumentHighlight(reference: ReferenceDescription): DocumentHighlight {
10
+ return DocumentHighlight.create(reference.segment.range, DocumentHighlightKind.Read)
11
+ }
12
+ }
@@ -0,0 +1,66 @@
1
+ import { beforeAll, describe, expect, it, vi } from 'vitest'
2
+ import type { LikeC4LangiumDocument } from '../ast'
3
+ import type { LikeC4Services } from '../module'
4
+ import { createTestServices } from '../test'
5
+ import type { LikeC4DocumentLinkProvider } from './DocumentLinkProvider'
6
+
7
+ vi.mock('../logger')
8
+
9
+ describe('DocumentLinkProvider', () => {
10
+ let services: LikeC4Services
11
+ let doc: LikeC4LangiumDocument
12
+ let documentLinkProvider: LikeC4DocumentLinkProvider
13
+
14
+ beforeAll(async () => {
15
+ const test = createTestServices('vscode-vfs://host/virtual')
16
+ services = test.services
17
+ documentLinkProvider = services.lsp.DocumentLinkProvider
18
+ doc = await test.parse(
19
+ `
20
+ specification {
21
+ element component
22
+ }
23
+ `,
24
+ 'dir1/doc.c4'
25
+ )
26
+ })
27
+
28
+ it('test should have correct doc uri and workspace uri', () => {
29
+ expect(services.shared.workspace.WorkspaceManager.workspaceUri.toString()).toBe(
30
+ 'vscode-vfs://host/virtual'
31
+ )
32
+ expect(services.shared.workspace.WorkspaceManager.workspaceURL.toString()).toBe(
33
+ 'vscode-vfs://host/virtual'
34
+ )
35
+ expect(doc.uri.toString()).toBe('vscode-vfs://host/virtual/src/dir1/doc.c4')
36
+ })
37
+
38
+ it('should return the link unchanged if it has a protocol', () => {
39
+ const link = 'http://example.com/link'
40
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(link)
41
+ })
42
+
43
+ it('should resolve a relative link against the document URI', () => {
44
+ const link = './relative/link#fragment'
45
+ const expected = 'vscode-vfs://host/virtual/src/dir1/relative/link#fragment'
46
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected)
47
+ })
48
+
49
+ it('should resolve a parent relative link against the document URI', () => {
50
+ const link = '../dir2/link?query=1#L1=22'
51
+ const expected = 'vscode-vfs://host/virtual/src/dir2/link?query=1#L1=22'
52
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected)
53
+ })
54
+
55
+ it('should resolve a link against the workspace URL', () => {
56
+ const link = '/root'
57
+ const expected = 'vscode-vfs://host/virtual/root'
58
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected)
59
+ })
60
+
61
+ it('should resolve a link with quary and hash against the workspace URL', () => {
62
+ const link = '/root/a/b/c/link?query=1#L1=22'
63
+ const expected = 'vscode-vfs://host/virtual/root/a/b/c/link?query=1#L1=22'
64
+ expect(documentLinkProvider.resolveLink(doc, link)).toBe(expected)
65
+ })
66
+ })
@@ -0,0 +1,53 @@
1
+ import type { LangiumDocument, MaybePromise } from 'langium'
2
+ import { AstUtils, GrammarUtils } from 'langium'
3
+ import type { DocumentLinkProvider } from 'langium/lsp'
4
+ import { hasProtocol, isRelative, withBase } from 'ufo'
5
+ import type { DocumentLink, DocumentLinkParams } from 'vscode-languageserver-protocol'
6
+ import { ast, isParsedLikeC4LangiumDocument } from '../ast'
7
+ import { logError } from '../logger'
8
+ import type { LikeC4Services } from '../module'
9
+
10
+ export class LikeC4DocumentLinkProvider implements DocumentLinkProvider {
11
+ constructor(private services: LikeC4Services) {
12
+ //
13
+ }
14
+ getDocumentLinks(
15
+ doc: LangiumDocument,
16
+ _params: DocumentLinkParams
17
+ ): MaybePromise<DocumentLink[]> {
18
+ if (!isParsedLikeC4LangiumDocument(doc)) {
19
+ return []
20
+ }
21
+ return AstUtils.streamAllContents(doc.parseResult.value)
22
+ .filter(ast.isLinkProperty)
23
+ .flatMap((n): DocumentLink | Iterable<DocumentLink> => {
24
+ try {
25
+ const range = GrammarUtils.findNodeForProperty(n.$cstNode, 'value')?.range
26
+ if (!range) {
27
+ return []
28
+ }
29
+ const target = this.resolveLink(doc, n.value)
30
+ return {
31
+ range,
32
+ target
33
+ }
34
+ } catch (e) {
35
+ logError(e)
36
+ return []
37
+ }
38
+ })
39
+ .toArray()
40
+ }
41
+
42
+ resolveLink(doc: LangiumDocument, link: string): string {
43
+ if (hasProtocol(link)) {
44
+ return link
45
+ }
46
+ if (isRelative(link)) {
47
+ const base = new URL(doc.uri.toString(true))
48
+ return new URL(link, base).toString()
49
+ }
50
+ const workspace = this.services.shared.workspace.WorkspaceManager.workspaceURL
51
+ return withBase(link, workspace.toString())
52
+ }
53
+ }
@@ -0,0 +1,201 @@
1
+ import { nonexhaustive } from '@likec4/core'
2
+ import { type AstNode, GrammarUtils, type MaybePromise } from 'langium'
3
+ import type { DocumentSymbolProvider, NodeKindProvider } from 'langium/lsp'
4
+ import { filter, isEmpty, isTruthy, map, pipe } from 'remeda'
5
+ import { type DocumentSymbol, SymbolKind } from 'vscode-languageserver-protocol'
6
+ import { ast, type LikeC4LangiumDocument } from '../ast'
7
+ import { getFqnElementRef } from '../elementRef'
8
+ import { logError } from '../logger'
9
+ import type { LikeC4Services } from '../module'
10
+
11
+ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
12
+ protected readonly nodeKindProvider: NodeKindProvider
13
+
14
+ constructor(private services: LikeC4Services) {
15
+ this.nodeKindProvider = services.shared.lsp.NodeKindProvider
16
+ }
17
+
18
+ getSymbols({
19
+ parseResult: {
20
+ value: { specifications, models, views }
21
+ }
22
+ }: LikeC4LangiumDocument): MaybePromise<DocumentSymbol[]> {
23
+ return [
24
+ ...specifications.map(s => () => this.getSpecSymbol(s)),
25
+ ...models.map(s => () => this.getModelSymbol(s)),
26
+ ...views.map(s => () => this.getModelViewsSymbol(s))
27
+ ].flatMap(fn => {
28
+ try {
29
+ return fn() ?? []
30
+ } catch (e) {
31
+ logError(e)
32
+ return []
33
+ }
34
+ })
35
+ }
36
+
37
+ protected getSpecSymbol(astSpec: ast.SpecificationRule): DocumentSymbol[] {
38
+ const cstModel = astSpec?.$cstNode
39
+ if (!cstModel) return []
40
+ const specKeywordNode = GrammarUtils.findNodeForProperty(cstModel, 'name')
41
+ if (!specKeywordNode) return []
42
+
43
+ const specSymbols = pipe(
44
+ [...astSpec.elements, ...astSpec.tags, ...astSpec.relationships],
45
+ map(nd => {
46
+ try {
47
+ if (ast.isSpecificationElementKind(nd) || ast.isSpecificationRelationshipKind(nd)) {
48
+ return this.getKindSymbol(nd)
49
+ }
50
+ if (ast.isSpecificationTag(nd)) {
51
+ return this.getTagSymbol(nd)
52
+ }
53
+ } catch (e) {
54
+ logError(e)
55
+ return null
56
+ }
57
+ nonexhaustive(nd)
58
+ }),
59
+ filter(isTruthy)
60
+ )
61
+
62
+ if (specSymbols.length === 0) return []
63
+
64
+ return [
65
+ {
66
+ kind: SymbolKind.Namespace,
67
+ name: astSpec.name,
68
+ range: cstModel.range,
69
+ selectionRange: specKeywordNode.range,
70
+ children: specSymbols
71
+ }
72
+ ]
73
+ }
74
+
75
+ protected getModelSymbol(astModel: ast.Model): DocumentSymbol[] {
76
+ const cstModel = astModel.$cstNode
77
+ if (!cstModel) return []
78
+ const nameNode = GrammarUtils.findNodeForProperty(cstModel, 'name')
79
+ if (!nameNode) return []
80
+ return [
81
+ {
82
+ kind: this.symbolKind(astModel),
83
+ name: astModel.name,
84
+ range: cstModel.range,
85
+ selectionRange: nameNode.range,
86
+ children: astModel.elements.flatMap(e => this.getElementsSymbol(e))
87
+ }
88
+ ]
89
+ }
90
+
91
+ protected getElementsSymbol(
92
+ el: ast.Element | ast.Relation | ast.ExtendElement
93
+ ): DocumentSymbol[] {
94
+ try {
95
+ if (ast.isExtendElement(el)) {
96
+ return this.getExtendElementSymbol(el)
97
+ }
98
+ if (ast.isElement(el)) {
99
+ return this.getElementSymbol(el)
100
+ }
101
+ } catch (e) {
102
+ logError(e)
103
+ }
104
+ return []
105
+ }
106
+
107
+ protected getExtendElementSymbol(astElement: ast.ExtendElement): DocumentSymbol[] {
108
+ const cst = astElement.$cstNode
109
+ const nameNode = astElement.element.$cstNode
110
+ const body = astElement.body
111
+ if (!cst || !nameNode) return []
112
+
113
+ return [
114
+ {
115
+ kind: this.symbolKind(astElement),
116
+ name: getFqnElementRef(astElement.element),
117
+ range: cst.range,
118
+ selectionRange: nameNode.range,
119
+ children: body.elements.flatMap(e => this.getElementsSymbol(e))
120
+ }
121
+ ]
122
+ }
123
+
124
+ protected getElementSymbol(astElement: ast.Element): DocumentSymbol[] {
125
+ const cst = astElement.$cstNode
126
+ const nameNode = GrammarUtils.findNodeForProperty(cst, 'name')
127
+ if (!nameNode || !cst) return []
128
+
129
+ const name = astElement.name
130
+ const kind = astElement.kind.$refText
131
+ // TODO: return the title as well
132
+ const detail = kind // + (astElement.title ? ': ' + astElement.title : '').replaceAll('\n', ' ').trim()
133
+ return [
134
+ {
135
+ kind: this.symbolKind(astElement),
136
+ name: name,
137
+ range: cst.range,
138
+ selectionRange: nameNode.range,
139
+ detail,
140
+ children: astElement.body?.elements.flatMap(e => this.getElementsSymbol(e)) ?? []
141
+ }
142
+ ]
143
+ }
144
+ protected getModelViewsSymbol(astViews: ast.ModelViews): DocumentSymbol[] {
145
+ const cst = astViews.$cstNode
146
+ const nameNode = GrammarUtils.findNodeForProperty(cst, 'name')
147
+ if (!nameNode || !cst) return []
148
+ return [
149
+ {
150
+ kind: this.symbolKind(astViews),
151
+ name: astViews.name,
152
+ range: cst.range,
153
+ selectionRange: nameNode.range,
154
+ children: astViews.views.flatMap(e => this.getViewSymbol(e))
155
+ }
156
+ ]
157
+ }
158
+
159
+ protected getKindSymbol(
160
+ astKind: ast.SpecificationElementKind | ast.SpecificationRelationshipKind
161
+ ): DocumentSymbol | null {
162
+ if (!astKind.$cstNode || !astKind.kind.$cstNode || isEmpty(astKind.kind.name)) return null
163
+
164
+ return {
165
+ kind: this.symbolKind(astKind),
166
+ name: astKind.kind.name,
167
+ range: astKind.$cstNode.range,
168
+ selectionRange: astKind.kind.$cstNode.range
169
+ }
170
+ }
171
+
172
+ protected getTagSymbol(astTag: ast.SpecificationTag): DocumentSymbol | null {
173
+ if (!astTag.$cstNode || !astTag.tag.$cstNode || isEmpty(astTag.tag.name)) return null
174
+ return {
175
+ kind: this.symbolKind(astTag),
176
+ name: '#' + astTag.tag.name,
177
+ range: astTag.$cstNode.range,
178
+ selectionRange: astTag.tag.$cstNode.range
179
+ }
180
+ }
181
+
182
+ protected getViewSymbol(astView: ast.LikeC4View): DocumentSymbol[] {
183
+ const cst = astView?.$cstNode
184
+ if (!cst) return []
185
+ const nameNode = astView.name ? GrammarUtils.findNodeForProperty(cst, 'name') : null
186
+ if (!nameNode) return []
187
+ return [
188
+ {
189
+ kind: this.symbolKind(astView),
190
+ name: nameNode.text,
191
+ range: cst.range,
192
+ selectionRange: nameNode.range,
193
+ children: []
194
+ }
195
+ ]
196
+ }
197
+
198
+ protected symbolKind(node: AstNode): SymbolKind {
199
+ return this.nodeKindProvider.getSymbolKind(node)
200
+ }
201
+ }
@@ -0,0 +1,58 @@
1
+ import { type AstNode, type MaybePromise } from 'langium'
2
+ import { AstNodeHoverProvider } from 'langium/lsp'
3
+ import { isTruthy } from 'remeda'
4
+ import stripIndent from 'strip-indent'
5
+ import type { Hover } from 'vscode-languageserver-protocol'
6
+ import { ast } from '../ast'
7
+ import type { LikeC4ModelLocator } from '../model'
8
+ import type { LikeC4Services } from '../module'
9
+
10
+ export class LikeC4HoverProvider extends AstNodeHoverProvider {
11
+ private locator: LikeC4ModelLocator
12
+
13
+ constructor(services: LikeC4Services) {
14
+ super(services)
15
+ this.locator = services.likec4.ModelLocator
16
+ }
17
+
18
+ protected getAstNodeHoverContent(node: AstNode): MaybePromise<Hover | undefined> {
19
+ if (ast.isTag(node)) {
20
+ return {
21
+ contents: {
22
+ kind: 'markdown',
23
+ value: stripIndent(`
24
+ tag: \`${node.name}\`
25
+ `)
26
+ }
27
+ }
28
+ }
29
+
30
+ // if (ast.isElementKind(node)) {
31
+ // const spec = this.specIndex.get(node.name as ElementKind)
32
+ // return {
33
+ // contents: {
34
+ // kind: 'markdown',
35
+ // value: stripIndent(`
36
+ // kind: **${spec.id}**
37
+ // shape: ${spec.style.shape}
38
+ // `)
39
+ // }
40
+ // }
41
+ // }
42
+
43
+ if (ast.isElement(node)) {
44
+ const el = this.locator.getParsedElement(node)
45
+ if (!el) {
46
+ return
47
+ }
48
+ const lines = [el.id, `### ${el.title}`, '`' + el.kind + '` ']
49
+ return {
50
+ contents: {
51
+ kind: 'markdown',
52
+ value: lines.join('\n')
53
+ }
54
+ }
55
+ }
56
+ return
57
+ }
58
+ }
@@ -1,134 +1,149 @@
1
- import { AbstractSemanticTokenProvider } from "langium/lsp";
2
- import { SemanticTokenModifiers, SemanticTokenTypes } from "vscode-languageserver-protocol";
3
- import { ast } from "../ast.js";
1
+ import type { AstNode } from 'langium'
2
+ import { AbstractSemanticTokenProvider, type SemanticTokenAcceptor } from 'langium/lsp'
3
+ import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-protocol'
4
+ import { ast } from '../ast'
5
+
4
6
  export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
5
7
  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
6
- highlightElement(node, acceptor) {
7
- if (ast.isRelation(node) && "kind" in node) {
8
+ protected override highlightElement(
9
+ node: AstNode,
10
+ acceptor: SemanticTokenAcceptor
11
+ ): void | undefined | 'prune' {
12
+ if (ast.isRelation(node) && 'kind' in node) {
8
13
  acceptor({
9
14
  node,
10
- property: "kind",
15
+ property: 'kind',
11
16
  type: SemanticTokenTypes.type,
12
17
  modifier: [SemanticTokenModifiers.definition]
13
- });
18
+ })
14
19
  }
20
+
15
21
  if (ast.isElementViewRef(node)) {
16
22
  acceptor({
17
23
  node,
18
- property: "view",
24
+ property: 'view',
19
25
  type: SemanticTokenTypes.variable
20
- });
26
+ })
21
27
  }
22
28
  if (ast.isDescedantsExpr(node) && node.$cstNode) {
23
29
  acceptor({
24
30
  cst: node.$cstNode,
25
31
  type: SemanticTokenTypes.variable
26
- });
27
- return "prune";
32
+ })
33
+ return 'prune'
28
34
  }
29
35
  if (ast.isWildcardExpr(node) && node.$cstNode) {
30
36
  acceptor({
31
37
  cst: node.$cstNode,
32
38
  type: SemanticTokenTypes.variable
33
- });
39
+ })
34
40
  }
41
+
35
42
  if (ast.isElementKindExpr(node)) {
36
43
  acceptor({
37
44
  node,
38
- property: "kind",
45
+ property: 'kind',
39
46
  type: SemanticTokenTypes.type,
40
47
  modifier: [SemanticTokenModifiers.definition]
41
- });
48
+ })
42
49
  }
43
50
  if (ast.isElementTagExpr(node)) {
44
51
  acceptor({
45
52
  node,
46
- property: "tag",
53
+ property: 'tag',
47
54
  type: SemanticTokenTypes.type,
48
55
  modifier: [SemanticTokenModifiers.definition]
49
- });
56
+ })
50
57
  }
51
58
  if (ast.isElementRef(node) || ast.isFqnElementRef(node)) {
52
59
  acceptor({
53
60
  node,
54
- property: "el",
61
+ property: 'el',
55
62
  type: node.parent ? SemanticTokenTypes.property : SemanticTokenTypes.variable
56
- });
63
+ })
57
64
  }
58
65
  if (ast.isSpecificationElementKind(node) || ast.isSpecificationRelationshipKind(node)) {
59
66
  acceptor({
60
67
  node,
61
- property: "kind",
68
+ property: 'kind',
62
69
  type: SemanticTokenTypes.type,
63
70
  modifier: [SemanticTokenModifiers.definition]
64
- });
71
+ })
65
72
  }
66
73
  if (ast.isTags(node)) {
67
74
  acceptor({
68
75
  node,
69
- property: "value",
76
+ property: 'value',
70
77
  type: SemanticTokenTypes.type,
71
78
  modifier: [SemanticTokenModifiers.definition]
72
- });
79
+ })
73
80
  }
74
81
  if (ast.isTag(node)) {
75
82
  acceptor({
76
83
  node,
77
- property: "name",
84
+ property: 'name',
78
85
  type: SemanticTokenTypes.type,
79
86
  modifier: [SemanticTokenModifiers.definition]
80
- });
81
- }
82
- if (ast.isColorProperty(node) || ast.isShapeProperty(node) || ast.isArrowProperty(node) || ast.isLineProperty(node) || ast.isBorderProperty(node)) {
87
+ })
88
+ }
89
+ if (
90
+ ast.isColorProperty(node)
91
+ || ast.isShapeProperty(node)
92
+ || ast.isArrowProperty(node)
93
+ || ast.isLineProperty(node)
94
+ || ast.isBorderProperty(node)
95
+ ) {
83
96
  acceptor({
84
97
  node,
85
- property: "value",
98
+ property: 'value',
86
99
  type: SemanticTokenTypes.enum
87
- });
100
+ })
88
101
  }
89
102
  if (ast.isOpacityProperty(node)) {
90
103
  acceptor({
91
104
  node,
92
- property: "value",
105
+ property: 'value',
93
106
  type: SemanticTokenTypes.number
94
- });
107
+ })
95
108
  }
96
109
  if (ast.isLinkProperty(node) || ast.isIconProperty(node)) {
97
110
  acceptor({
98
111
  node,
99
- property: "value",
112
+ property: 'value',
100
113
  type: SemanticTokenTypes.string
101
- });
114
+ })
102
115
  }
103
116
  if (ast.isElement(node)) {
104
- this.highlightAstElement(node, acceptor);
117
+ this.highlightAstElement(node, acceptor)
105
118
  }
106
119
  if (ast.isLikeC4View(node)) {
107
- this.highlightView(node, acceptor);
120
+ this.highlightView(node, acceptor)
108
121
  }
109
122
  }
110
- highlightAstElement(node, acceptor) {
123
+
124
+ private highlightAstElement(node: ast.Element, acceptor: SemanticTokenAcceptor) {
111
125
  acceptor({
112
126
  node,
113
- property: "name",
127
+ property: 'name',
114
128
  type: SemanticTokenTypes.variable,
115
129
  modifier: [SemanticTokenModifiers.declaration]
116
- });
130
+ })
117
131
  acceptor({
118
132
  node,
119
- property: "kind",
133
+ property: 'kind',
120
134
  type: SemanticTokenTypes.keyword,
121
135
  modifier: []
122
- });
136
+ })
123
137
  }
124
- highlightView(node, acceptor) {
138
+
139
+ private highlightView(node: ast.LikeC4View, acceptor: SemanticTokenAcceptor) {
125
140
  if (node.name) {
126
141
  acceptor({
127
142
  node,
128
- property: "name",
143
+ property: 'name',
129
144
  type: SemanticTokenTypes.variable,
130
145
  modifier: [SemanticTokenModifiers.declaration]
131
- });
146
+ })
132
147
  }
133
148
  }
134
149
  }