@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.
- 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/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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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(
|
|
7
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
84
|
+
property: 'name',
|
|
78
85
|
type: SemanticTokenTypes.type,
|
|
79
86
|
modifier: [SemanticTokenModifiers.definition]
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
if (
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
123
|
+
|
|
124
|
+
private highlightAstElement(node: ast.Element, acceptor: SemanticTokenAcceptor) {
|
|
111
125
|
acceptor({
|
|
112
126
|
node,
|
|
113
|
-
property:
|
|
127
|
+
property: 'name',
|
|
114
128
|
type: SemanticTokenTypes.variable,
|
|
115
129
|
modifier: [SemanticTokenModifiers.declaration]
|
|
116
|
-
})
|
|
130
|
+
})
|
|
117
131
|
acceptor({
|
|
118
132
|
node,
|
|
119
|
-
property:
|
|
133
|
+
property: 'kind',
|
|
120
134
|
type: SemanticTokenTypes.keyword,
|
|
121
135
|
modifier: []
|
|
122
|
-
})
|
|
136
|
+
})
|
|
123
137
|
}
|
|
124
|
-
|
|
138
|
+
|
|
139
|
+
private highlightView(node: ast.LikeC4View, acceptor: SemanticTokenAcceptor) {
|
|
125
140
|
if (node.name) {
|
|
126
141
|
acceptor({
|
|
127
142
|
node,
|
|
128
|
-
property:
|
|
143
|
+
property: 'name',
|
|
129
144
|
type: SemanticTokenTypes.variable,
|
|
130
145
|
modifier: [SemanticTokenModifiers.declaration]
|
|
131
|
-
})
|
|
146
|
+
})
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
}
|