@likec4/language-server 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -10
- package/src/Rpc.ts +108 -0
- package/src/ast.ts +443 -0
- package/src/browser/index.ts +30 -0
- package/src/elementRef.ts +26 -0
- package/src/generated/ast.ts +1632 -0
- package/src/generated/grammar.ts +10 -0
- package/src/generated/module.ts +32 -0
- package/src/index.ts +4 -0
- package/src/like-c4.langium +395 -0
- package/src/logger.ts +54 -0
- package/src/lsp/CodeLensProvider.ts +51 -0
- package/src/lsp/DocumentHighlightProvider.ts +12 -0
- package/src/lsp/DocumentLinkProvider.test.ts +66 -0
- package/src/lsp/DocumentLinkProvider.ts +53 -0
- package/src/lsp/DocumentSymbolProvider.ts +201 -0
- package/src/lsp/HoverProvider.ts +58 -0
- package/{dist/lsp/SemanticTokenProvider.js → src/lsp/SemanticTokenProvider.ts} +57 -42
- package/src/lsp/index.ts +6 -0
- package/src/model/fqn-computation.ts +47 -0
- package/src/model/fqn-index.ts +161 -0
- package/src/model/index.ts +5 -0
- package/src/model/model-builder.ts +447 -0
- package/src/model/model-locator.ts +130 -0
- package/src/model/model-parser.ts +580 -0
- package/src/model-change/ModelChanges.ts +120 -0
- package/src/model-change/changeElementStyle.ts +176 -0
- package/src/model-change/changeViewLayout.ts +41 -0
- package/src/module.ts +197 -0
- package/src/node/index.ts +20 -0
- package/src/protocol.ts +87 -0
- package/src/references/index.ts +2 -0
- package/src/references/scope-computation.ts +142 -0
- package/src/references/scope-provider.ts +166 -0
- package/src/shared/NodeKindProvider.ts +67 -0
- package/src/shared/WorkspaceManager.ts +39 -0
- package/src/shared/WorkspaceSymbolProvider.ts +3 -0
- package/src/shared/index.ts +3 -0
- package/src/test/index.ts +1 -0
- package/src/test/testServices.ts +119 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/printDocs.ts +3 -0
- package/src/utils/stringHash.ts +6 -0
- package/{dist/validation/dynamic-view-rule.js → src/validation/dynamic-view-rule.ts} +14 -11
- package/src/validation/dynamic-view-step.ts +39 -0
- package/src/validation/element.ts +52 -0
- package/{dist/validation/index.js → src/validation/index.ts} +22 -18
- package/src/validation/property-checks.ts +17 -0
- package/src/validation/relation.ts +57 -0
- package/src/validation/specification.ts +118 -0
- package/src/validation/view-predicates/custom-element-expr.ts +21 -0
- package/{dist/validation/view-predicates/expanded-element.js → src/validation/view-predicates/expanded-element.ts} +18 -13
- package/src/validation/view-predicates/incoming.ts +19 -0
- package/src/validation/view-predicates/index.ts +4 -0
- package/src/validation/view-predicates/outgoing.ts +19 -0
- package/src/validation/view.ts +26 -0
- package/src/view-utils/assignNavigateTo.ts +30 -0
- package/src/view-utils/index.ts +3 -0
- package/src/view-utils/resolve-extended-views.ts +57 -0
- package/src/view-utils/resolve-relative-paths.ts +84 -0
- package/dist/Rpc.d.ts +0 -10
- package/dist/Rpc.js +0 -98
- package/dist/ast.d.ts +0 -149
- package/dist/ast.js +0 -271
- package/dist/browser/index.d.ts +0 -9
- package/dist/browser/index.js +0 -16
- package/dist/elementRef.d.ts +0 -12
- package/dist/elementRef.js +0 -15
- package/dist/generated/ast.d.ts +0 -615
- package/dist/generated/ast.js +0 -957
- package/dist/generated/grammar.d.ts +0 -7
- package/dist/generated/grammar.js +0 -3
- package/dist/generated/module.d.ts +0 -14
- package/dist/generated/module.js +0 -22
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -2
- package/dist/logger.d.ts +0 -12
- package/dist/logger.js +0 -51
- package/dist/lsp/CodeLensProvider.d.ts +0 -10
- package/dist/lsp/CodeLensProvider.js +0 -40
- package/dist/lsp/DocumentHighlightProvider.d.ts +0 -10
- package/dist/lsp/DocumentHighlightProvider.js +0 -10
- package/dist/lsp/DocumentLinkProvider.d.ts +0 -11
- package/dist/lsp/DocumentLinkProvider.js +0 -41
- package/dist/lsp/DocumentLinkProvider.test.d.ts +0 -2
- package/dist/lsp/DocumentLinkProvider.test.js +0 -54
- package/dist/lsp/DocumentSymbolProvider.d.ts +0 -22
- package/dist/lsp/DocumentSymbolProvider.js +0 -189
- package/dist/lsp/HoverProvider.d.ts +0 -10
- package/dist/lsp/HoverProvider.js +0 -36
- package/dist/lsp/SemanticTokenProvider.d.ts +0 -8
- package/dist/lsp/index.d.ts +0 -7
- package/dist/lsp/index.js +0 -6
- package/dist/model/fqn-computation.d.ts +0 -4
- package/dist/model/fqn-computation.js +0 -43
- package/dist/model/fqn-index.d.ts +0 -26
- package/dist/model/fqn-index.js +0 -114
- package/dist/model/index.d.ts +0 -6
- package/dist/model/index.js +0 -5
- package/dist/model/model-builder.d.ts +0 -20
- package/dist/model/model-builder.js +0 -365
- package/dist/model/model-locator.d.ts +0 -22
- package/dist/model/model-locator.js +0 -115
- package/dist/model/model-parser.d.ts +0 -29
- package/dist/model/model-parser.js +0 -520
- package/dist/model-change/ModelChanges.d.ts +0 -16
- package/dist/model-change/ModelChanges.js +0 -106
- package/dist/model-change/changeElementStyle.d.ts +0 -18
- package/dist/model-change/changeElementStyle.js +0 -141
- package/dist/model-change/changeViewLayout.d.ts +0 -13
- package/dist/model-change/changeViewLayout.js +0 -29
- package/dist/module.d.ts +0 -59
- package/dist/module.js +0 -121
- package/dist/node/index.d.ts +0 -6
- package/dist/node/index.js +0 -13
- package/dist/protocol.d.ts +0 -58
- package/dist/protocol.js +0 -14
- package/dist/references/index.d.ts +0 -3
- package/dist/references/index.js +0 -2
- package/dist/references/scope-computation.d.ts +0 -11
- package/dist/references/scope-computation.js +0 -111
- package/dist/references/scope-provider.d.ts +0 -18
- package/dist/references/scope-provider.js +0 -136
- package/dist/shared/NodeKindProvider.d.ts +0 -16
- package/dist/shared/NodeKindProvider.js +0 -60
- package/dist/shared/WorkspaceManager.d.ts +0 -17
- package/dist/shared/WorkspaceManager.js +0 -29
- package/dist/shared/WorkspaceSymbolProvider.d.ts +0 -4
- package/dist/shared/WorkspaceSymbolProvider.js +0 -3
- package/dist/shared/index.d.ts +0 -4
- package/dist/shared/index.js +0 -3
- package/dist/test/index.d.ts +0 -2
- package/dist/test/index.js +0 -1
- package/dist/test/testServices.d.ts +0 -23
- package/dist/test/testServices.js +0 -102
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -1
- package/dist/utils/printDocs.d.ts +0 -3
- package/dist/utils/printDocs.js +0 -1
- package/dist/utils/stringHash.d.ts +0 -2
- package/dist/utils/stringHash.js +0 -5
- package/dist/validation/dynamic-view-rule.d.ts +0 -5
- package/dist/validation/dynamic-view-step.d.ts +0 -5
- package/dist/validation/dynamic-view-step.js +0 -33
- package/dist/validation/element.d.ts +0 -6
- package/dist/validation/element.js +0 -38
- package/dist/validation/index.d.ts +0 -3
- package/dist/validation/property-checks.d.ts +0 -5
- package/dist/validation/property-checks.js +0 -11
- package/dist/validation/relation.d.ts +0 -5
- package/dist/validation/relation.js +0 -50
- package/dist/validation/specification.d.ts +0 -10
- package/dist/validation/specification.js +0 -97
- package/dist/validation/view-predicates/custom-element-expr.d.ts +0 -5
- package/dist/validation/view-predicates/custom-element-expr.js +0 -16
- package/dist/validation/view-predicates/expanded-element.d.ts +0 -5
- package/dist/validation/view-predicates/incoming.d.ts +0 -5
- package/dist/validation/view-predicates/incoming.js +0 -14
- package/dist/validation/view-predicates/index.d.ts +0 -5
- package/dist/validation/view-predicates/index.js +0 -4
- package/dist/validation/view-predicates/outgoing.d.ts +0 -5
- package/dist/validation/view-predicates/outgoing.js +0 -14
- package/dist/validation/view.d.ts +0 -5
- package/dist/validation/view.js +0 -16
- package/dist/view-utils/assignNavigateTo.d.ts +0 -3
- package/dist/view-utils/assignNavigateTo.js +0 -24
- package/dist/view-utils/index.d.ts +0 -4
- package/dist/view-utils/index.js +0 -3
- package/dist/view-utils/resolve-extended-views.d.ts +0 -7
- package/dist/view-utils/resolve-extended-views.js +0 -41
- package/dist/view-utils/resolve-relative-paths.d.ts +0 -3
- package/dist/view-utils/resolve-relative-paths.js +0 -75
- /package/{dist → src}/reset.d.ts +0 -0
|
@@ -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
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -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
|