@likec4/language-server 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.d.ts +3 -2
- package/dist/ast.js +3 -6
- package/dist/model/model-builder.js +40 -30
- package/dist/model/model-parser.js +13 -13
- package/dist/model-change/ModelChanges.js +24 -26
- package/dist/model-change/{changeViewStyle.d.ts → changeElementStyle.d.ts} +5 -5
- package/dist/model-change/changeElementStyle.js +141 -0
- package/dist/protocol.d.ts +12 -12
- package/dist/references/scope-computation.js +5 -3
- package/package.json +3 -3
- package/dist/model-change/changeViewStyle.js +0 -123
package/dist/ast.d.ts
CHANGED
|
@@ -102,12 +102,13 @@ export declare function isLikeC4LangiumDocument(doc: LangiumDocument): doc is Li
|
|
|
102
102
|
export declare function isParsedLikeC4LangiumDocument(doc: LangiumDocument): doc is ParsedLikeC4LangiumDocument;
|
|
103
103
|
type Guard<N extends AstNode> = (n: AstNode) => n is N;
|
|
104
104
|
type Guarded<G> = G extends Guard<infer N> ? N : never;
|
|
105
|
-
declare const isValidatableAstNode: (n: AstNode) => n is ast.DescedantsExpr | ast.ElementKindExpr | ast.ElementRef | ast.ElementTagExpr | ast.ExpandElementExpr | ast.WildcardExpr | ast.ElementStringProperty | ast.LinkProperty | ast.StyleProperties | ast.ExplicitRelation | ast.ImplicitRelation | ast.RelationStringProperty | ast.RelationStyleProperty | ast.ViewStringProperty | ast.ViewRuleAutoLayout | ast.ExcludePredicate | ast.IncludePredicate | ast.ViewRuleStyle | ast.CustomElementExpr | ast.InOutExpr | ast.IncomingExpr | ast.OutgoingExpr | ast.RelationExpr | ast.SpecificationRelationshipKind | ast.RelationBody | ast.ElementBody | ast.ExtendElementBody | ast.Model | ast.Element | ast.ExtendElement | ast.SpecificationRule | ast.SpecificationElementKind | ast.ElementViewBody | ast.ElementView | ast.ModelViews | ast.SpecificationTag;
|
|
105
|
+
declare const isValidatableAstNode: (n: AstNode) => n is ast.DescedantsExpr | ast.ElementKindExpr | ast.ElementRef | ast.ElementTagExpr | ast.ExpandElementExpr | ast.WildcardExpr | ast.ElementStringProperty | ast.LinkProperty | ast.StyleProperties | ast.ExplicitRelation | ast.ImplicitRelation | ast.RelationStringProperty | ast.RelationStyleProperty | ast.ViewStringProperty | ast.ViewRuleAutoLayout | ast.ExcludePredicate | ast.IncludePredicate | ast.ViewRuleStyle | ast.CustomElementExpr | ast.InOutExpr | ast.IncomingExpr | ast.OutgoingExpr | ast.RelationExpr | ast.SpecificationRelationshipKind | ast.RelationBody | ast.ElementBody | ast.ExtendElementBody | ast.Model | ast.Element | ast.ExtendElement | ast.SpecificationRule | ast.CustomElementExprBody | ast.SpecificationElementKind | ast.ElementViewBody | ast.ElementView | ast.ModelViews | ast.Tags | ast.SpecificationTag;
|
|
106
106
|
type ValidatableAstNode = Guarded<typeof isValidatableAstNode>;
|
|
107
107
|
export declare function checksFromDiagnostics(doc: LikeC4LangiumDocument): {
|
|
108
108
|
isValid: (n: ValidatableAstNode) => boolean;
|
|
109
|
-
invalidNodes: WeakSet<ast.DescedantsExpr | ast.ElementKindExpr | ast.ElementRef | ast.ElementTagExpr | ast.ExpandElementExpr | ast.WildcardExpr | ast.ElementStringProperty | ast.LinkProperty | ast.StyleProperties | ast.ExplicitRelation | ast.ImplicitRelation | ast.RelationStringProperty | ast.RelationStyleProperty | ast.ViewStringProperty | ast.ViewRuleAutoLayout | ast.ExcludePredicate | ast.IncludePredicate | ast.ViewRuleStyle | ast.CustomElementExpr | ast.InOutExpr | ast.IncomingExpr | ast.OutgoingExpr | ast.RelationExpr | ast.SpecificationRelationshipKind | ast.RelationBody | ast.ElementBody | ast.ExtendElementBody | ast.Model | ast.Element | ast.ExtendElement | ast.SpecificationRule | ast.SpecificationElementKind | ast.ElementViewBody | ast.ElementView | ast.ModelViews | ast.SpecificationTag>;
|
|
109
|
+
invalidNodes: WeakSet<ast.DescedantsExpr | ast.ElementKindExpr | ast.ElementRef | ast.ElementTagExpr | ast.ExpandElementExpr | ast.WildcardExpr | ast.ElementStringProperty | ast.LinkProperty | ast.StyleProperties | ast.ExplicitRelation | ast.ImplicitRelation | ast.RelationStringProperty | ast.RelationStyleProperty | ast.ViewStringProperty | ast.ViewRuleAutoLayout | ast.ExcludePredicate | ast.IncludePredicate | ast.ViewRuleStyle | ast.CustomElementExpr | ast.InOutExpr | ast.IncomingExpr | ast.OutgoingExpr | ast.RelationExpr | ast.SpecificationRelationshipKind | ast.RelationBody | ast.ElementBody | ast.ExtendElementBody | ast.Model | ast.Element | ast.ExtendElement | ast.SpecificationRule | ast.CustomElementExprBody | ast.SpecificationElementKind | ast.ElementViewBody | ast.ElementView | ast.ModelViews | ast.Tags | ast.SpecificationTag>;
|
|
110
110
|
};
|
|
111
|
+
export type ChecksFromDiagnostics = ReturnType<typeof checksFromDiagnostics>;
|
|
111
112
|
export declare function streamModel(doc: LikeC4LangiumDocument): Generator<ast.Relation | ast.Element, void, unknown>;
|
|
112
113
|
export declare function resolveRelationPoints(node: ast.Relation): {
|
|
113
114
|
source: ast.Element;
|
package/dist/ast.js
CHANGED
|
@@ -60,9 +60,9 @@ function validatableAstNodeGuards(predicates) {
|
|
|
60
60
|
return (n) => predicates.some((p) => p(n));
|
|
61
61
|
}
|
|
62
62
|
const isValidatableAstNode = validatableAstNodeGuards([
|
|
63
|
-
ast.
|
|
63
|
+
ast.isCustomElementExprBody,
|
|
64
64
|
ast.isViewRulePredicateExpr,
|
|
65
|
-
ast.
|
|
65
|
+
ast.isTags,
|
|
66
66
|
ast.isViewRule,
|
|
67
67
|
ast.isViewProperty,
|
|
68
68
|
ast.isElementViewBody,
|
|
@@ -177,10 +177,7 @@ export function toElementStyle(props) {
|
|
|
177
177
|
break;
|
|
178
178
|
}
|
|
179
179
|
case ast.isOpacityProperty(prop): {
|
|
180
|
-
|
|
181
|
-
if (opacity !== void 0) {
|
|
182
|
-
result.opacity = opacity;
|
|
183
|
-
}
|
|
180
|
+
result.opacity = parseAstOpacityProperty(prop);
|
|
184
181
|
break;
|
|
185
182
|
}
|
|
186
183
|
default:
|
|
@@ -32,14 +32,14 @@ function buildModel(services, docs) {
|
|
|
32
32
|
relationships: {}
|
|
33
33
|
};
|
|
34
34
|
forEach(map(docs, prop("c4Specification")), (spec) => {
|
|
35
|
-
Object.assign(c4Specification.kinds, spec.kinds)
|
|
35
|
+
Object.assign(c4Specification.kinds, spec.kinds);
|
|
36
|
+
Object.assign(c4Specification.relationships, spec.relationships);
|
|
36
37
|
});
|
|
37
38
|
const resolveLinks = (doc, links) => {
|
|
38
39
|
return links.map((l) => services.lsp.DocumentLinkProvider.resolveLink(doc, l));
|
|
39
40
|
};
|
|
40
41
|
const toModelElement = (doc) => {
|
|
41
42
|
return ({
|
|
42
|
-
astPath,
|
|
43
43
|
tags,
|
|
44
44
|
links,
|
|
45
45
|
style: {
|
|
@@ -49,19 +49,23 @@ function buildModel(services, docs) {
|
|
|
49
49
|
opacity,
|
|
50
50
|
border
|
|
51
51
|
},
|
|
52
|
-
|
|
52
|
+
id,
|
|
53
|
+
kind,
|
|
54
|
+
title,
|
|
55
|
+
description,
|
|
56
|
+
technology
|
|
53
57
|
}) => {
|
|
54
58
|
try {
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
logger.warn(`No kind '${
|
|
59
|
+
const __kind = c4Specification.kinds[kind];
|
|
60
|
+
if (!__kind) {
|
|
61
|
+
logger.warn(`No kind '${kind}' found for ${id}`);
|
|
58
62
|
return null;
|
|
59
63
|
}
|
|
60
|
-
color ??=
|
|
61
|
-
shape ??=
|
|
62
|
-
icon ??=
|
|
63
|
-
opacity ??=
|
|
64
|
-
border ??=
|
|
64
|
+
color ??= __kind.color;
|
|
65
|
+
shape ??= __kind.shape;
|
|
66
|
+
icon ??= __kind.icon;
|
|
67
|
+
opacity ??= __kind.opacity;
|
|
68
|
+
border ??= __kind.border;
|
|
65
69
|
return {
|
|
66
70
|
...color && { color },
|
|
67
71
|
...shape && { shape },
|
|
@@ -70,11 +74,13 @@ function buildModel(services, docs) {
|
|
|
70
74
|
...border && { border },
|
|
71
75
|
...opacity && { opacity }
|
|
72
76
|
},
|
|
73
|
-
description: null,
|
|
74
|
-
technology: null,
|
|
75
|
-
tags: tags ?? null,
|
|
76
77
|
links: links ? resolveLinks(doc, links) : null,
|
|
77
|
-
|
|
78
|
+
tags: tags ?? null,
|
|
79
|
+
technology: technology ?? null,
|
|
80
|
+
description: description ?? null,
|
|
81
|
+
title,
|
|
82
|
+
kind,
|
|
83
|
+
id
|
|
78
84
|
};
|
|
79
85
|
} catch (e) {
|
|
80
86
|
logWarnError(e);
|
|
@@ -83,7 +89,8 @@ function buildModel(services, docs) {
|
|
|
83
89
|
};
|
|
84
90
|
};
|
|
85
91
|
const elements = pipe(
|
|
86
|
-
|
|
92
|
+
docs,
|
|
93
|
+
flatMap((d) => d.c4Elements.map(toModelElement(d))),
|
|
87
94
|
filter(isTruthy),
|
|
88
95
|
sort(compareByFqnHierarchically),
|
|
89
96
|
reduce(
|
|
@@ -110,27 +117,30 @@ function buildModel(services, docs) {
|
|
|
110
117
|
target,
|
|
111
118
|
kind,
|
|
112
119
|
links,
|
|
120
|
+
id,
|
|
113
121
|
...model
|
|
114
122
|
}) => {
|
|
115
|
-
if (source
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
target,
|
|
120
|
-
kind,
|
|
121
|
-
...links && { links: resolveLinks(doc, links) },
|
|
122
|
-
...c4Specification.relationships[kind],
|
|
123
|
-
...model
|
|
124
|
-
};
|
|
125
|
-
}
|
|
123
|
+
if (isNullish(elements[source]) || isNullish(elements[target])) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
if (!!kind && kind in c4Specification.relationships) {
|
|
126
127
|
return {
|
|
128
|
+
...links && { links: resolveLinks(doc, links) },
|
|
129
|
+
...c4Specification.relationships[kind],
|
|
130
|
+
...model,
|
|
127
131
|
source,
|
|
128
132
|
target,
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
kind,
|
|
134
|
+
id
|
|
131
135
|
};
|
|
132
136
|
}
|
|
133
|
-
return
|
|
137
|
+
return {
|
|
138
|
+
...links && { links: resolveLinks(doc, links) },
|
|
139
|
+
...model,
|
|
140
|
+
source,
|
|
141
|
+
target,
|
|
142
|
+
id
|
|
143
|
+
};
|
|
134
144
|
};
|
|
135
145
|
};
|
|
136
146
|
const relations = pipe(
|
|
@@ -56,10 +56,10 @@ export class LikeC4ModelParser {
|
|
|
56
56
|
return doc;
|
|
57
57
|
}
|
|
58
58
|
parseSpecification(doc) {
|
|
59
|
-
const { isValid } = checksFromDiagnostics(doc);
|
|
59
|
+
const { isValid: isValid2 } = checksFromDiagnostics(doc);
|
|
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(isValid2);
|
|
62
|
+
const element_specs = specifications.flatMap((s) => s.elements.filter(isValid2));
|
|
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(isValid2));
|
|
75
75
|
for (const { kind, props } of relations_specs) {
|
|
76
76
|
try {
|
|
77
77
|
const kindName = kind.name;
|
|
@@ -160,11 +160,11 @@ export class LikeC4ModelParser {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
parseViews(doc) {
|
|
163
|
-
const { isValid } = checksFromDiagnostics(doc);
|
|
164
|
-
const views = doc.parseResult.value.views.flatMap((v) =>
|
|
163
|
+
const { isValid: isValid2 } = checksFromDiagnostics(doc);
|
|
164
|
+
const views = doc.parseResult.value.views.flatMap((v) => isValid2(v) ? v.views.filter(isValid2) : []);
|
|
165
165
|
for (const view of views) {
|
|
166
166
|
try {
|
|
167
|
-
const v = this.parseElementView(view);
|
|
167
|
+
const v = this.parseElementView(view, isValid2);
|
|
168
168
|
doc.c4Views.push(v);
|
|
169
169
|
} catch (e) {
|
|
170
170
|
logWarnError(e);
|
|
@@ -307,11 +307,11 @@ export class LikeC4ModelParser {
|
|
|
307
307
|
}
|
|
308
308
|
nonexhaustive(astNode);
|
|
309
309
|
}
|
|
310
|
-
parseViewRule(astRule) {
|
|
310
|
+
parseViewRule(astRule, isValid2) {
|
|
311
311
|
if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
|
|
312
312
|
const exprs = astRule.expressions.flatMap((n) => {
|
|
313
313
|
try {
|
|
314
|
-
return this.parsePredicateExpr(n);
|
|
314
|
+
return isValid2(n) ? this.parsePredicateExpr(n) : [];
|
|
315
315
|
} catch (e) {
|
|
316
316
|
logWarnError(e);
|
|
317
317
|
return [];
|
|
@@ -335,7 +335,7 @@ export class LikeC4ModelParser {
|
|
|
335
335
|
}
|
|
336
336
|
nonexhaustive(astRule);
|
|
337
337
|
}
|
|
338
|
-
parseElementView(astNode) {
|
|
338
|
+
parseElementView(astNode, isValid2) {
|
|
339
339
|
const body = astNode.body;
|
|
340
340
|
invariant(body, "ElementView body is not defined");
|
|
341
341
|
const astPath = this.getAstNodePath(astNode);
|
|
@@ -357,8 +357,8 @@ export class LikeC4ModelParser {
|
|
|
357
357
|
viewOf ?? ""
|
|
358
358
|
);
|
|
359
359
|
}
|
|
360
|
-
const title = body.props.find((p) => p.key === "title")?.value;
|
|
361
|
-
const description = body.props.find((p) => p.key === "description")?.value;
|
|
360
|
+
const title = toSingleLine(body.props.find((p) => p.key === "title")?.value);
|
|
361
|
+
const description = removeIndent(body.props.find((p) => p.key === "description")?.value);
|
|
362
362
|
const tags = this.convertTags(body);
|
|
363
363
|
const links = body.props.filter(ast.isLinkProperty).map((p) => p.value);
|
|
364
364
|
const basic = {
|
|
@@ -371,7 +371,7 @@ export class LikeC4ModelParser {
|
|
|
371
371
|
...isNonEmptyArray(links) && { links },
|
|
372
372
|
rules: body.rules.flatMap((n) => {
|
|
373
373
|
try {
|
|
374
|
-
return this.parseViewRule(n);
|
|
374
|
+
return isValid2(n) ? this.parseViewRule(n, isValid2) : [];
|
|
375
375
|
} catch (e) {
|
|
376
376
|
logWarnError(e);
|
|
377
377
|
return [];
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { invariant, nonexhaustive } from "@likec4/core";
|
|
2
|
-
import { Range
|
|
2
|
+
import { Range } from "vscode-languageserver-protocol";
|
|
3
|
+
import { changeElementStyle } from "./changeElementStyle.js";
|
|
3
4
|
import { changeViewLayout } from "./changeViewLayout.js";
|
|
4
|
-
import { changeViewStyle } from "./changeViewStyle.js";
|
|
5
5
|
function unionRangeOfAllEdits(edits) {
|
|
6
6
|
let start = Number.MAX_SAFE_INTEGER;
|
|
7
7
|
let end = Number.MIN_SAFE_INTEGER;
|
|
8
|
-
let startCharacter =
|
|
9
|
-
let endCharacter =
|
|
8
|
+
let startCharacter = Number.MAX_SAFE_INTEGER;
|
|
9
|
+
let endCharacter = Number.MIN_SAFE_INTEGER;
|
|
10
10
|
for (const { range } of edits) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
if (range.start.line <= start) {
|
|
12
|
+
if (start == range.start.line) {
|
|
13
|
+
startCharacter = Math.min(range.start.character, startCharacter);
|
|
14
|
+
} else {
|
|
15
|
+
start = range.start.line;
|
|
16
|
+
startCharacter = range.start.character;
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
if (end <= range.end.line) {
|
|
20
|
+
if (end == range.end.line) {
|
|
21
|
+
endCharacter = Math.max(range.end.character, endCharacter);
|
|
22
|
+
} else {
|
|
23
|
+
end = range.end.line;
|
|
24
|
+
endCharacter = range.end.character;
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
28
|
return Range.create(start, startCharacter, end, endCharacter);
|
|
@@ -41,9 +49,9 @@ export class LikeC4ModelChanges {
|
|
|
41
49
|
const applyResult = await lspConnection.workspace.applyEdit({
|
|
42
50
|
label: `LikeC4 - change view ${changeView.viewId}`,
|
|
43
51
|
edit: {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
changes: {
|
|
53
|
+
[textDocument.uri]: edits
|
|
54
|
+
}
|
|
47
55
|
}
|
|
48
56
|
});
|
|
49
57
|
if (!applyResult.applied) {
|
|
@@ -65,21 +73,11 @@ export class LikeC4ModelChanges {
|
|
|
65
73
|
const edits = [];
|
|
66
74
|
for (const change of changes) {
|
|
67
75
|
switch (change.op) {
|
|
68
|
-
case "change-
|
|
69
|
-
edits.push(...
|
|
70
|
-
...lookup,
|
|
71
|
-
targets: change.targets,
|
|
72
|
-
key: "color",
|
|
73
|
-
value: change.color
|
|
74
|
-
}));
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case "change-shape": {
|
|
78
|
-
edits.push(...changeViewStyle(this.services, {
|
|
76
|
+
case "change-element-style": {
|
|
77
|
+
edits.push(...changeElementStyle(this.services, {
|
|
79
78
|
...lookup,
|
|
80
79
|
targets: change.targets,
|
|
81
|
-
|
|
82
|
-
value: change.shape
|
|
80
|
+
style: change.style
|
|
83
81
|
}));
|
|
84
82
|
break;
|
|
85
83
|
}
|
|
@@ -2,14 +2,14 @@ import { type Fqn, type NonEmptyArray } from '@likec4/core';
|
|
|
2
2
|
import { TextEdit } from 'vscode-languageserver-protocol';
|
|
3
3
|
import { ast, type ParsedAstElementView, type ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
|
-
type
|
|
5
|
+
import type { ChangeView } from '../protocol';
|
|
6
|
+
type ChangeElementStyleArg = {
|
|
6
7
|
view: ParsedAstElementView;
|
|
7
8
|
doc: ParsedLikeC4LangiumDocument;
|
|
8
9
|
viewAst: ast.ElementView;
|
|
9
|
-
key: string;
|
|
10
|
-
value: string;
|
|
11
10
|
targets: NonEmptyArray<Fqn>;
|
|
11
|
+
style: ChangeView.ChangeElementStyle['style'];
|
|
12
12
|
};
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function changeElementStyle(services: LikeC4Services, { view, viewAst, targets, style }: ChangeElementStyleArg): TextEdit[];
|
|
14
14
|
export {};
|
|
15
|
-
//# sourceMappingURL=
|
|
15
|
+
//# sourceMappingURL=changeElementStyle.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { invariant, isAncestor, nonNullable } from "@likec4/core";
|
|
2
|
+
import { GrammarUtils } from "langium";
|
|
3
|
+
import { entries, findLast, isNumber, last } from "remeda";
|
|
4
|
+
import { TextEdit } from "vscode-languageserver-protocol";
|
|
5
|
+
import { ast } from "../ast.js";
|
|
6
|
+
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
|
|
7
|
+
const asViewStyleRule = (target, style, indent = 0) => {
|
|
8
|
+
const indentStr = indent > 0 ? " ".repeat(indent) : "";
|
|
9
|
+
return [
|
|
10
|
+
indentStr + `style ${target} {`,
|
|
11
|
+
...entries(style).map(([key, value]) => indentStr + ` ${key} ${value}`),
|
|
12
|
+
indentStr + `}`
|
|
13
|
+
].join("\n");
|
|
14
|
+
};
|
|
15
|
+
const isMatchingViewRule = (fqn, index) => (rule) => {
|
|
16
|
+
if (!ast.isViewRuleStyle(rule)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const [target, ...rest] = rule.targets;
|
|
20
|
+
if (!target || rest.length > 0 || !ast.isElementRef(target)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const ref = target.el.ref;
|
|
24
|
+
const _fqn = ref ? index.getFqn(ref) : null;
|
|
25
|
+
return _fqn === fqn;
|
|
26
|
+
};
|
|
27
|
+
export function changeElementStyle(services, {
|
|
28
|
+
view,
|
|
29
|
+
viewAst,
|
|
30
|
+
targets,
|
|
31
|
+
style
|
|
32
|
+
}) {
|
|
33
|
+
const viewCstNode = viewAst.$cstNode;
|
|
34
|
+
invariant(viewCstNode, "viewCstNode");
|
|
35
|
+
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? last(viewAst.body.props)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.start;
|
|
36
|
+
invariant(insertPos, "insertPos is not defined");
|
|
37
|
+
const indent = viewCstNode.range.start.character + 2;
|
|
38
|
+
const fqnIndex = services.likec4.FqnIndex;
|
|
39
|
+
const styleRules = viewAst.body.rules.filter(ast.isViewRuleStyle);
|
|
40
|
+
const viewOf = view.viewOf;
|
|
41
|
+
const existing = [];
|
|
42
|
+
const insert = [];
|
|
43
|
+
targets.forEach((target) => {
|
|
44
|
+
const rule = findLast(styleRules, isMatchingViewRule(target, fqnIndex));
|
|
45
|
+
const fqn = viewOf && isAncestor(viewOf, target) ? target.substring(viewOf.length + 1) : target;
|
|
46
|
+
if (rule) {
|
|
47
|
+
existing.push({ fqn, rule });
|
|
48
|
+
} else {
|
|
49
|
+
insert.push({ fqn });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const modifiedRange = {
|
|
53
|
+
start: insertPos,
|
|
54
|
+
end: insertPos
|
|
55
|
+
};
|
|
56
|
+
const includeRange = (range) => {
|
|
57
|
+
if (range.start.line <= modifiedRange.start.line) {
|
|
58
|
+
if (range.start.line == modifiedRange.start.line) {
|
|
59
|
+
modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character);
|
|
60
|
+
} else {
|
|
61
|
+
modifiedRange.start.line = range.start.line;
|
|
62
|
+
modifiedRange.start.character = range.start.character;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (range.end.line >= modifiedRange.end.line) {
|
|
66
|
+
if (range.end.line == modifiedRange.end.line) {
|
|
67
|
+
modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character);
|
|
68
|
+
} else {
|
|
69
|
+
modifiedRange.end.line = range.end.line;
|
|
70
|
+
modifiedRange.end.character = range.end.character;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const edits = [];
|
|
75
|
+
if (insert.length > 0) {
|
|
76
|
+
const linesToInsert = [
|
|
77
|
+
"",
|
|
78
|
+
...insert.map(({ fqn }) => asViewStyleRule(fqn, style, indent))
|
|
79
|
+
];
|
|
80
|
+
edits.push(
|
|
81
|
+
TextEdit.insert(
|
|
82
|
+
insertPos,
|
|
83
|
+
linesToInsert.join("\n")
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
modifiedRange.start = {
|
|
87
|
+
line: insertPos.line + 1,
|
|
88
|
+
character: indent
|
|
89
|
+
};
|
|
90
|
+
modifiedRange.end = {
|
|
91
|
+
line: insertPos.line + linesToInsert.length,
|
|
92
|
+
character: indent + (last(linesToInsert)?.length ?? 0) + 1
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (existing.length > 0) {
|
|
96
|
+
for (const { rule } of existing) {
|
|
97
|
+
const ruleCstNode = rule.$cstNode;
|
|
98
|
+
invariant(ruleCstNode, "RuleCstNode not found");
|
|
99
|
+
for (const [key, _value] of entries.strict(style)) {
|
|
100
|
+
const value = isNumber(_value) ? _value.toString() + "%" : _value;
|
|
101
|
+
const ruleProp = rule.styleprops.find((p) => p.key === key);
|
|
102
|
+
if (ruleProp && ruleProp.$cstNode) {
|
|
103
|
+
const { range: { start, end } } = nonNullable(
|
|
104
|
+
findNodeForProperty(ruleProp.$cstNode, "value"),
|
|
105
|
+
"cant find value cst node"
|
|
106
|
+
);
|
|
107
|
+
includeRange({
|
|
108
|
+
start,
|
|
109
|
+
end: {
|
|
110
|
+
line: end.line,
|
|
111
|
+
character: start.character + value.length + 1
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
edits.push(TextEdit.replace({ start, end }, value));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const insertPos2 = findNodeForKeyword(ruleCstNode, "{")?.range.end;
|
|
118
|
+
invariant(insertPos2, "Opening brace not found");
|
|
119
|
+
const indentStr = " ".repeat(2 + ruleCstNode.range.start.character);
|
|
120
|
+
const insertKeyValue = indentStr + key + " " + value;
|
|
121
|
+
edits.push(
|
|
122
|
+
TextEdit.insert(
|
|
123
|
+
insertPos2,
|
|
124
|
+
"\n" + insertKeyValue
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
includeRange({
|
|
128
|
+
start: {
|
|
129
|
+
line: insertPos2.line + 1,
|
|
130
|
+
character: indentStr.length + 1
|
|
131
|
+
},
|
|
132
|
+
end: {
|
|
133
|
+
line: insertPos2.line + 1,
|
|
134
|
+
character: insertKeyValue.length + 1
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return edits;
|
|
141
|
+
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AutoLayoutDirection, ComputedView, ElementShape, Fqn, LikeC4ComputedModel, LikeC4Model, NonEmptyArray, RelationID, ThemeColor, ViewID } from '@likec4/core';
|
|
1
|
+
import type { AutoLayoutDirection, BorderStyle, ComputedView, ElementShape, Fqn, LikeC4ComputedModel, LikeC4Model, NonEmptyArray, RelationID, ThemeColor, ViewID } from '@likec4/core';
|
|
2
2
|
import type { DocumentUri, Location } from 'vscode-languageserver-protocol';
|
|
3
3
|
import { NotificationType, RequestType, RequestType0 } from 'vscode-languageserver-protocol';
|
|
4
4
|
export declare const onDidChangeModel: NotificationType<string>;
|
|
@@ -33,22 +33,22 @@ export type LocateParams = {
|
|
|
33
33
|
export declare const locate: RequestType<LocateParams, Location | null, void>;
|
|
34
34
|
export type LocateRequest = typeof locate;
|
|
35
35
|
export declare namespace ChangeView {
|
|
36
|
-
interface ChangeColor {
|
|
37
|
-
op: 'change-color';
|
|
38
|
-
color: ThemeColor;
|
|
39
|
-
targets: NonEmptyArray<Fqn>;
|
|
40
|
-
}
|
|
41
|
-
interface ChangeShape {
|
|
42
|
-
op: 'change-shape';
|
|
43
|
-
shape: ElementShape;
|
|
44
|
-
targets: NonEmptyArray<Fqn>;
|
|
45
|
-
}
|
|
46
36
|
interface ChangeAutoLayout {
|
|
47
37
|
op: 'change-autolayout';
|
|
48
38
|
layout: AutoLayoutDirection;
|
|
49
39
|
}
|
|
40
|
+
interface ChangeElementStyle {
|
|
41
|
+
op: 'change-element-style';
|
|
42
|
+
style: {
|
|
43
|
+
border?: BorderStyle;
|
|
44
|
+
opacity?: number;
|
|
45
|
+
shape?: ElementShape;
|
|
46
|
+
color?: ThemeColor;
|
|
47
|
+
};
|
|
48
|
+
targets: NonEmptyArray<Fqn>;
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
|
-
export type ChangeView = ChangeView.
|
|
51
|
+
export type ChangeView = ChangeView.ChangeAutoLayout | ChangeView.ChangeElementStyle;
|
|
52
52
|
export interface ChangeViewRequestParams {
|
|
53
53
|
viewId: ViewID;
|
|
54
54
|
changes: NonEmptyArray<ChangeView>;
|
|
@@ -95,9 +95,11 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
localScope.
|
|
98
|
+
if (nestedScopes.size > 0) {
|
|
99
|
+
for (const [name, descriptions] of nestedScopes.entriesGroupedByKey()) {
|
|
100
|
+
if (!localScope.has(name) && descriptions.length === 1) {
|
|
101
|
+
localScope.add(name, descriptions[0]);
|
|
102
|
+
}
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
scopes.addAll(container, localScope.values());
|
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.1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"test": "vitest run"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@likec4/core": "1.1.
|
|
78
|
-
"@likec4/graph": "1.1.
|
|
77
|
+
"@likec4/core": "1.1.1",
|
|
78
|
+
"@likec4/graph": "1.1.1",
|
|
79
79
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
80
80
|
"fast-equals": "^5.0.1",
|
|
81
81
|
"langium": "^3.0.0",
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { invariant, isAncestor, nonNullable } from "@likec4/core";
|
|
2
|
-
import { GrammarUtils } from "langium";
|
|
3
|
-
import { findLast, last, partition } from "remeda";
|
|
4
|
-
import { TextEdit } from "vscode-languageserver-protocol";
|
|
5
|
-
import { ast } from "../ast.js";
|
|
6
|
-
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
|
|
7
|
-
const asViewStyleRule = (target, key, value, indent = 0) => {
|
|
8
|
-
const indentStr = indent > 0 ? " ".repeat(indent) : "";
|
|
9
|
-
return [
|
|
10
|
-
indentStr + `style ${target} {`,
|
|
11
|
-
indentStr + ` ${key} ${value}`,
|
|
12
|
-
indentStr + `}`
|
|
13
|
-
].join("\n");
|
|
14
|
-
};
|
|
15
|
-
const isMatchingViewRule = (fqn, index) => (rule) => {
|
|
16
|
-
if (!ast.isViewRuleStyle(rule)) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
const [target, ...rest] = rule.targets;
|
|
20
|
-
if (!target || rest.length > 0 || !ast.isElementRef(target)) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
const ref = target.el.ref;
|
|
24
|
-
const _fqn = ref ? index.getFqn(ref) : null;
|
|
25
|
-
return _fqn === fqn;
|
|
26
|
-
};
|
|
27
|
-
export function changeViewStyle(services, {
|
|
28
|
-
view,
|
|
29
|
-
viewAst,
|
|
30
|
-
targets,
|
|
31
|
-
key,
|
|
32
|
-
value
|
|
33
|
-
}) {
|
|
34
|
-
const viewCstNode = viewAst.$cstNode;
|
|
35
|
-
invariant(viewCstNode, "viewCstNode");
|
|
36
|
-
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? last(viewAst.body.props)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.start;
|
|
37
|
-
invariant(insertPos, "insertPos is not defined");
|
|
38
|
-
const indent = viewCstNode.range.start.character + 2;
|
|
39
|
-
const fqnIndex = services.likec4.FqnIndex;
|
|
40
|
-
const styleRules = viewAst.body.rules.filter(ast.isViewRuleStyle);
|
|
41
|
-
const viewOf = view.viewOf;
|
|
42
|
-
const targetsWithRules = targets.map((target) => {
|
|
43
|
-
const rule = findLast(styleRules, isMatchingViewRule(target, fqnIndex));
|
|
44
|
-
const fqn = viewOf && isAncestor(viewOf, target) ? target.substring(viewOf.length + 1) : target;
|
|
45
|
-
if (rule) {
|
|
46
|
-
return {
|
|
47
|
-
fqn,
|
|
48
|
-
rule
|
|
49
|
-
};
|
|
50
|
-
} else {
|
|
51
|
-
return {
|
|
52
|
-
fqn
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
const [existing, insert] = partition(
|
|
57
|
-
targetsWithRules,
|
|
58
|
-
(a) => !!a.rule
|
|
59
|
-
);
|
|
60
|
-
const modifiedRange = {
|
|
61
|
-
start: insertPos,
|
|
62
|
-
end: insertPos
|
|
63
|
-
};
|
|
64
|
-
const includeRange = (range) => {
|
|
65
|
-
if (range.start.line < modifiedRange.start.line) {
|
|
66
|
-
modifiedRange.start = range.start;
|
|
67
|
-
}
|
|
68
|
-
if (range.end.line > modifiedRange.end.line) {
|
|
69
|
-
modifiedRange.end = range.end;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
const edits = [];
|
|
73
|
-
if (insert.length > 0) {
|
|
74
|
-
const linesToInsert = [
|
|
75
|
-
"",
|
|
76
|
-
...insert.map(({ fqn }) => asViewStyleRule(fqn, key, value, indent))
|
|
77
|
-
];
|
|
78
|
-
edits.push(
|
|
79
|
-
TextEdit.insert(
|
|
80
|
-
insertPos,
|
|
81
|
-
linesToInsert.join("\n")
|
|
82
|
-
)
|
|
83
|
-
);
|
|
84
|
-
modifiedRange.end = {
|
|
85
|
-
line: modifiedRange.end.line + linesToInsert.length,
|
|
86
|
-
character: last(linesToInsert)?.length ?? insertPos.character
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (existing.length > 0) {
|
|
90
|
-
for (const { rule } of existing) {
|
|
91
|
-
const ruleCstNode = rule.$cstNode;
|
|
92
|
-
invariant(ruleCstNode, "RuleCstNode not found");
|
|
93
|
-
const ruleProp = rule.styleprops.find((p) => p.key === key);
|
|
94
|
-
if (ruleProp && ruleProp.$cstNode) {
|
|
95
|
-
const { range: { start, end } } = nonNullable(
|
|
96
|
-
findNodeForProperty(ruleProp.$cstNode, "value"),
|
|
97
|
-
"cant find value cst node"
|
|
98
|
-
);
|
|
99
|
-
includeRange(ruleProp.$cstNode.range);
|
|
100
|
-
edits.push(TextEdit.replace({ start, end }, value));
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const insertPos2 = findNodeForKeyword(ruleCstNode, "{")?.range.end;
|
|
104
|
-
invariant(insertPos2, "Opening brace not found");
|
|
105
|
-
const indentStr = " ".repeat(2 + ruleCstNode.range.start.character);
|
|
106
|
-
const insertKeyValue = indentStr + key + " " + value;
|
|
107
|
-
edits.push(
|
|
108
|
-
TextEdit.insert(
|
|
109
|
-
insertPos2,
|
|
110
|
-
"\n" + insertKeyValue
|
|
111
|
-
)
|
|
112
|
-
);
|
|
113
|
-
includeRange({
|
|
114
|
-
start: insertPos2,
|
|
115
|
-
end: {
|
|
116
|
-
line: insertPos2.line + 1,
|
|
117
|
-
character: insertKeyValue.length
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return edits;
|
|
123
|
-
}
|