@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.
Files changed (39) hide show
  1. package/contrib/likec4.monarch.ts +4 -4
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/dist/ast.d.ts +27 -11
  4. package/dist/ast.js +11 -7
  5. package/dist/generated/ast.d.ts +75 -19
  6. package/dist/generated/ast.js +96 -7
  7. package/dist/generated/grammar.js +1 -1
  8. package/dist/lsp/CodeLensProvider.js +5 -2
  9. package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
  10. package/dist/lsp/DocumentSymbolProvider.js +14 -9
  11. package/dist/lsp/SemanticTokenProvider.js +1 -1
  12. package/dist/model/model-builder.js +28 -15
  13. package/dist/model/model-locator.d.ts +2 -2
  14. package/dist/model/model-locator.js +12 -16
  15. package/dist/model/model-parser.d.ts +2 -0
  16. package/dist/model/model-parser.js +143 -33
  17. package/dist/model-change/ModelChanges.d.ts +2 -1
  18. package/dist/model-change/ModelChanges.js +26 -20
  19. package/dist/model-change/changeElementStyle.d.ts +8 -5
  20. package/dist/model-change/changeElementStyle.js +23 -23
  21. package/dist/model-change/changeViewLayout.d.ts +4 -4
  22. package/dist/model-change/changeViewLayout.js +4 -5
  23. package/dist/references/scope-computation.js +31 -28
  24. package/dist/shared/NodeKindProvider.js +4 -2
  25. package/dist/validation/dynamic-view-rule.d.ts +5 -0
  26. package/dist/validation/dynamic-view-rule.js +32 -0
  27. package/dist/validation/dynamic-view-step.d.ts +5 -0
  28. package/dist/validation/dynamic-view-step.js +33 -0
  29. package/dist/validation/index.js +5 -1
  30. package/dist/validation/view-predicates/expanded-element.js +1 -0
  31. package/dist/validation/view-predicates/outgoing.js +2 -2
  32. package/dist/validation/view.d.ts +1 -1
  33. package/dist/validation/view.js +1 -3
  34. package/dist/view-utils/assignNavigateTo.d.ts +1 -1
  35. package/dist/view-utils/assignNavigateTo.js +2 -1
  36. package/dist/view-utils/resolve-extended-views.d.ts +2 -2
  37. package/dist/view-utils/resolve-relative-paths.d.ts +2 -2
  38. package/dist/view-utils/resolve-relative-paths.js +2 -3
  39. 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
