@likec4/language-server 1.1.1 → 1.2.0
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/contrib/likec4.monarch.ts +4 -4
- package/contrib/likec4.tmLanguage.json +1 -1
- package/dist/ast.d.ts +27 -11
- package/dist/ast.js +11 -7
- package/dist/generated/ast.d.ts +75 -19
- package/dist/generated/ast.js +96 -7
- package/dist/generated/grammar.js +1 -1
- package/dist/lsp/CodeLensProvider.js +5 -2
- package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
- package/dist/lsp/DocumentSymbolProvider.js +14 -9
- package/dist/lsp/SemanticTokenProvider.js +1 -1
- package/dist/model/model-builder.js +28 -15
- package/dist/model/model-locator.d.ts +2 -2
- package/dist/model/model-locator.js +12 -16
- package/dist/model/model-parser.d.ts +2 -0
- package/dist/model/model-parser.js +143 -33
- package/dist/model-change/ModelChanges.d.ts +2 -1
- package/dist/model-change/ModelChanges.js +26 -20
- package/dist/model-change/changeElementStyle.d.ts +8 -5
- package/dist/model-change/changeElementStyle.js +23 -23
- package/dist/model-change/changeViewLayout.d.ts +4 -4
- package/dist/model-change/changeViewLayout.js +4 -5
- package/dist/references/scope-computation.js +31 -28
- package/dist/shared/NodeKindProvider.js +4 -2
- package/dist/validation/dynamic-view-rule.d.ts +5 -0
- package/dist/validation/dynamic-view-rule.js +32 -0
- package/dist/validation/dynamic-view-step.d.ts +5 -0
- package/dist/validation/dynamic-view-step.js +33 -0
- package/dist/validation/index.js +5 -1
- package/dist/validation/view-predicates/expanded-element.js +1 -0
- package/dist/validation/view-predicates/outgoing.js +2 -2
- package/dist/validation/view.d.ts +1 -1
- package/dist/validation/view.js +1 -3
- package/dist/view-utils/assignNavigateTo.d.ts +1 -1
- package/dist/view-utils/assignNavigateTo.js +2 -1
- package/dist/view-utils/resolve-extended-views.d.ts +2 -2
- package/dist/view-utils/resolve-relative-paths.d.ts +2 -2
- package/dist/view-utils/resolve-relative-paths.js +2 -3
- package/package.json +6 -6
|
@@ -55,21 +55,19 @@ export class LikeC4ModelLocator {
|
|
|
55
55
|
if (targetNode2) {
|
|
56
56
|
return {
|
|
57
57
|
uri: doc.uri.toString(),
|
|
58
|
-
range:
|
|
59
|
-
start: targetNode2.range.start,
|
|
60
|
-
end: targetNode2.range.end
|
|
61
|
-
}
|
|
58
|
+
range: targetNode2.range
|
|
62
59
|
};
|
|
63
60
|
}
|
|
64
61
|
}
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
let targetNode = node.kind ? findNodeForProperty(node.$cstNode, "kind") : findNodeForKeyword(node.$cstNode, "->");
|
|
63
|
+
targetNode ??= findNodeForProperty(node.$cstNode, "target");
|
|
64
|
+
if (!targetNode) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
67
68
|
uri: doc.uri.toString(),
|
|
68
|
-
range:
|
|
69
|
-
|
|
70
|
-
end: targetNode.range.end
|
|
71
|
-
}
|
|
72
|
-
} : null;
|
|
69
|
+
range: targetNode.range
|
|
70
|
+
};
|
|
73
71
|
}
|
|
74
72
|
return null;
|
|
75
73
|
}
|
|
@@ -83,7 +81,7 @@ export class LikeC4ModelLocator {
|
|
|
83
81
|
doc.parseResult.value,
|
|
84
82
|
view.astPath
|
|
85
83
|
);
|
|
86
|
-
if (ast.
|
|
84
|
+
if (ast.isLikeC4View(viewAst)) {
|
|
87
85
|
return {
|
|
88
86
|
doc,
|
|
89
87
|
view,
|
|
@@ -105,15 +103,13 @@ export class LikeC4ModelLocator {
|
|
|
105
103
|
} else if ("viewOf" in node) {
|
|
106
104
|
targetNode = findNodeForProperty(node.$cstNode, "viewOf") ?? targetNode;
|
|
107
105
|
}
|
|
106
|
+
targetNode ??= findNodeForKeyword(node.$cstNode, "view");
|
|
108
107
|
if (!targetNode) {
|
|
109
108
|
return null;
|
|
110
109
|
}
|
|
111
110
|
return {
|
|
112
111
|
uri: res.doc.uri.toString(),
|
|
113
|
-
range:
|
|
114
|
-
start: targetNode.range.start,
|
|
115
|
-
end: targetNode.range.start
|
|
116
|
-
}
|
|
112
|
+
range: targetNode.range
|
|
117
113
|
};
|
|
118
114
|
}
|
|
119
115
|
}
|
|
@@ -19,7 +19,9 @@ export declare class LikeC4ModelParser {
|
|
|
19
19
|
private parseCustomElementExpr;
|
|
20
20
|
private parsePredicateExpr;
|
|
21
21
|
private parseViewRule;
|
|
22
|
+
private parseDynamicStep;
|
|
22
23
|
private parseElementView;
|
|
24
|
+
private parseDynamicElementView;
|
|
23
25
|
protected resolveFqn(node: ast.Element | ast.ExtendElement): c4.Fqn;
|
|
24
26
|
private getAstNodePath;
|
|
25
27
|
private convertTags;
|
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
ast,
|
|
7
7
|
checksFromDiagnostics,
|
|
8
8
|
cleanParsedModel,
|
|
9
|
-
ElementViewOps,
|
|
10
9
|
isFqnIndexedDocument,
|
|
11
10
|
parseAstOpacityProperty,
|
|
12
11
|
resolveRelationPoints,
|
|
13
12
|
streamModel,
|
|
14
13
|
toAutoLayout,
|
|
15
14
|
toElementStyle,
|
|
16
|
-
toRelationshipStyleExcludeDefaults
|
|
15
|
+
toRelationshipStyleExcludeDefaults,
|
|
16
|
+
ViewOps
|
|
17
17
|
} from "../ast.js";
|
|
18
18
|
import { elementRef, getFqnElementRef } from "../elementRef.js";
|
|
19
19
|
import { logError, logger, logWarnError } from "../logger.js";
|
|
@@ -50,16 +50,16 @@ export class LikeC4ModelParser {
|
|
|
50
50
|
}
|
|
51
51
|
parseLikeC4Document(_doc) {
|
|
52
52
|
const doc = cleanParsedModel(_doc);
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
this.
|
|
53
|
+
const { isValid } = checksFromDiagnostics(doc);
|
|
54
|
+
this.parseSpecification(doc, isValid);
|
|
55
|
+
this.parseModel(doc, isValid);
|
|
56
|
+
this.parseViews(doc, isValid);
|
|
56
57
|
return doc;
|
|
57
58
|
}
|
|
58
|
-
parseSpecification(doc) {
|
|
59
|
-
const { isValid: isValid2 } = checksFromDiagnostics(doc);
|
|
59
|
+
parseSpecification(doc, isValid) {
|
|
60
60
|
const { parseResult, c4Specification } = doc;
|
|
61
|
-
const specifications = parseResult.value.specifications.filter(
|
|
62
|
-
const element_specs = specifications.flatMap((s) => s.elements.filter(
|
|
61
|
+
const specifications = parseResult.value.specifications.filter(isValid);
|
|
62
|
+
const element_specs = specifications.flatMap((s) => s.elements.filter(isValid));
|
|
63
63
|
for (const { kind, style } of element_specs) {
|
|
64
64
|
try {
|
|
65
65
|
const kindName = kind.name;
|
|
@@ -71,7 +71,7 @@ export class LikeC4ModelParser {
|
|
|
71
71
|
logWarnError(e);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
const relations_specs = specifications.flatMap((s) => s.relationships.filter(
|
|
74
|
+
const relations_specs = specifications.flatMap((s) => s.relationships.filter(isValid));
|
|
75
75
|
for (const { kind, props } of relations_specs) {
|
|
76
76
|
try {
|
|
77
77
|
const kindName = kind.name;
|
|
@@ -84,8 +84,8 @@ export class LikeC4ModelParser {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
parseModel(doc) {
|
|
88
|
-
for (const el of streamModel(doc)) {
|
|
87
|
+
parseModel(doc, isValid) {
|
|
88
|
+
for (const el of streamModel(doc, isValid)) {
|
|
89
89
|
if (ast.isElement(el)) {
|
|
90
90
|
try {
|
|
91
91
|
doc.c4Elements.push(this.parseElement(el));
|
|
@@ -159,13 +159,16 @@ export class LikeC4ModelParser {
|
|
|
159
159
|
...toRelationshipStyleExcludeDefaults(styleProp?.props)
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
|
-
parseViews(doc) {
|
|
163
|
-
const
|
|
164
|
-
const views = doc.parseResult.value.views.flatMap((v) => isValid2(v) ? v.views.filter(isValid2) : []);
|
|
162
|
+
parseViews(doc, isValid) {
|
|
163
|
+
const views = doc.parseResult.value.views.flatMap((v) => isValid(v) ? v.views : []);
|
|
165
164
|
for (const view of views) {
|
|
166
165
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
if (!isValid(view)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
doc.c4Views.push(
|
|
170
|
+
ast.isElementView(view) ? this.parseElementView(view, isValid) : this.parseDynamicElementView(view, isValid)
|
|
171
|
+
);
|
|
169
172
|
} catch (e) {
|
|
170
173
|
logWarnError(e);
|
|
171
174
|
}
|
|
@@ -307,11 +310,11 @@ export class LikeC4ModelParser {
|
|
|
307
310
|
}
|
|
308
311
|
nonexhaustive(astNode);
|
|
309
312
|
}
|
|
310
|
-
parseViewRule(astRule,
|
|
313
|
+
parseViewRule(astRule, isValid) {
|
|
311
314
|
if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
|
|
312
315
|
const exprs = astRule.expressions.flatMap((n) => {
|
|
313
316
|
try {
|
|
314
|
-
return
|
|
317
|
+
return isValid(n) ? this.parsePredicateExpr(n) : [];
|
|
315
318
|
} catch (e) {
|
|
316
319
|
logWarnError(e);
|
|
317
320
|
return [];
|
|
@@ -335,7 +338,30 @@ export class LikeC4ModelParser {
|
|
|
335
338
|
}
|
|
336
339
|
nonexhaustive(astRule);
|
|
337
340
|
}
|
|
338
|
-
|
|
341
|
+
parseDynamicStep(node) {
|
|
342
|
+
const sourceEl = elementRef(node.source);
|
|
343
|
+
if (!sourceEl) {
|
|
344
|
+
throw new Error("Invalid reference to source");
|
|
345
|
+
}
|
|
346
|
+
const targetEl = elementRef(node.target);
|
|
347
|
+
if (!targetEl) {
|
|
348
|
+
throw new Error("Invalid reference to target");
|
|
349
|
+
}
|
|
350
|
+
let source = this.resolveFqn(sourceEl);
|
|
351
|
+
let target = this.resolveFqn(targetEl);
|
|
352
|
+
if (node.isBackward) {
|
|
353
|
+
;
|
|
354
|
+
[source, target] = [target, source];
|
|
355
|
+
}
|
|
356
|
+
const title = toSingleLine(node.title) ?? null;
|
|
357
|
+
return {
|
|
358
|
+
source,
|
|
359
|
+
target,
|
|
360
|
+
title,
|
|
361
|
+
isBackward: node.isBackward
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
parseElementView(astNode, isValid) {
|
|
339
365
|
const body = astNode.body;
|
|
340
366
|
invariant(body, "ElementView body is not defined");
|
|
341
367
|
const astPath = this.getAstNodePath(astNode);
|
|
@@ -357,37 +383,121 @@ export class LikeC4ModelParser {
|
|
|
357
383
|
viewOf ?? ""
|
|
358
384
|
);
|
|
359
385
|
}
|
|
360
|
-
const title = toSingleLine(body.props.find((p) => p.key === "title")?.value);
|
|
361
|
-
const description = removeIndent(body.props.find((p) => p.key === "description")?.value);
|
|
386
|
+
const title = toSingleLine(body.props.find((p) => p.key === "title")?.value) ?? null;
|
|
387
|
+
const description = removeIndent(body.props.find((p) => p.key === "description")?.value) ?? null;
|
|
362
388
|
const tags = this.convertTags(body);
|
|
363
389
|
const links = body.props.filter(ast.isLinkProperty).map((p) => p.value);
|
|
364
|
-
const
|
|
390
|
+
const view = {
|
|
391
|
+
__: "element",
|
|
365
392
|
id,
|
|
366
393
|
astPath,
|
|
367
394
|
...viewOf && { viewOf },
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
395
|
+
title,
|
|
396
|
+
description,
|
|
397
|
+
tags,
|
|
398
|
+
links: isNonEmptyArray(links) ? links : null,
|
|
372
399
|
rules: body.rules.flatMap((n) => {
|
|
373
400
|
try {
|
|
374
|
-
return
|
|
401
|
+
return isValid(n) ? this.parseViewRule(n, isValid) : [];
|
|
375
402
|
} catch (e) {
|
|
376
403
|
logWarnError(e);
|
|
377
404
|
return [];
|
|
378
405
|
}
|
|
379
406
|
})
|
|
380
407
|
};
|
|
381
|
-
|
|
408
|
+
ViewOps.writeId(astNode, view.id);
|
|
382
409
|
if ("extends" in astNode) {
|
|
383
410
|
const extendsView = astNode.extends.view.ref;
|
|
384
411
|
invariant(extendsView?.name, "view extends is not resolved: " + astNode.$cstNode?.text);
|
|
385
|
-
return {
|
|
386
|
-
...basic,
|
|
412
|
+
return Object.assign(view, {
|
|
387
413
|
extends: extendsView.name
|
|
388
|
-
};
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return view;
|
|
417
|
+
}
|
|
418
|
+
parseDynamicElementView(astNode, isValid) {
|
|
419
|
+
const body = astNode.body;
|
|
420
|
+
invariant(body, "ElementView body is not defined");
|
|
421
|
+
const props = body.props.filter(isValid);
|
|
422
|
+
const astPath = this.getAstNodePath(astNode);
|
|
423
|
+
let id = astNode.name;
|
|
424
|
+
if (!id) {
|
|
425
|
+
id = "dynamic_" + stringHash(
|
|
426
|
+
getDocument(astNode).uri.toString(),
|
|
427
|
+
astPath
|
|
428
|
+
);
|
|
389
429
|
}
|
|
390
|
-
|
|
430
|
+
const title = toSingleLine(props.find((p) => p.key === "title")?.value) ?? null;
|
|
431
|
+
const description = removeIndent(props.find((p) => p.key === "description")?.value) ?? null;
|
|
432
|
+
const tags = this.convertTags(body);
|
|
433
|
+
const links = props.filter(ast.isLinkProperty).map((p) => p.value);
|
|
434
|
+
ViewOps.writeId(astNode, id);
|
|
435
|
+
return {
|
|
436
|
+
__: "dynamic",
|
|
437
|
+
id,
|
|
438
|
+
astPath,
|
|
439
|
+
title,
|
|
440
|
+
description,
|
|
441
|
+
tags,
|
|
442
|
+
links: isNonEmptyArray(links) ? links : null,
|
|
443
|
+
rules: body.rules.reduce((acc, n) => {
|
|
444
|
+
if (!isValid(n)) {
|
|
445
|
+
return acc;
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
if (ast.isDynamicViewRulePredicate(n)) {
|
|
449
|
+
const include = [];
|
|
450
|
+
for (const expr of n.expressions) {
|
|
451
|
+
if (ast.isElementExpr(expr)) {
|
|
452
|
+
include.push(this.parseElementExpr(expr));
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (ast.isCustomElementExpr(expr)) {
|
|
456
|
+
include.push(this.parseCustomElementExpr(expr));
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (include.length > 0) {
|
|
461
|
+
acc.push({ include });
|
|
462
|
+
}
|
|
463
|
+
return acc;
|
|
464
|
+
}
|
|
465
|
+
if (ast.isViewRuleStyle(n)) {
|
|
466
|
+
const styleProps = toElementStyle(n.styleprops);
|
|
467
|
+
const targets = n.targets.map((n2) => this.parseElementExpr(n2));
|
|
468
|
+
if (targets.length > 0) {
|
|
469
|
+
acc.push({
|
|
470
|
+
targets,
|
|
471
|
+
style: {
|
|
472
|
+
...styleProps
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return acc;
|
|
477
|
+
}
|
|
478
|
+
if (ast.isViewRuleAutoLayout(n)) {
|
|
479
|
+
acc.push({
|
|
480
|
+
autoLayout: toAutoLayout(n.direction)
|
|
481
|
+
});
|
|
482
|
+
return acc;
|
|
483
|
+
}
|
|
484
|
+
nonexhaustive(n);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
logWarnError(e);
|
|
487
|
+
return acc;
|
|
488
|
+
}
|
|
489
|
+
}, []),
|
|
490
|
+
steps: body.steps.reduce((acc, n) => {
|
|
491
|
+
try {
|
|
492
|
+
if (isValid(n)) {
|
|
493
|
+
acc.push(this.parseDynamicStep(n));
|
|
494
|
+
}
|
|
495
|
+
} catch (e) {
|
|
496
|
+
logWarnError(e);
|
|
497
|
+
}
|
|
498
|
+
return acc;
|
|
499
|
+
}, [])
|
|
500
|
+
};
|
|
391
501
|
}
|
|
392
502
|
resolveFqn(node) {
|
|
393
503
|
if (ast.isExtendElement(node)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Location, TextEdit } from 'vscode-languageserver-protocol';
|
|
1
|
+
import { Location, Range, TextEdit } from 'vscode-languageserver-protocol';
|
|
2
2
|
import { type ParsedLikeC4LangiumDocument } from '../ast';
|
|
3
3
|
import type { LikeC4Services } from '../module';
|
|
4
4
|
import type { ChangeViewRequestParams } from '../protocol';
|
|
@@ -9,6 +9,7 @@ export declare class LikeC4ModelChanges {
|
|
|
9
9
|
applyChange(changeView: ChangeViewRequestParams): Promise<Location | null>;
|
|
10
10
|
protected convertToTextEdit({ viewId, changes }: ChangeViewRequestParams): {
|
|
11
11
|
doc: ParsedLikeC4LangiumDocument;
|
|
12
|
+
ranges: Range[];
|
|
12
13
|
edits: TextEdit[];
|
|
13
14
|
};
|
|
14
15
|
}
|
|
@@ -2,30 +2,30 @@ import { invariant, nonexhaustive } from "@likec4/core";
|
|
|
2
2
|
import { Range } from "vscode-languageserver-protocol";
|
|
3
3
|
import { changeElementStyle } from "./changeElementStyle.js";
|
|
4
4
|
import { changeViewLayout } from "./changeViewLayout.js";
|
|
5
|
-
function unionRangeOfAllEdits(
|
|
6
|
-
let
|
|
7
|
-
let
|
|
5
|
+
function unionRangeOfAllEdits(ranges) {
|
|
6
|
+
let startLine = Number.MAX_SAFE_INTEGER;
|
|
7
|
+
let endLine = Number.MIN_SAFE_INTEGER;
|
|
8
8
|
let startCharacter = Number.MAX_SAFE_INTEGER;
|
|
9
9
|
let endCharacter = Number.MIN_SAFE_INTEGER;
|
|
10
|
-
for (const {
|
|
11
|
-
if (
|
|
12
|
-
if (
|
|
13
|
-
startCharacter = Math.min(
|
|
10
|
+
for (const { start, end } of ranges) {
|
|
11
|
+
if (start.line <= startLine) {
|
|
12
|
+
if (startLine == start.line) {
|
|
13
|
+
startCharacter = Math.min(start.character, startCharacter);
|
|
14
14
|
} else {
|
|
15
|
-
|
|
16
|
-
startCharacter =
|
|
15
|
+
startLine = start.line;
|
|
16
|
+
startCharacter = start.character;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
if (
|
|
20
|
-
if (
|
|
21
|
-
endCharacter = Math.max(
|
|
19
|
+
if (endLine <= end.line) {
|
|
20
|
+
if (endLine == end.line) {
|
|
21
|
+
endCharacter = Math.max(end.character, endCharacter);
|
|
22
22
|
} else {
|
|
23
|
-
|
|
24
|
-
endCharacter =
|
|
23
|
+
endLine = end.line;
|
|
24
|
+
endCharacter = end.character;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
return Range.create(
|
|
28
|
+
return Range.create(startLine, startCharacter, endLine, endCharacter);
|
|
29
29
|
}
|
|
30
30
|
export class LikeC4ModelChanges {
|
|
31
31
|
constructor(services) {
|
|
@@ -60,7 +60,7 @@ export class LikeC4ModelChanges {
|
|
|
60
60
|
}
|
|
61
61
|
result = {
|
|
62
62
|
uri: textDocument.uri,
|
|
63
|
-
range: unionRangeOfAllEdits(edits)
|
|
63
|
+
range: unionRangeOfAllEdits(edits.map((edit) => edit.range))
|
|
64
64
|
};
|
|
65
65
|
});
|
|
66
66
|
return result;
|
|
@@ -70,22 +70,27 @@ export class LikeC4ModelChanges {
|
|
|
70
70
|
if (!lookup) {
|
|
71
71
|
throw new Error(`View not found: ${viewId}`);
|
|
72
72
|
}
|
|
73
|
+
const ranges = [];
|
|
73
74
|
const edits = [];
|
|
74
75
|
for (const change of changes) {
|
|
75
76
|
switch (change.op) {
|
|
76
77
|
case "change-element-style": {
|
|
77
|
-
edits
|
|
78
|
+
const { edits: elementEdits, modifiedRange } = changeElementStyle(this.services, {
|
|
78
79
|
...lookup,
|
|
79
80
|
targets: change.targets,
|
|
80
81
|
style: change.style
|
|
81
|
-
})
|
|
82
|
+
});
|
|
83
|
+
ranges.push(modifiedRange);
|
|
84
|
+
edits.push(...elementEdits);
|
|
82
85
|
break;
|
|
83
86
|
}
|
|
84
87
|
case "change-autolayout": {
|
|
85
|
-
|
|
88
|
+
const edit = changeViewLayout(this.services, {
|
|
86
89
|
...lookup,
|
|
87
90
|
layout: change.layout
|
|
88
|
-
})
|
|
91
|
+
});
|
|
92
|
+
edits.push(edit);
|
|
93
|
+
ranges.push(edit.range);
|
|
89
94
|
break;
|
|
90
95
|
}
|
|
91
96
|
default:
|
|
@@ -94,6 +99,7 @@ export class LikeC4ModelChanges {
|
|
|
94
99
|
}
|
|
95
100
|
return {
|
|
96
101
|
doc: lookup.doc,
|
|
102
|
+
ranges,
|
|
97
103
|
edits
|
|
98
104
|
};
|
|
99
105
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { type Fqn, type NonEmptyArray } from '@likec4/core';
|
|
2
|
-
import { TextEdit } from 'vscode-languageserver-protocol';
|
|
3
|
-
import { ast, type
|
|
2
|
+
import { type Range, TextEdit } from 'vscode-languageserver-protocol';
|
|
3
|
+
import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
5
|
import type { ChangeView } from '../protocol';
|
|
6
6
|
type ChangeElementStyleArg = {
|
|
7
|
-
view:
|
|
7
|
+
view: ParsedAstView;
|
|
8
8
|
doc: ParsedLikeC4LangiumDocument;
|
|
9
|
-
viewAst: ast.
|
|
9
|
+
viewAst: ast.LikeC4View;
|
|
10
10
|
targets: NonEmptyArray<Fqn>;
|
|
11
11
|
style: ChangeView.ChangeElementStyle['style'];
|
|
12
12
|
};
|
|
13
|
-
export declare function changeElementStyle(services: LikeC4Services, { view, viewAst, targets, style }: ChangeElementStyleArg):
|
|
13
|
+
export declare function changeElementStyle(services: LikeC4Services, { view, viewAst, targets, style }: ChangeElementStyleArg): {
|
|
14
|
+
modifiedRange: Range;
|
|
15
|
+
edits: TextEdit[];
|
|
16
|
+
};
|
|
14
17
|
export {};
|
|
15
18
|
//# sourceMappingURL=changeElementStyle.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { invariant, isAncestor, nonNullable } from "@likec4/core";
|
|
2
2
|
import { GrammarUtils } from "langium";
|
|
3
|
-
import { entries,
|
|
3
|
+
import { entries, filter, findLast, last } from "remeda";
|
|
4
4
|
import { TextEdit } from "vscode-languageserver-protocol";
|
|
5
5
|
import { ast } from "../ast.js";
|
|
6
6
|
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
|
|
@@ -8,9 +8,11 @@ const asViewStyleRule = (target, style, indent = 0) => {
|
|
|
8
8
|
const indentStr = indent > 0 ? " ".repeat(indent) : "";
|
|
9
9
|
return [
|
|
10
10
|
indentStr + `style ${target} {`,
|
|
11
|
-
...entries(style).map(
|
|
11
|
+
...entries.strict(style).map(
|
|
12
|
+
([key, value]) => indentStr + ` ${key} ${key === "opacity" ? value.toString() + "%" : value}`
|
|
13
|
+
),
|
|
12
14
|
indentStr + `}`
|
|
13
|
-
]
|
|
15
|
+
];
|
|
14
16
|
};
|
|
15
17
|
const isMatchingViewRule = (fqn, index) => (rule) => {
|
|
16
18
|
if (!ast.isViewRuleStyle(rule)) {
|
|
@@ -32,12 +34,12 @@ export function changeElementStyle(services, {
|
|
|
32
34
|
}) {
|
|
33
35
|
const viewCstNode = viewAst.$cstNode;
|
|
34
36
|
invariant(viewCstNode, "viewCstNode");
|
|
35
|
-
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ??
|
|
37
|
+
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.end;
|
|
36
38
|
invariant(insertPos, "insertPos is not defined");
|
|
37
39
|
const indent = viewCstNode.range.start.character + 2;
|
|
38
40
|
const fqnIndex = services.likec4.FqnIndex;
|
|
39
|
-
const styleRules = viewAst.body.rules
|
|
40
|
-
const viewOf = view.viewOf;
|
|
41
|
+
const styleRules = filter(viewAst.body.rules, ast.isViewRuleStyle);
|
|
42
|
+
const viewOf = view.__ === "element" ? view.viewOf : null;
|
|
41
43
|
const existing = [];
|
|
42
44
|
const insert = [];
|
|
43
45
|
targets.forEach((target) => {
|
|
@@ -58,38 +60,33 @@ export function changeElementStyle(services, {
|
|
|
58
60
|
if (range.start.line == modifiedRange.start.line) {
|
|
59
61
|
modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character);
|
|
60
62
|
} else {
|
|
61
|
-
modifiedRange.start
|
|
62
|
-
modifiedRange.start.character = range.start.character;
|
|
63
|
+
modifiedRange.start = range.start;
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
if (range.end.line >= modifiedRange.end.line) {
|
|
66
67
|
if (range.end.line == modifiedRange.end.line) {
|
|
67
68
|
modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character);
|
|
68
69
|
} else {
|
|
69
|
-
modifiedRange.end
|
|
70
|
-
modifiedRange.end.character = range.end.character;
|
|
70
|
+
modifiedRange.end = range.end;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
const edits = [];
|
|
75
75
|
if (insert.length > 0) {
|
|
76
|
-
const linesToInsert =
|
|
77
|
-
"",
|
|
78
|
-
...insert.map(({ fqn }) => asViewStyleRule(fqn, style, indent))
|
|
79
|
-
];
|
|
76
|
+
const linesToInsert = insert.flatMap(({ fqn }) => asViewStyleRule(fqn, style, indent));
|
|
80
77
|
edits.push(
|
|
81
78
|
TextEdit.insert(
|
|
82
79
|
insertPos,
|
|
83
|
-
linesToInsert.join("\n")
|
|
80
|
+
"\n" + linesToInsert.join("\n")
|
|
84
81
|
)
|
|
85
82
|
);
|
|
86
83
|
modifiedRange.start = {
|
|
87
84
|
line: insertPos.line + 1,
|
|
88
|
-
character: indent
|
|
85
|
+
character: indent + 1
|
|
89
86
|
};
|
|
90
87
|
modifiedRange.end = {
|
|
91
88
|
line: insertPos.line + linesToInsert.length,
|
|
92
|
-
character:
|
|
89
|
+
character: last(linesToInsert)?.length ?? 0
|
|
93
90
|
};
|
|
94
91
|
}
|
|
95
92
|
if (existing.length > 0) {
|
|
@@ -97,7 +94,7 @@ export function changeElementStyle(services, {
|
|
|
97
94
|
const ruleCstNode = rule.$cstNode;
|
|
98
95
|
invariant(ruleCstNode, "RuleCstNode not found");
|
|
99
96
|
for (const [key, _value] of entries.strict(style)) {
|
|
100
|
-
const value =
|
|
97
|
+
const value = key === "opacity" ? _value.toString() + "%" : _value;
|
|
101
98
|
const ruleProp = rule.styleprops.find((p) => p.key === key);
|
|
102
99
|
if (ruleProp && ruleProp.$cstNode) {
|
|
103
100
|
const { range: { start, end } } = nonNullable(
|
|
@@ -107,8 +104,8 @@ export function changeElementStyle(services, {
|
|
|
107
104
|
includeRange({
|
|
108
105
|
start,
|
|
109
106
|
end: {
|
|
110
|
-
line:
|
|
111
|
-
character: start.character + value.length
|
|
107
|
+
line: start.line,
|
|
108
|
+
character: start.character + value.length
|
|
112
109
|
}
|
|
113
110
|
});
|
|
114
111
|
edits.push(TextEdit.replace({ start, end }, value));
|
|
@@ -127,15 +124,18 @@ export function changeElementStyle(services, {
|
|
|
127
124
|
includeRange({
|
|
128
125
|
start: {
|
|
129
126
|
line: insertPos2.line + 1,
|
|
130
|
-
character: indentStr.length
|
|
127
|
+
character: indentStr.length
|
|
131
128
|
},
|
|
132
129
|
end: {
|
|
133
130
|
line: insertPos2.line + 1,
|
|
134
|
-
character: insertKeyValue.length
|
|
131
|
+
character: insertKeyValue.length
|
|
135
132
|
}
|
|
136
133
|
});
|
|
137
134
|
}
|
|
138
135
|
}
|
|
139
136
|
}
|
|
140
|
-
return
|
|
137
|
+
return {
|
|
138
|
+
modifiedRange,
|
|
139
|
+
edits
|
|
140
|
+
};
|
|
141
141
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type AutoLayoutDirection } from '@likec4/core';
|
|
2
2
|
import { TextEdit } from 'vscode-languageserver-protocol';
|
|
3
|
-
import { ast, type
|
|
3
|
+
import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
5
|
type ChangeViewLayoutArg = {
|
|
6
|
-
view:
|
|
6
|
+
view: ParsedAstView;
|
|
7
7
|
doc: ParsedLikeC4LangiumDocument;
|
|
8
|
-
viewAst: ast.
|
|
8
|
+
viewAst: ast.LikeC4View;
|
|
9
9
|
layout: AutoLayoutDirection;
|
|
10
10
|
};
|
|
11
|
-
export declare function changeViewLayout(services: LikeC4Services, {
|
|
11
|
+
export declare function changeViewLayout(services: LikeC4Services, { viewAst, layout }: ChangeViewLayoutArg): TextEdit;
|
|
12
12
|
export {};
|
|
13
13
|
//# sourceMappingURL=changeViewLayout.d.ts.map
|
|
@@ -5,7 +5,6 @@ import { TextEdit } from "vscode-languageserver-protocol";
|
|
|
5
5
|
import { ast, toAstViewLayoutDirection } from "../ast.js";
|
|
6
6
|
const { findNodeForProperty } = GrammarUtils;
|
|
7
7
|
export function changeViewLayout(services, {
|
|
8
|
-
view,
|
|
9
8
|
viewAst,
|
|
10
9
|
layout
|
|
11
10
|
}) {
|
|
@@ -16,15 +15,15 @@ export function changeViewLayout(services, {
|
|
|
16
15
|
if (existingRule && existingRule.$cstNode) {
|
|
17
16
|
const directionCstNode = findNodeForProperty(existingRule.$cstNode, "direction");
|
|
18
17
|
if (directionCstNode) {
|
|
19
|
-
return
|
|
18
|
+
return TextEdit.replace(directionCstNode.range, newlayout);
|
|
20
19
|
}
|
|
21
|
-
return
|
|
20
|
+
return TextEdit.replace(existingRule.$cstNode.range, `autoLayout ${newlayout}`);
|
|
22
21
|
}
|
|
23
|
-
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ??
|
|
22
|
+
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.end;
|
|
24
23
|
invariant(insertPos, "insertPos is not defined");
|
|
25
24
|
const indent = " ".repeat(2 + viewCstNode.range.start.character);
|
|
26
25
|
const insert = `
|
|
27
26
|
|
|
28
27
|
${indent}autoLayout ${newlayout}`;
|
|
29
|
-
return
|
|
28
|
+
return TextEdit.insert(insertPos, insert);
|
|
30
29
|
}
|