@likec4/language-server 1.7.1 → 1.7.3
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 +7 -7
- package/src/model/model-parser-where.ts +1 -1
- package/src/model/model-parser.ts +30 -15
- package/src/model-change/ModelChanges.ts +29 -24
- package/src/model-change/changeElementStyle.ts +7 -13
- package/src/model-change/changeViewLayout.ts +1 -1
- package/src/model-change/saveManualLayout.ts +2 -2
- package/src/model-graph/compute-view/__test__/fixture.ts +61 -41
- package/src/model-graph/compute-view/compute.ts +29 -23
- package/src/model-graph/dynamic-view/compute.ts +23 -8
- package/src/model-graph/utils/applyCustomElementProperties.ts +2 -3
- package/src/model-graph/utils/applyCustomRelationProperties.ts +3 -19
- package/src/protocol.ts +2 -13
- package/src/references/scope-provider.ts +4 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/language-server",
|
|
3
3
|
"description": "LikeC4 Language Server",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
"test:watch": "vitest"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@dagrejs/graphlib": "^2.2.
|
|
95
|
-
"@likec4/core": "1.7.
|
|
96
|
-
"@likec4/log": "1.7.
|
|
94
|
+
"@dagrejs/graphlib": "^2.2.3",
|
|
95
|
+
"@likec4/core": "1.7.3",
|
|
96
|
+
"@likec4/log": "1.7.3",
|
|
97
97
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
98
98
|
"@smithy/util-base64": "^3.0.0",
|
|
99
99
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
"vscode-uri": "3.0.8"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
|
-
"@likec4/icons": "1.7.
|
|
118
|
-
"@likec4/tsconfig": "1.7.
|
|
119
|
-
"@types/node": "^20.14.
|
|
117
|
+
"@likec4/icons": "1.7.3",
|
|
118
|
+
"@likec4/tsconfig": "1.7.3",
|
|
119
|
+
"@types/node": "^20.14.14",
|
|
120
120
|
"@types/object-hash": "^3.0.6",
|
|
121
121
|
"@types/string-hash": "^1.1.3",
|
|
122
122
|
"execa": "^9.3.0",
|
|
@@ -19,7 +19,7 @@ const parseEquals = (
|
|
|
19
19
|
return not ? { neq: value } : { eq: value }
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export function parseWhereClause(astNode: ast.WhereExpression): c4.WhereOperator {
|
|
22
|
+
export function parseWhereClause(astNode: ast.WhereExpression): c4.WhereOperator<string, string> {
|
|
23
23
|
switch (true) {
|
|
24
24
|
case ast.isWhereTagEqual(astNode): {
|
|
25
25
|
const tag = astNode.value?.ref?.name
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type c4, InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
2
|
import type { AstNode, LangiumDocument } from 'langium'
|
|
3
3
|
import { AstUtils, CstUtils } from 'langium'
|
|
4
|
-
import { isTruthy, mapToObj } from 'remeda'
|
|
4
|
+
import { isDefined, isTruthy, mapToObj } from 'remeda'
|
|
5
5
|
import stripIndent from 'strip-indent'
|
|
6
6
|
import type { Writable } from 'type-fest'
|
|
7
7
|
import type {
|
|
@@ -183,7 +183,7 @@ export class LikeC4ModelParser {
|
|
|
183
183
|
const source = this.resolveFqn(coupling.source)
|
|
184
184
|
const tags = this.convertTags(astNode) ?? this.convertTags(astNode.body)
|
|
185
185
|
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
|
|
186
|
-
const kind = astNode.kind?.ref?.name as c4.RelationshipKind
|
|
186
|
+
const kind = astNode.kind?.ref?.name as (c4.RelationshipKind | undefined)
|
|
187
187
|
const astPath = this.getAstNodePath(astNode)
|
|
188
188
|
|
|
189
189
|
const bodyProps = mapToObj(
|
|
@@ -354,31 +354,41 @@ export class LikeC4ModelParser {
|
|
|
354
354
|
return acc
|
|
355
355
|
}
|
|
356
356
|
if (ast.isElementStringProperty(prop)) {
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
if (isDefined(prop.value)) {
|
|
358
|
+
let value = prop.key === 'description' ? removeIndent(prop.value) : toSingleLine(prop.value)
|
|
359
|
+
acc.custom[prop.key] = value || ''
|
|
360
|
+
}
|
|
359
361
|
return acc
|
|
360
362
|
}
|
|
361
363
|
if (ast.isIconProperty(prop)) {
|
|
362
364
|
const value = prop.libicon?.ref?.name ?? prop.value
|
|
363
|
-
if (
|
|
365
|
+
if (isDefined(value)) {
|
|
364
366
|
acc.custom[prop.key] = value as c4.IconUrl
|
|
365
367
|
}
|
|
366
368
|
return acc
|
|
367
369
|
}
|
|
368
370
|
if (ast.isColorProperty(prop)) {
|
|
369
|
-
|
|
371
|
+
if (isDefined(prop.value)) {
|
|
372
|
+
acc.custom[prop.key] = prop.value
|
|
373
|
+
}
|
|
370
374
|
return acc
|
|
371
375
|
}
|
|
372
376
|
if (ast.isShapeProperty(prop)) {
|
|
373
|
-
|
|
377
|
+
if (isDefined(prop.value)) {
|
|
378
|
+
acc.custom[prop.key] = prop.value
|
|
379
|
+
}
|
|
374
380
|
return acc
|
|
375
381
|
}
|
|
376
382
|
if (ast.isBorderProperty(prop)) {
|
|
377
|
-
|
|
383
|
+
if (isDefined(prop.value)) {
|
|
384
|
+
acc.custom[prop.key] = prop.value
|
|
385
|
+
}
|
|
378
386
|
return acc
|
|
379
387
|
}
|
|
380
388
|
if (ast.isOpacityProperty(prop)) {
|
|
381
|
-
|
|
389
|
+
if (isDefined(prop.value)) {
|
|
390
|
+
acc.custom[prop.key] = parseAstOpacityProperty(prop)
|
|
391
|
+
}
|
|
382
392
|
return acc
|
|
383
393
|
}
|
|
384
394
|
|
|
@@ -442,22 +452,27 @@ export class LikeC4ModelParser {
|
|
|
442
452
|
return props.reduce(
|
|
443
453
|
(acc, prop) => {
|
|
444
454
|
if (ast.isRelationStringProperty(prop)) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
acc.customRelation[prop.key] = value
|
|
455
|
+
if (isDefined(prop.value)) {
|
|
456
|
+
acc.customRelation[prop.key] = removeIndent(prop.value) ?? ''
|
|
448
457
|
}
|
|
449
458
|
return acc
|
|
450
459
|
}
|
|
451
460
|
if (ast.isArrowProperty(prop)) {
|
|
452
|
-
|
|
461
|
+
if (isTruthy(prop.value)) {
|
|
462
|
+
acc.customRelation[prop.key] = prop.value
|
|
463
|
+
}
|
|
453
464
|
return acc
|
|
454
465
|
}
|
|
455
466
|
if (ast.isColorProperty(prop)) {
|
|
456
|
-
|
|
467
|
+
if (isTruthy(prop.value)) {
|
|
468
|
+
acc.customRelation[prop.key] = prop.value
|
|
469
|
+
}
|
|
457
470
|
return acc
|
|
458
471
|
}
|
|
459
472
|
if (ast.isLineProperty(prop)) {
|
|
460
|
-
|
|
473
|
+
if (isTruthy(prop.value)) {
|
|
474
|
+
acc.customRelation[prop.key] = prop.value
|
|
475
|
+
}
|
|
461
476
|
return acc
|
|
462
477
|
}
|
|
463
478
|
nonexhaustive(prop)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { invariant, nonexhaustive } from '@likec4/core'
|
|
2
|
+
import { logger } from '@likec4/log'
|
|
2
3
|
import { Location, Range, TextEdit } from 'vscode-languageserver-types'
|
|
3
4
|
import { type ParsedLikeC4LangiumDocument } from '../ast'
|
|
4
5
|
import type { LikeC4ModelLocator } from '../model'
|
|
@@ -47,32 +48,36 @@ export class LikeC4ModelChanges {
|
|
|
47
48
|
const lspConnection = this.services.shared.lsp.Connection
|
|
48
49
|
invariant(lspConnection, 'LSP Connection not available')
|
|
49
50
|
let result: Location | null = null
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
try {
|
|
52
|
+
await this.services.shared.workspace.WorkspaceLock.write(async () => {
|
|
53
|
+
const { doc, edits, modifiedRange } = this.convertToTextEdit(changeView)
|
|
54
|
+
const textDocument = {
|
|
55
|
+
uri: doc.textDocument.uri,
|
|
56
|
+
version: doc.textDocument.version
|
|
57
|
+
}
|
|
58
|
+
if (!edits.length) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
const applyResult = await lspConnection.workspace.applyEdit({
|
|
62
|
+
label: `LikeC4 - change view ${changeView.viewId}`,
|
|
63
|
+
edit: {
|
|
64
|
+
changes: {
|
|
65
|
+
[textDocument.uri]: edits
|
|
66
|
+
}
|
|
64
67
|
}
|
|
68
|
+
})
|
|
69
|
+
if (!applyResult.applied) {
|
|
70
|
+
lspConnection.window.showErrorMessage(`Failed to apply changes ${applyResult.failureReason}`)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
result = {
|
|
74
|
+
uri: textDocument.uri,
|
|
75
|
+
range: modifiedRange
|
|
65
76
|
}
|
|
66
77
|
})
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
result = {
|
|
72
|
-
uri: textDocument.uri,
|
|
73
|
-
range: modifiedRange
|
|
74
|
-
}
|
|
75
|
-
})
|
|
78
|
+
} catch (e) {
|
|
79
|
+
logger.error(`Failed to apply change ${changeView.change.op} ${changeView.viewId}`, e)
|
|
80
|
+
}
|
|
76
81
|
return result
|
|
77
82
|
}
|
|
78
83
|
|
|
@@ -83,7 +88,7 @@ export class LikeC4ModelChanges {
|
|
|
83
88
|
} {
|
|
84
89
|
const lookup = this.locator.locateViewAst(viewId)
|
|
85
90
|
if (!lookup) {
|
|
86
|
-
throw new Error(`
|
|
91
|
+
throw new Error(`LikeC4ModelChanges: view not found: ${viewId}`)
|
|
87
92
|
}
|
|
88
93
|
switch (change.op) {
|
|
89
94
|
case 'change-element-style': {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Fqn, invariant, isAncestor, type NonEmptyArray, nonNullable, type
|
|
1
|
+
import { type Fqn, invariant, isAncestor, type NonEmptyArray, nonNullable, type ViewChange } from '@likec4/core'
|
|
2
2
|
import { GrammarUtils } from 'langium'
|
|
3
3
|
import { entries, filter, findLast, isTruthy, last } from 'remeda'
|
|
4
4
|
import { type Range, TextEdit } from 'vscode-languageserver-types'
|
|
@@ -8,7 +8,7 @@ import type { LikeC4Services } from '../module'
|
|
|
8
8
|
|
|
9
9
|
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils
|
|
10
10
|
|
|
11
|
-
const asViewStyleRule = (target: string, style:
|
|
11
|
+
const asViewStyleRule = (target: string, style: ViewChange.ChangeElementStyle['style'], indent = 0) => {
|
|
12
12
|
const indentStr = indent > 0 ? ' '.repeat(indent) : ''
|
|
13
13
|
return [
|
|
14
14
|
indentStr + `style ${target} {`,
|
|
@@ -24,7 +24,7 @@ type ChangeElementStyleArg = {
|
|
|
24
24
|
doc: ParsedLikeC4LangiumDocument
|
|
25
25
|
viewAst: ast.LikeC4View
|
|
26
26
|
targets: NonEmptyArray<Fqn>
|
|
27
|
-
style:
|
|
27
|
+
style: ViewChange.ChangeElementStyle['style']
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -116,7 +116,7 @@ export function changeElementStyle(services: LikeC4Services, {
|
|
|
116
116
|
)
|
|
117
117
|
modifiedRange.start = {
|
|
118
118
|
line: insertPos.line + 1,
|
|
119
|
-
character: indent
|
|
119
|
+
character: indent
|
|
120
120
|
}
|
|
121
121
|
modifiedRange.end = {
|
|
122
122
|
line: insertPos.line + linesToInsert.length,
|
|
@@ -133,18 +133,12 @@ export function changeElementStyle(services: LikeC4Services, {
|
|
|
133
133
|
const ruleProp = rule.props.find(p => p.key === key)
|
|
134
134
|
// replace existing property
|
|
135
135
|
if (ruleProp && ruleProp.$cstNode) {
|
|
136
|
-
const { range: { start, end } } =
|
|
137
|
-
findNodeForProperty(ruleProp.$cstNode, 'value'),
|
|
138
|
-
'cant find value cst node'
|
|
139
|
-
)
|
|
136
|
+
const { range: { start, end } } = ruleProp.$cstNode
|
|
140
137
|
includeRange({
|
|
141
138
|
start,
|
|
142
|
-
end
|
|
143
|
-
line: start.line,
|
|
144
|
-
character: start.character + value.length
|
|
145
|
-
}
|
|
139
|
+
end
|
|
146
140
|
})
|
|
147
|
-
edits.push(TextEdit.replace({ start, end }, value))
|
|
141
|
+
edits.push(TextEdit.replace({ start, end }, key + ' ' + value))
|
|
148
142
|
continue
|
|
149
143
|
}
|
|
150
144
|
// insert new style property right after the opening brace
|
|
@@ -35,7 +35,7 @@ export function changeViewLayout(_services: LikeC4Services, {
|
|
|
35
35
|
|
|
36
36
|
const insertPos = findNodeForKeyword(viewAst.body.$cstNode, '}')?.range.start
|
|
37
37
|
invariant(insertPos, 'Closing brace not found')
|
|
38
|
-
const insert =
|
|
38
|
+
const insert = ` autoLayout ${newlayout}\n` + ' '.repeat(insertPos.character)
|
|
39
39
|
|
|
40
40
|
return TextEdit.insert(insertPos, insert)
|
|
41
41
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { invariant, type
|
|
1
|
+
import { invariant, type ViewChange } from '@likec4/core'
|
|
2
2
|
import indentString from 'indent-string'
|
|
3
3
|
import { CstUtils, GrammarUtils } from 'langium'
|
|
4
4
|
import { TextEdit } from 'vscode-languageserver-types'
|
|
@@ -12,7 +12,7 @@ export type ManualLayoutArg = {
|
|
|
12
12
|
view: ParsedAstView
|
|
13
13
|
doc: ParsedLikeC4LangiumDocument
|
|
14
14
|
viewAst: ast.LikeC4View
|
|
15
|
-
layout:
|
|
15
|
+
layout: ViewChange.SaveManualLayout['layout']
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function saveManualLayout(_services: LikeC4Services, {
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
BorderStyle,
|
|
3
|
-
ComputedView,
|
|
4
|
-
CustomElementExpr as C4CustomElementExpr,
|
|
5
|
-
CustomRelationExpr as C4CustomRelationExpr,
|
|
6
|
-
Element,
|
|
7
|
-
ElementExpression as C4ElementExpression,
|
|
8
|
-
ElementKind,
|
|
9
|
-
ElementShape,
|
|
10
|
-
ElementWhereExpr,
|
|
11
|
-
Expression as C4Expression,
|
|
12
|
-
Fqn,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
import {
|
|
2
|
+
type BorderStyle,
|
|
3
|
+
type ComputedView,
|
|
4
|
+
type CustomElementExpr as C4CustomElementExpr,
|
|
5
|
+
type CustomRelationExpr as C4CustomRelationExpr,
|
|
6
|
+
type Element,
|
|
7
|
+
type ElementExpression as C4ElementExpression,
|
|
8
|
+
type ElementKind,
|
|
9
|
+
type ElementShape,
|
|
10
|
+
type ElementWhereExpr,
|
|
11
|
+
type Expression as C4Expression,
|
|
12
|
+
type Fqn,
|
|
13
|
+
type NonEmptyArray,
|
|
14
|
+
type Relation,
|
|
15
|
+
type RelationID,
|
|
16
|
+
type RelationshipArrowType,
|
|
17
|
+
type RelationshipLineType,
|
|
18
|
+
type RelationWhereExpr,
|
|
19
|
+
type Tag,
|
|
20
|
+
type ThemeColor,
|
|
21
|
+
type ViewID,
|
|
22
|
+
type ViewRule,
|
|
23
|
+
type ViewRulePredicate,
|
|
24
|
+
type ViewRuleStyle,
|
|
25
|
+
type WhereOperator
|
|
25
26
|
} from '@likec4/core'
|
|
26
27
|
import { indexBy, isString, map, prop } from 'remeda'
|
|
27
28
|
import { LikeC4ModelGraph } from '../../LikeC4ModelGraph'
|
|
@@ -107,19 +108,26 @@ model {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
*/
|
|
111
|
+
type TestTag = 'old' | 'next' | 'aws' | 'storage' | 'communication' | 'legacy'
|
|
112
|
+
|
|
110
113
|
const el = ({
|
|
111
114
|
id,
|
|
112
115
|
kind,
|
|
113
116
|
title,
|
|
114
117
|
style,
|
|
118
|
+
tags,
|
|
115
119
|
...props
|
|
116
|
-
}: Partial<Omit<Element, 'id' | 'kind'>> & {
|
|
120
|
+
}: Partial<Omit<Element, 'id' | 'kind' | 'tags'>> & {
|
|
121
|
+
id: string
|
|
122
|
+
kind: string
|
|
123
|
+
tags?: NonEmptyArray<TestTag>
|
|
124
|
+
}): Element => ({
|
|
117
125
|
id: id as Fqn,
|
|
118
126
|
kind: kind as ElementKind,
|
|
119
127
|
title: title ?? id,
|
|
120
128
|
description: null,
|
|
121
129
|
technology: null,
|
|
122
|
-
tags: null,
|
|
130
|
+
tags: tags as NonEmptyArray<Tag> ?? null,
|
|
123
131
|
links: null,
|
|
124
132
|
style: {
|
|
125
133
|
...style
|
|
@@ -144,7 +152,7 @@ export const fakeElements = {
|
|
|
144
152
|
id: 'cloud',
|
|
145
153
|
kind: 'system',
|
|
146
154
|
title: 'cloud',
|
|
147
|
-
tags: ['next'
|
|
155
|
+
tags: ['next', 'old']
|
|
148
156
|
}),
|
|
149
157
|
'cloud.backend': el({
|
|
150
158
|
id: 'cloud.backend',
|
|
@@ -171,32 +179,32 @@ export const fakeElements = {
|
|
|
171
179
|
id: 'cloud.backend.storage',
|
|
172
180
|
kind: 'component',
|
|
173
181
|
title: 'storage',
|
|
174
|
-
tags: ['old'
|
|
182
|
+
tags: ['storage', 'old']
|
|
175
183
|
}),
|
|
176
184
|
'cloud.frontend.adminPanel': el({
|
|
177
185
|
id: 'cloud.frontend.adminPanel',
|
|
178
186
|
kind: 'component',
|
|
179
187
|
title: 'adminPanel',
|
|
180
|
-
tags: ['old'
|
|
188
|
+
tags: ['old']
|
|
181
189
|
}),
|
|
182
190
|
'cloud.frontend.dashboard': el({
|
|
183
191
|
id: 'cloud.frontend.dashboard',
|
|
184
192
|
kind: 'component',
|
|
185
193
|
title: 'dashboard',
|
|
186
|
-
tags: ['next'
|
|
194
|
+
tags: ['next']
|
|
187
195
|
}),
|
|
188
196
|
'amazon': el({
|
|
189
197
|
id: 'amazon',
|
|
190
198
|
kind: 'system',
|
|
191
199
|
title: 'amazon',
|
|
192
|
-
tags: ['aws'
|
|
200
|
+
tags: ['aws']
|
|
193
201
|
}),
|
|
194
202
|
'amazon.s3': el({
|
|
195
203
|
id: 'amazon.s3',
|
|
196
204
|
kind: 'component',
|
|
197
205
|
title: 's3',
|
|
198
206
|
shape: 'storage',
|
|
199
|
-
tags: ['aws'
|
|
207
|
+
tags: ['aws', 'storage']
|
|
200
208
|
})
|
|
201
209
|
} satisfies Record<string, Element>
|
|
202
210
|
|
|
@@ -216,6 +224,7 @@ const rel = ({
|
|
|
216
224
|
line?: RelationshipLineType
|
|
217
225
|
head?: RelationshipArrowType
|
|
218
226
|
tail?: RelationshipArrowType
|
|
227
|
+
tags?: NonEmptyArray<TestTag>
|
|
219
228
|
}): Relation => ({
|
|
220
229
|
id: `${source}:${target}` as RelationID,
|
|
221
230
|
title: title ?? '',
|
|
@@ -238,7 +247,8 @@ export const fakeRelations = [
|
|
|
238
247
|
rel({
|
|
239
248
|
source: 'cloud.backend.storage',
|
|
240
249
|
target: 'amazon.s3',
|
|
241
|
-
title: 'uploads'
|
|
250
|
+
title: 'uploads',
|
|
251
|
+
tags: ['aws', 'storage', 'legacy']
|
|
242
252
|
}),
|
|
243
253
|
rel({
|
|
244
254
|
source: 'customer',
|
|
@@ -248,7 +258,8 @@ export const fakeRelations = [
|
|
|
248
258
|
rel({
|
|
249
259
|
source: 'cloud.backend.graphql',
|
|
250
260
|
target: 'cloud.backend.storage',
|
|
251
|
-
title: 'stores'
|
|
261
|
+
title: 'stores',
|
|
262
|
+
tags: ['old', 'storage']
|
|
252
263
|
}),
|
|
253
264
|
// rel({
|
|
254
265
|
// source: 'cloud.backend',
|
|
@@ -270,7 +281,8 @@ export const fakeRelations = [
|
|
|
270
281
|
target: 'cloud.backend.graphql',
|
|
271
282
|
kind: 'graphlql',
|
|
272
283
|
title: 'requests',
|
|
273
|
-
line: 'solid'
|
|
284
|
+
line: 'solid',
|
|
285
|
+
tags: ['next']
|
|
274
286
|
}),
|
|
275
287
|
rel({
|
|
276
288
|
source: 'cloud.frontend.adminPanel',
|
|
@@ -278,29 +290,34 @@ export const fakeRelations = [
|
|
|
278
290
|
kind: 'graphlql',
|
|
279
291
|
title: 'fetches',
|
|
280
292
|
line: 'dashed',
|
|
281
|
-
tail: 'odiamond'
|
|
293
|
+
tail: 'odiamond',
|
|
294
|
+
tags: ['old']
|
|
282
295
|
}),
|
|
283
296
|
rel({
|
|
284
297
|
source: 'cloud',
|
|
285
298
|
target: 'amazon',
|
|
286
299
|
title: 'uses',
|
|
287
300
|
head: 'diamond',
|
|
288
|
-
tail: 'odiamond'
|
|
301
|
+
tail: 'odiamond',
|
|
302
|
+
tags: ['aws']
|
|
289
303
|
}),
|
|
290
304
|
rel({
|
|
291
305
|
source: 'cloud.backend',
|
|
292
306
|
target: 'email',
|
|
293
|
-
title: 'schedule'
|
|
307
|
+
title: 'schedule',
|
|
308
|
+
tags: ['communication']
|
|
294
309
|
}),
|
|
295
310
|
rel({
|
|
296
311
|
source: 'cloud',
|
|
297
312
|
target: 'email',
|
|
298
|
-
title: 'uses'
|
|
313
|
+
title: 'uses',
|
|
314
|
+
tags: ['communication']
|
|
299
315
|
}),
|
|
300
316
|
rel({
|
|
301
317
|
source: 'email',
|
|
302
318
|
target: 'cloud',
|
|
303
|
-
title: 'notifies'
|
|
319
|
+
title: 'notifies',
|
|
320
|
+
tags: ['communication']
|
|
304
321
|
})
|
|
305
322
|
]
|
|
306
323
|
|
|
@@ -380,7 +397,10 @@ export function $customRelation(
|
|
|
380
397
|
}
|
|
381
398
|
}
|
|
382
399
|
|
|
383
|
-
export function $where(
|
|
400
|
+
export function $where(
|
|
401
|
+
expr: Expression | C4Expression,
|
|
402
|
+
operator: WhereOperator<TestTag, string>
|
|
403
|
+
): ElementWhereExpr | RelationWhereExpr {
|
|
384
404
|
return {
|
|
385
405
|
where: {
|
|
386
406
|
expr: $expr(expr) as any,
|
|
@@ -5,10 +5,13 @@ import type {
|
|
|
5
5
|
Element,
|
|
6
6
|
ElementPredicateExpression,
|
|
7
7
|
ElementView,
|
|
8
|
+
NonEmptyArray,
|
|
8
9
|
Relation,
|
|
9
10
|
RelationPredicateExpression,
|
|
10
11
|
RelationshipArrowType,
|
|
12
|
+
RelationshipKind,
|
|
11
13
|
RelationshipLineType,
|
|
14
|
+
Tag,
|
|
12
15
|
ThemeColor,
|
|
13
16
|
ViewRulePredicate
|
|
14
17
|
} from '@likec4/core'
|
|
@@ -193,16 +196,21 @@ export class ComputeCtx {
|
|
|
193
196
|
relations: relations.map(r => r.id)
|
|
194
197
|
}
|
|
195
198
|
|
|
196
|
-
let relation:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
let relation:
|
|
200
|
+
| Pick<Relation, 'title' | 'kind' | 'description' | 'technology' | 'color' | 'line' | 'head' | 'tail' | 'tags'>
|
|
201
|
+
| {
|
|
202
|
+
// TODO refactor with type-fest
|
|
203
|
+
title: string
|
|
204
|
+
description?: string | undefined
|
|
205
|
+
technology?: string | undefined
|
|
206
|
+
kind?: RelationshipKind | undefined
|
|
207
|
+
color?: ThemeColor | undefined
|
|
208
|
+
line?: RelationshipLineType | undefined
|
|
209
|
+
head?: RelationshipArrowType | undefined
|
|
210
|
+
tail?: RelationshipArrowType | undefined
|
|
211
|
+
tags?: NonEmptyArray<Tag>
|
|
212
|
+
}
|
|
213
|
+
| undefined
|
|
206
214
|
if (relations.length === 1) {
|
|
207
215
|
relation = relations[0]
|
|
208
216
|
} else {
|
|
@@ -217,10 +225,13 @@ export class ComputeCtx {
|
|
|
217
225
|
if (r.color && acc.color !== r.color) {
|
|
218
226
|
acc.color = undefined
|
|
219
227
|
}
|
|
220
|
-
if (r.
|
|
228
|
+
if (r.kind && acc.kind !== r.kind) {
|
|
229
|
+
acc.kind = undefined
|
|
230
|
+
}
|
|
231
|
+
if (r.head && acc.head !== r.head) {
|
|
221
232
|
acc.head = undefined
|
|
222
233
|
}
|
|
223
|
-
if (r.tail && acc.tail !== r.
|
|
234
|
+
if (r.tail && acc.tail !== r.tail) {
|
|
224
235
|
acc.tail = undefined
|
|
225
236
|
}
|
|
226
237
|
if (r.line && acc.line !== r.line) {
|
|
@@ -240,32 +251,27 @@ export class ComputeCtx {
|
|
|
240
251
|
title: first(flatMap(relations, r => isTruthy(r.title) ? r.title : [])) ?? '[...]',
|
|
241
252
|
description: first(flatMap(relations, r => isTruthy(r.description) ? r.description : [])),
|
|
242
253
|
technology: first(flatMap(relations, r => isTruthy(r.technology) ? r.technology : [])),
|
|
254
|
+
kind: first(flatMap(relations, r => isTruthy(r.kind) ? r.kind : [])),
|
|
243
255
|
head: first(flatMap(relations, r => isTruthy(r.head) ? r.head : [])),
|
|
244
256
|
tail: first(flatMap(relations, r => isTruthy(r.tail) ? r.tail : [])),
|
|
245
257
|
color: first(flatMap(relations, r => isTruthy(r.color) ? r.color : [])),
|
|
246
258
|
line: first(flatMap(relations, r => isTruthy(r.line) ? r.line : []))
|
|
247
259
|
})
|
|
248
|
-
// return Object.assign(
|
|
249
|
-
// edge,
|
|
250
|
-
// isTruthy(shared.title) && { label: shared.title },
|
|
251
|
-
// isTruthy(shared.description) && { description: shared.description },
|
|
252
|
-
// isTruthy(shared.technology) && { technology: shared.technology },
|
|
253
|
-
// shared.color && { color: shared.color },
|
|
254
|
-
// shared.line && { line: shared.line },
|
|
255
|
-
// shared.head && { head: shared.head },
|
|
256
|
-
// shared.tail && { tail: shared.tail }
|
|
257
|
-
// )
|
|
258
260
|
}
|
|
259
261
|
|
|
262
|
+
const tags = unique(flatMap(relations, r => r.tags ?? []))
|
|
263
|
+
|
|
260
264
|
return Object.assign(
|
|
261
265
|
edge,
|
|
262
266
|
isTruthy(relation.title) && { label: relation.title },
|
|
263
267
|
isTruthy(relation.description) && { description: relation.description },
|
|
264
268
|
isTruthy(relation.technology) && { description: relation.technology },
|
|
269
|
+
isTruthy(relation.kind) && { kind: relation.kind },
|
|
265
270
|
relation.color && { color: relation.color },
|
|
266
271
|
relation.line && { line: relation.line },
|
|
267
272
|
relation.head && { head: relation.head },
|
|
268
|
-
relation.tail && { tail: relation.tail }
|
|
273
|
+
relation.tail && { tail: relation.tail },
|
|
274
|
+
hasAtLeast(tags, 1) && { tags }
|
|
269
275
|
)
|
|
270
276
|
})
|
|
271
277
|
}
|
|
@@ -3,9 +3,11 @@ import type {
|
|
|
3
3
|
ComputedEdge,
|
|
4
4
|
DynamicView,
|
|
5
5
|
Element,
|
|
6
|
+
NonEmptyArray,
|
|
6
7
|
RelationID,
|
|
7
8
|
RelationshipArrowType,
|
|
8
9
|
RelationshipLineType,
|
|
10
|
+
Tag,
|
|
9
11
|
ThemeColor
|
|
10
12
|
} from '@likec4/core'
|
|
11
13
|
import {
|
|
@@ -20,7 +22,7 @@ import {
|
|
|
20
22
|
parentFqn,
|
|
21
23
|
StepEdgeId
|
|
22
24
|
} from '@likec4/core'
|
|
23
|
-
import { isTruthy, unique } from 'remeda'
|
|
25
|
+
import { hasAtLeast, isTruthy, map, unique } from 'remeda'
|
|
24
26
|
import { calcViewLayoutHash } from '../../view-utils/view-hash'
|
|
25
27
|
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
26
28
|
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
@@ -41,6 +43,7 @@ export namespace DynamicViewComputeCtx {
|
|
|
41
43
|
tail?: RelationshipArrowType
|
|
42
44
|
relations: RelationID[]
|
|
43
45
|
isBackward: boolean
|
|
46
|
+
tags?: NonEmptyArray<Tag>
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -81,15 +84,16 @@ export class DynamicViewComputeCtx {
|
|
|
81
84
|
this.explicits.add(source)
|
|
82
85
|
this.explicits.add(target)
|
|
83
86
|
|
|
84
|
-
const { title, relations } = this.findRelations(source, target)
|
|
87
|
+
const { title, relations, tags } = this.findRelations(source, target)
|
|
85
88
|
|
|
86
89
|
this.steps.push({
|
|
87
90
|
...step,
|
|
88
91
|
source,
|
|
89
92
|
target,
|
|
90
93
|
title: isTruthy(stepTitle) ? stepTitle : title,
|
|
91
|
-
relations,
|
|
92
|
-
isBackward: isBackward ?? false
|
|
94
|
+
relations: relations ?? [],
|
|
95
|
+
isBackward: isBackward ?? false,
|
|
96
|
+
...(tags ? { tags } : {})
|
|
93
97
|
})
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -168,12 +172,20 @@ export class DynamicViewComputeCtx {
|
|
|
168
172
|
|
|
169
173
|
private findRelations(source: Element, target: Element): {
|
|
170
174
|
title: string | null
|
|
171
|
-
|
|
175
|
+
tags: NonEmptyArray<Tag> | null
|
|
176
|
+
relations: NonEmptyArray<RelationID> | null
|
|
172
177
|
} {
|
|
173
|
-
const relationships = this.graph.edgesBetween(source, target).flatMap(e => e.relations)
|
|
174
|
-
const
|
|
178
|
+
const relationships = unique(this.graph.edgesBetween(source, target).flatMap(e => e.relations))
|
|
179
|
+
const alltags = unique(relationships.flatMap(r => r.tags ?? []))
|
|
180
|
+
const tags = hasAtLeast(alltags, 1) ? alltags : null
|
|
181
|
+
|
|
182
|
+
const relations = hasAtLeast(relationships, 1) ? map(relationships, r => r.id) : null
|
|
175
183
|
if (relationships.length === 0) {
|
|
176
|
-
return {
|
|
184
|
+
return {
|
|
185
|
+
title: null,
|
|
186
|
+
tags,
|
|
187
|
+
relations
|
|
188
|
+
}
|
|
177
189
|
}
|
|
178
190
|
let relation
|
|
179
191
|
if (relationships.length === 1) {
|
|
@@ -185,6 +197,7 @@ export class DynamicViewComputeCtx {
|
|
|
185
197
|
if (relation && isTruthy(relation.title)) {
|
|
186
198
|
return {
|
|
187
199
|
title: relation.title,
|
|
200
|
+
tags,
|
|
188
201
|
relations
|
|
189
202
|
}
|
|
190
203
|
}
|
|
@@ -195,12 +208,14 @@ export class DynamicViewComputeCtx {
|
|
|
195
208
|
if (labels.length === 1) {
|
|
196
209
|
return {
|
|
197
210
|
title: labels[0]!,
|
|
211
|
+
tags,
|
|
198
212
|
relations
|
|
199
213
|
}
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
return {
|
|
203
217
|
title: null,
|
|
218
|
+
tags,
|
|
204
219
|
relations
|
|
205
220
|
}
|
|
206
221
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComputedNode, ViewRule } from '@likec4/core'
|
|
2
2
|
import { Expr, nonNullable } from '@likec4/core'
|
|
3
|
-
import { isEmpty, isNonNullish, pickBy } from 'remeda'
|
|
3
|
+
import { isEmpty, isNonNullish, isNullish, omitBy, pickBy } from 'remeda'
|
|
4
4
|
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
5
|
|
|
6
6
|
export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
|
|
@@ -14,13 +14,12 @@ export function applyCustomElementProperties(_rules: ViewRule[], _nodes: Compute
|
|
|
14
14
|
custom: { expr, ...props }
|
|
15
15
|
} of rules
|
|
16
16
|
) {
|
|
17
|
+
const { border, opacity, ...rest } = omitBy(props, isNullish)
|
|
17
18
|
const satisfies = elementExprToPredicate(expr)
|
|
18
19
|
nodes.forEach((node, i) => {
|
|
19
20
|
if (!satisfies(node)) {
|
|
20
21
|
return
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
const { border, opacity, ...rest } = pickBy(props, isNonNullish)
|
|
24
23
|
if (!isEmpty(rest)) {
|
|
25
24
|
node = {
|
|
26
25
|
...node,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComputedEdge, ComputedNode, Element, ViewRule } from '@likec4/core'
|
|
2
2
|
import { Expr, nonexhaustive } from '@likec4/core'
|
|
3
|
-
import { isEmpty } from 'remeda'
|
|
3
|
+
import { isEmpty, isNullish, omitBy } from 'remeda'
|
|
4
4
|
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
5
|
|
|
6
6
|
function relationExpressionToPredicates(
|
|
@@ -46,27 +46,11 @@ export function applyCustomRelationProperties(
|
|
|
46
46
|
}
|
|
47
47
|
for (
|
|
48
48
|
const {
|
|
49
|
-
customRelation: { relation, title, ...
|
|
49
|
+
customRelation: { relation, title, ...customprops }
|
|
50
50
|
} of rules
|
|
51
51
|
) {
|
|
52
|
-
|
|
53
|
-
continue
|
|
54
|
-
}
|
|
52
|
+
const props = omitBy(customprops, isNullish)
|
|
55
53
|
const satisfies = relationExpressionToPredicates(relation)
|
|
56
|
-
// const isSource = elementExprToPredicate(relation.source)
|
|
57
|
-
// const isTarget = elementExprToPredicate(relation.target)
|
|
58
|
-
// const satisfies = (edge: ComputedEdge) => {
|
|
59
|
-
// const source = nodes.find(n => n.id === edge.source)
|
|
60
|
-
// const target = nodes.find(n => n.id === edge.target)
|
|
61
|
-
// if (!source || !target) {
|
|
62
|
-
// return false
|
|
63
|
-
// }
|
|
64
|
-
// let result = isSource(source) && isTarget(target)
|
|
65
|
-
// if (!result && relation.isBidirectional) {
|
|
66
|
-
// result = isSource(target) && isTarget(source)
|
|
67
|
-
// }
|
|
68
|
-
// return result
|
|
69
|
-
// }
|
|
70
54
|
edges.forEach((edge, i) => {
|
|
71
55
|
const source = nodes.find(n => n.id === edge.source)
|
|
72
56
|
const target = nodes.find(n => n.id === edge.target)
|
package/src/protocol.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
LikeC4ComputedModel,
|
|
6
6
|
LikeC4Model,
|
|
7
7
|
RelationID,
|
|
8
|
-
|
|
8
|
+
ViewChange,
|
|
9
9
|
ViewID
|
|
10
10
|
} from '@likec4/core'
|
|
11
11
|
import { NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc'
|
|
@@ -53,20 +53,9 @@ export const locate = new RequestType<LocateParams, Location | null, void>('like
|
|
|
53
53
|
export type LocateRequest = typeof locate
|
|
54
54
|
// #endregion
|
|
55
55
|
|
|
56
|
-
export namespace ChangeView {
|
|
57
|
-
export interface ChangeAutoLayout {
|
|
58
|
-
op: 'change-autolayout'
|
|
59
|
-
layout: AutoLayoutDirection
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type ChangeView =
|
|
64
|
-
| ChangeView.ChangeAutoLayout
|
|
65
|
-
| ViewChangeOp
|
|
66
|
-
|
|
67
56
|
export interface ChangeViewRequestParams {
|
|
68
57
|
viewId: ViewID
|
|
69
|
-
change:
|
|
58
|
+
change: ViewChange
|
|
70
59
|
}
|
|
71
60
|
export const changeView = new RequestType<ChangeViewRequestParams, Location | null, void>('likec4/change-view')
|
|
72
61
|
export type ChangeViewRequest = typeof changeView
|
|
@@ -85,6 +85,9 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
85
85
|
override getScope(context: ReferenceInfo): Scope {
|
|
86
86
|
try {
|
|
87
87
|
const referenceType = this.reflection.getReferenceType(context)
|
|
88
|
+
if (referenceType !== ast.Element) {
|
|
89
|
+
return this.getGlobalScope(referenceType)
|
|
90
|
+
}
|
|
88
91
|
try {
|
|
89
92
|
const container = context.container
|
|
90
93
|
if (ast.isFqnElementRef(container) && context.property === 'el') {
|
|
@@ -102,7 +105,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
102
105
|
}
|
|
103
106
|
return this.computeScope(context)
|
|
104
107
|
} catch (e) {
|
|
105
|
-
logger.
|
|
108
|
+
logger.warn(e)
|
|
106
109
|
return this.getGlobalScope(referenceType)
|
|
107
110
|
}
|
|
108
111
|
} catch (e) {
|