- const targetNode = (node.kind ? findNodeForProperty(node.$cstNode, "kind") : findNodeForKeyword(node.$cstNode, "->")) ?? findNodeForProperty(node.$cstNode, "target");
66
- return targetNode ? {
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
- start: targetNode.range.start,
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.isElementView(viewAst)) {
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
- this.parseSpecification(doc);
54
- this.parseModel(doc);
55
- this.parseViews(doc);
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(isValid2);
62
- const element_specs = specifications.flatMap((s) => s.elements.filter(isValid2));
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(isValid2));
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 { isValid: isValid2 } = checksFromDiagnostics(doc);
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
- const v = this.parseElementView(view, isValid2);
168
- doc.c4Views.push(v);
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, isValid2) {
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 isValid2(n) ? this.parsePredicateExpr(n) : [];
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
- parseElementView(astNode, isValid2) {
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 basic = {
390
+ const view = {
391
+ __: "element",
365
392
  id,
366
393
  astPath,
367
394
  ...viewOf && { viewOf },
368
- ...title && { title },
369
- ...description && { description },
370
- ...tags && { tags },
371
- ...isNonEmptyArray(links) && { links },
395
+ title,
396
+ description,
397
+ tags,
398
+ links: isNonEmptyArray(links) ? links : null,
372
399
  rules: body.rules.flatMap((n) => {
373
400
  try {
374
- return isValid2(n) ? this.parseViewRule(n, isValid2) : [];
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
- ElementViewOps.writeId(astNode, basic.id);
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
- return basic;
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(edits) {
6
- let start = Number.MAX_SAFE_INTEGER;
7
- let end = Number.MIN_SAFE_INTEGER;
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 { range } of edits) {
11
- if (range.start.line <= start) {
12
- if (start == range.start.line) {
13
- startCharacter = Math.min(range.start.character, startCharacter);
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
- start = range.start.line;
16
- startCharacter = range.start.character;
15
+ startLine = start.line;
16
+ startCharacter = start.character;
17
17
  }
18
18
  }
19
- if (end <= range.end.line) {
20
- if (end == range.end.line) {
21
- endCharacter = Math.max(range.end.character, endCharacter);
19
+ if (endLine <= end.line) {
20
+ if (endLine == end.line) {
21
+ endCharacter = Math.max(end.character, endCharacter);
22
22
  } else {
23
- end = range.end.line;
24
- endCharacter = range.end.character;
23
+ endLine = end.line;
24
+ endCharacter = end.character;
25
25
  }
26
26
  }
27
27
  }
28
- return Range.create(start, startCharacter, end, endCharacter);
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.push(...changeElementStyle(this.services, {
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
- edits.push(...changeViewLayout(this.services, {
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 ParsedAstElementView, type ParsedLikeC4LangiumDocument } from '../ast';
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: ParsedAstElementView;
7
+ view: ParsedAstView;
8
8
  doc: ParsedLikeC4LangiumDocument;
9
- viewAst: ast.ElementView;
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): TextEdit[];
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, findLast, isNumber, last } from "remeda";
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(([key, value]) => indentStr + ` ${key} ${value}`),
11
+ ...entries.strict(style).map(
12
+ ([key, value]) => indentStr + ` ${key} ${key === "opacity" ? value.toString() + "%" : value}`
13
+ ),
12
14
  indentStr + `}`
13
- ].join("\n");
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 ?? last(viewAst.body.props)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.start;
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.filter(ast.isViewRuleStyle);
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.line = range.start.line;
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.line = range.end.line;
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: indent + (last(linesToInsert)?.length ?? 0) + 1
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 = isNumber(_value) ? _value.toString() + "%" : _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: end.line,
111
- character: start.character + value.length + 1
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 + 1
127
+ character: indentStr.length
131
128
  },
132
129
  end: {
133
130
  line: insertPos2.line + 1,
134
- character: insertKeyValue.length + 1
131
+ character: insertKeyValue.length
135
132
  }
136
133
  });
137
134
  }
138
135
  }
139
136
  }
140
- return edits;
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 ParsedAstElementView, type ParsedLikeC4LangiumDocument } from '../ast';
3
+ import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast';
4
4
  import type { LikeC4Services } from '../module';
5
5
  type ChangeViewLayoutArg = {
6
- view: ParsedAstElementView;
6
+ view: ParsedAstView;
7
7
  doc: ParsedLikeC4LangiumDocument;
8
- viewAst: ast.ElementView;
8
+ viewAst: ast.LikeC4View;
9
9
  layout: AutoLayoutDirection;
10
10
  };
11
- export declare function changeViewLayout(services: LikeC4Services, { view, viewAst, layout }: ChangeViewLayoutArg): TextEdit[];
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 [TextEdit.replace(directionCstNode.range, newlayout)];
18
+ return TextEdit.replace(directionCstNode.range, newlayout);
20
19
  }
21
- return [TextEdit.replace(existingRule.$cstNode.range, `autoLayout ${newlayout}`)];
20
+ return TextEdit.replace(existingRule.$cstNode.range, `autoLayout ${newlayout}`);
22
21
  }
23
- const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? last(viewAst.body.props)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.start;
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 [TextEdit.insert(insertPos, insert)];
28
+ return TextEdit.insert(insertPos, insert);
30
29
  }