@likec4/language-server 1.0.2 → 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.
@@ -8,11 +8,11 @@ import {
8
8
  cleanParsedModel,
9
9
  ElementViewOps,
10
10
  isFqnIndexedDocument,
11
+ parseAstOpacityProperty,
11
12
  resolveRelationPoints,
12
13
  streamModel,
13
14
  toAutoLayout,
14
15
  toElementStyle,
15
- toElementStyleExcludeDefaults,
16
16
  toRelationshipStyleExcludeDefaults
17
17
  } from "../ast.js";
18
18
  import { elementRef, getFqnElementRef } from "../elementRef.js";
@@ -56,22 +56,22 @@ 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(isValid);
62
- const element_specs = specifications.flatMap((s) => s.elements.filter(isValid));
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;
66
66
  c4Specification.kinds[kindName] = {
67
67
  ...c4Specification.kinds[kindName],
68
- ...toElementStyleExcludeDefaults(style?.props)
68
+ ...toElementStyle(style?.props)
69
69
  };
70
70
  } catch (e) {
71
71
  logWarnError(e);
72
72
  }
73
73
  }
74
- const relations_specs = specifications.flatMap((s) => s.relationships.filter(isValid));
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;
@@ -110,7 +110,7 @@ export class LikeC4ModelParser {
110
110
  const kind = astNode.kind.$refText;
111
111
  const tags = this.convertTags(astNode.body);
112
112
  const stylePropsAst = astNode.body?.props.find(ast.isStyleProperties)?.props;
113
- const styleProps = toElementStyleExcludeDefaults(stylePropsAst);
113
+ const style = toElementStyle(stylePropsAst);
114
114
  const astPath = this.getAstNodePath(astNode);
115
115
  let [title, description, technology] = astNode.props ?? [];
116
116
  const bodyProps = astNode.body?.props.filter(ast.isElementStringProperty) ?? [];
@@ -127,7 +127,7 @@ export class LikeC4ModelParser {
127
127
  ...links && isNonEmptyArray(links) && { links },
128
128
  ...isTruthy(technology) && { technology },
129
129
  ...isTruthy(description) && { description },
130
- ...styleProps
130
+ style
131
131
  };
132
132
  }
133
133
  parseRelation(astNode) {
@@ -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) => isValid(v) ? v.views.filter(isValid) : []);
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);
@@ -247,6 +247,10 @@ export class LikeC4ModelParser {
247
247
  acc.custom[prop.key] = value.trim();
248
248
  return acc;
249
249
  }
250
+ if (ast.isIconProperty(prop)) {
251
+ acc.custom[prop.key] = prop.value;
252
+ return acc;
253
+ }
250
254
  if (ast.isColorProperty(prop)) {
251
255
  acc.custom[prop.key] = prop.value;
252
256
  return acc;
@@ -255,6 +259,14 @@ export class LikeC4ModelParser {
255
259
  acc.custom[prop.key] = prop.value;
256
260
  return acc;
257
261
  }
262
+ if (ast.isBorderProperty(prop)) {
263
+ acc.custom[prop.key] = prop.value;
264
+ return acc;
265
+ }
266
+ if (ast.isOpacityProperty(prop)) {
267
+ acc.custom[prop.key] = parseAstOpacityProperty(prop);
268
+ return acc;
269
+ }
258
270
  nonexhaustive(prop);
259
271
  },
260
272
  {
@@ -295,11 +307,11 @@ export class LikeC4ModelParser {
295
307
  }
296
308
  nonexhaustive(astNode);
297
309
  }
298
- parseViewRule(astRule) {
310
+ parseViewRule(astRule, isValid2) {
299
311
  if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
300
312
  const exprs = astRule.expressions.flatMap((n) => {
301
313
  try {
302
- return this.parsePredicateExpr(n);
314
+ return isValid2(n) ? this.parsePredicateExpr(n) : [];
303
315
  } catch (e) {
304
316
  logWarnError(e);
305
317
  return [];
@@ -323,7 +335,7 @@ export class LikeC4ModelParser {
323
335
  }
324
336
  nonexhaustive(astRule);
325
337
  }
326
- parseElementView(astNode) {
338
+ parseElementView(astNode, isValid2) {
327
339
  const body = astNode.body;
328
340
  invariant(body, "ElementView body is not defined");
329
341
  const astPath = this.getAstNodePath(astNode);
@@ -345,8 +357,8 @@ export class LikeC4ModelParser {
345
357
  viewOf ?? ""
346
358
  );
347
359
  }
348
- const title = body.props.find((p) => p.key === "title")?.value;
349
- 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);
350
362
  const tags = this.convertTags(body);
351
363
  const links = body.props.filter(ast.isLinkProperty).map((p) => p.value);
352
364
  const basic = {
@@ -359,7 +371,7 @@ export class LikeC4ModelParser {
359
371
  ...isNonEmptyArray(links) && { links },
360
372
  rules: body.rules.flatMap((n) => {
361
373
  try {
362
- return this.parseViewRule(n);
374
+ return isValid2(n) ? this.parseViewRule(n, isValid2) : [];
363
375
  } catch (e) {
364
376
  logWarnError(e);
365
377
  return [];
@@ -1,20 +1,28 @@
1
1
  import { invariant, nonexhaustive } from "@likec4/core";
2
- import { Range, TextDocumentEdit } from "vscode-languageserver-protocol";
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 = 0;
9
- let endCharacter = 0;
8
+ let startCharacter = Number.MAX_SAFE_INTEGER;
9
+ let endCharacter = Number.MIN_SAFE_INTEGER;
10
10
  for (const { range } of edits) {
11
- start = Math.min(start, range.start.line);
12
- if (start === range.start.line) {
13
- startCharacter = range.start.character;
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
- end = Math.max(end, range.end.line);
16
- if (end === range.end.line) {
17
- endCharacter = range.end.character;
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
- documentChanges: [
45
- TextDocumentEdit.create(textDocument, edits)
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-color": {
69
- edits.push(...changeViewStyle(this.services, {
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
- key: "shape",
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 ChangeViewStyleArg = {
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 changeViewStyle(services: LikeC4Services, { view, viewAst, targets, key, value }: ChangeViewStyleArg): TextEdit[];
13
+ export declare function changeElementStyle(services: LikeC4Services, { view, viewAst, targets, style }: ChangeElementStyleArg): TextEdit[];
14
14
  export {};
15
- //# sourceMappingURL=changeViewStyle.d.ts.map
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
+ }
@@ -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.ChangeColor | ChangeView.ChangeShape | ChangeView.ChangeAutoLayout;
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
- for (const [name, descriptions] of nestedScopes.entriesGroupedByKey()) {
99
- if (!localScope.has(name) && descriptions.length === 1) {
100
- localScope.add(name, descriptions[0]);
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());
@@ -1,5 +1,6 @@
1
1
  import { logger } from "../logger.js";
2
2
  import { elementChecks } from "./element.js";
3
+ import { opacityPropertyRuleChecks } from "./property-checks.js";
3
4
  import { relationChecks } from "./relation.js";
4
5
  import {
5
6
  elementKindChecks,
@@ -20,6 +21,7 @@ export function registerValidationChecks(services) {
20
21
  logger.info("registerValidationChecks");
21
22
  const registry = services.validation.ValidationRegistry;
22
23
  registry.register({
24
+ OpacityProperty: opacityPropertyRuleChecks(services),
23
25
  SpecificationRule: specificationRuleChecks(services),
24
26
  Model: modelRuleChecks(services),
25
27
  ModelViews: modelViewsChecks(services),
@@ -0,0 +1,5 @@
1
+ import type { ValidationCheck } from 'langium';
2
+ import type { ast } from '../ast';
3
+ import type { LikeC4Services } from '../module';
4
+ export declare const opacityPropertyRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.OpacityProperty>;
5
+ //# sourceMappingURL=property-checks.d.ts.map
@@ -0,0 +1,11 @@
1
+ export const opacityPropertyRuleChecks = (_) => {
2
+ return (node, accept) => {
3
+ const opacity = parseFloat(node.value);
4
+ if (isNaN(opacity) || opacity < 0 || opacity > 100) {
5
+ accept("warning", `Value ignored, must be between 0% and 100%`, {
6
+ node,
7
+ property: "value"
8
+ });
9
+ }
10
+ };
11
+ };
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.0.2",
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.0.2",
78
- "@likec4/graph": "1.0.2",
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",
@@ -95,7 +95,7 @@
95
95
  "@types/node": "^20.12.7",
96
96
  "@types/object-hash": "^3.0.6",
97
97
  "@types/string-hash": "^1",
98
- "execa": "^8.0.1",
98
+ "execa": "^9.1.0",
99
99
  "langium-cli": "3.0.3",
100
100
  "npm-run-all2": "^6.1.2",
101
101
  "typescript": "^5.4.5",
@@ -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
- }