@likec4/language-server 1.1.0 → 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 (42) hide show
  1. package/contrib/likec4.monarch.ts +4 -4
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/dist/ast.d.ts +28 -11
  4. package/dist/ast.js +14 -13
  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 +68 -45
  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 +140 -30
  17. package/dist/model-change/ModelChanges.d.ts +2 -1
  18. package/dist/model-change/ModelChanges.js +39 -35
  19. package/dist/model-change/changeElementStyle.d.ts +18 -0
  20. package/dist/model-change/changeElementStyle.js +141 -0
  21. package/dist/model-change/changeViewLayout.d.ts +4 -4
  22. package/dist/model-change/changeViewLayout.js +4 -5
  23. package/dist/protocol.d.ts +12 -12
  24. package/dist/references/scope-computation.js +36 -31
  25. package/dist/shared/NodeKindProvider.js +4 -2
  26. package/dist/validation/dynamic-view-rule.d.ts +5 -0
  27. package/dist/validation/dynamic-view-rule.js +32 -0
  28. package/dist/validation/dynamic-view-step.d.ts +5 -0
  29. package/dist/validation/dynamic-view-step.js +33 -0
  30. package/dist/validation/index.js +5 -1
  31. package/dist/validation/view-predicates/expanded-element.js +1 -0
  32. package/dist/validation/view-predicates/outgoing.js +2 -2
  33. package/dist/validation/view.d.ts +1 -1
  34. package/dist/validation/view.js +1 -3
  35. package/dist/view-utils/assignNavigateTo.d.ts +1 -1
  36. package/dist/view-utils/assignNavigateTo.js +2 -1
  37. package/dist/view-utils/resolve-extended-views.d.ts +2 -2
  38. package/dist/view-utils/resolve-relative-paths.d.ts +2 -2
  39. package/dist/view-utils/resolve-relative-paths.js +2 -3
  40. package/package.json +6 -6
  41. package/dist/model-change/changeViewStyle.d.ts +0 -15
  42. package/dist/model-change/changeViewStyle.js +0 -123
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  compareByFqnHierarchically,
3
+ isElementView,
3
4
  isStrictElementView,
4
5
  parentFqn
5
6
  } from "@likec4/core";
6
- import { computeView, LikeC4ModelGraph } from "@likec4/graph";
7
+ import { computeDynamicView, computeView, LikeC4ModelGraph } from "@likec4/graph";
7
8
  import { deepEqual as eq } from "fast-equals";
8
9
  import { DocumentState, interruptAndCheck } from "langium";
9
10
  import {
@@ -32,14 +33,14 @@ function buildModel(services, docs) {
32
33
  relationships: {}
33
34
  };
34
35
  forEach(map(docs, prop("c4Specification")), (spec) => {
35
- Object.assign(c4Specification.kinds, spec.kinds), Object.assign(c4Specification.relationships, spec.relationships);
36
+ Object.assign(c4Specification.kinds, spec.kinds);
37
+ Object.assign(c4Specification.relationships, spec.relationships);
36
38
  });
37
39
  const resolveLinks = (doc, links) => {
38
40
  return links.map((l) => services.lsp.DocumentLinkProvider.resolveLink(doc, l));
39
41
  };
40
42
  const toModelElement = (doc) => {
41
43
  return ({
42
- astPath,
43
44
  tags,
44
45
  links,
45
46
  style: {
@@ -49,19 +50,23 @@ function buildModel(services, docs) {
49
50
  opacity,
50
51
  border
51
52
  },
52
- ...parsed
53
+ id,
54
+ kind,
55
+ title,
56
+ description,
57
+ technology
53
58
  }) => {
54
59
  try {
55
- const kind = c4Specification.kinds[parsed.kind];
56
- if (!kind) {
57
- logger.warn(`No kind '${parsed.kind}' found for ${parsed.id}`);
60
+ const __kind = c4Specification.kinds[kind];
61
+ if (!__kind) {
62
+ logger.warn(`No kind '${kind}' found for ${id}`);
58
63
  return null;
59
64
  }
60
- color ??= kind.color;
61
- shape ??= kind.shape;
62
- icon ??= kind.icon;
63
- opacity ??= kind.opacity;
64
- border ??= kind.border;
65
+ color ??= __kind.color;
66
+ shape ??= __kind.shape;
67
+ icon ??= __kind.icon;
68
+ opacity ??= __kind.opacity;
69
+ border ??= __kind.border;
65
70
  return {
66
71
  ...color && { color },
67
72
  ...shape && { shape },
@@ -70,11 +75,13 @@ function buildModel(services, docs) {
70
75
  ...border && { border },
71
76
  ...opacity && { opacity }
72
77
  },
73
- description: null,
74
- technology: null,
75
- tags: tags ?? null,
76
78
  links: links ? resolveLinks(doc, links) : null,
77
- ...parsed
79
+ tags: tags ?? null,
80
+ technology: technology ?? null,
81
+ description: description ?? null,
82
+ title,
83
+ kind,
84
+ id
78
85
  };
79
86
  } catch (e) {
80
87
  logWarnError(e);
@@ -83,7 +90,8 @@ function buildModel(services, docs) {
83
90
  };
84
91
  };
85
92
  const elements = pipe(
86
- flatMap(docs, (d) => d.c4Elements.map(toModelElement(d))),
93
+ docs,
94
+ flatMap((d) => d.c4Elements.map(toModelElement(d))),
87
95
  filter(isTruthy),
88
96
  sort(compareByFqnHierarchically),
89
97
  reduce(
@@ -110,27 +118,30 @@ function buildModel(services, docs) {
110
118
  target,
111
119
  kind,
112
120
  links,
121
+ id,
113
122
  ...model
114
123
  }) => {
115
- if (source in elements && target in elements) {
116
- if (!!kind && kind in c4Specification.relationships) {
117
- return {
118
- source,
119
- target,
120
- kind,
121
- ...links && { links: resolveLinks(doc, links) },
122
- ...c4Specification.relationships[kind],
123
- ...model
124
- };
125
- }
124
+ if (isNullish(elements[source]) || isNullish(elements[target])) {
125
+ return null;
126
+ }
127
+ if (!!kind && kind in c4Specification.relationships) {
126
128
  return {
129
+ ...links && { links: resolveLinks(doc, links) },
130
+ ...c4Specification.relationships[kind],
131
+ ...model,
127
132
  source,
128
133
  target,
129
- ...links && { links: resolveLinks(doc, links) },
130
- ...model
134
+ kind,
135
+ id
131
136
  };
132
137
  }
133
- return null;
138
+ return {
139
+ ...links && { links: resolveLinks(doc, links) },
140
+ ...model,
141
+ source,
142
+ target,
143
+ id
144
+ };
134
145
  };
135
146
  };
136
147
  const relations = pipe(
@@ -138,35 +149,47 @@ function buildModel(services, docs) {
138
149
  filter(isTruthy),
139
150
  mapToObj((r) => [r.id, r])
140
151
  );
141
- const toElementView = (doc) => {
152
+ const toC4View = (doc) => {
142
153
  const docUri = doc.uri.toString();
143
- return (view) => {
144
- let { astPath, rules, title, description, tags, links, ...model } = view;
145
- if (!title && "viewOf" in view) {
146
- title = elements[view.viewOf]?.title;
154
+ return (parsedAstView) => {
155
+ let {
156
+ id,
157
+ title,
158
+ description,
159
+ tags,
160
+ links,
161
+ // ignore this property
162
+ astPath: _ignore,
163
+ // model should include discriminant __
164
+ ...model
165
+ } = parsedAstView;
166
+ if (parsedAstView.__ === "element" && isNullish(title) && "viewOf" in parsedAstView) {
167
+ title ??= elements[parsedAstView.viewOf]?.title ?? null;
147
168
  }
148
- if (!title && view.id === "index") {
169
+ if (isNullish(title) && id === "index") {
149
170
  title = "Landscape view";
150
171
  }
151
172
  return {
152
- ...model,
153
- title: title ?? null,
154
- description: description ?? null,
155
- tags: tags ?? null,
173
+ id,
174
+ title,
175
+ description,
176
+ tags,
156
177
  links: links ? resolveLinks(doc, links) : null,
157
178
  docUri,
158
- rules
179
+ ...model
159
180
  };
160
181
  };
161
182
  };
162
183
  const views = pipe(
163
- flatMap(docs, (d) => map(d.c4Views, toElementView(d))),
184
+ docs,
185
+ flatMap((d) => map(d.c4Views, toC4View(d))),
164
186
  resolveRelativePaths,
165
187
  mapToObj((v) => [v.id, v]),
166
188
  resolveRulesExtendedViews
167
189
  );
168
190
  if (!("index" in views)) {
169
191
  views["index"] = {
192
+ __: "element",
170
193
  id: "index",
171
194
  title: "Landscape",
172
195
  description: null,
@@ -254,7 +277,7 @@ ${printDocs(docs)}`);
254
277
  const index = new LikeC4ModelGraph(model);
255
278
  const allViews = [];
256
279
  for (const view of values(model.views)) {
257
- const result = computeView(view, index);
280
+ const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index);
258
281
  if (!result.isSuccess) {
259
282
  logWarnError(result.error);
260
283
  continue;
@@ -291,7 +314,7 @@ ${printDocs(docs)}`);
291
314
  const cache = this.services.WorkspaceCache;
292
315
  return cache.get(computedViewKey(viewId), () => {
293
316
  const index = new LikeC4ModelGraph(model);
294
- const result = computeView(view, index);
317
+ const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index);
295
318
  if (!result.isSuccess) {
296
319
  logError(result.error);
297
320
  return null;
@@ -14,8 +14,8 @@ export declare class LikeC4ModelLocator {
14
14
  locateRelation(relationId: c4.RelationID): Location | null;
15
15
  locateViewAst(viewId: c4.ViewID): {
16
16
  doc: import("../ast").ParsedLikeC4LangiumDocument;
17
- view: import("../ast").ParsedAstElementView;
18
- viewAst: ast.ElementView;
17
+ view: import("../ast").ParsedAstView;
18
+ viewAst: ast.LikeC4View;
19
19
  } | null;
20
20
  locateView(viewId: c4.ViewID): Location | null;
21
21
  }
@@ -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,13 +50,13 @@ 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 } = checksFromDiagnostics(doc);
59
+ parseSpecification(doc, isValid) {
60
60
  const { parseResult, c4Specification } = doc;
61
61
  const specifications = parseResult.value.specifications.filter(isValid);
62
62
  const element_specs = specifications.flatMap((s) => s.elements.filter(isValid));
@@ -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 } = checksFromDiagnostics(doc);
164
- const views = doc.parseResult.value.views.flatMap((v) => isValid(v) ? v.views.filter(isValid) : []);
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);
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) {
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 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) {
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 = body.props.find((p) => p.key === "title")?.value;
361
- const description = 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 this.parseViewRule(n);
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
+ });
389
415
  }
390
- return basic;
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
+ );
429
+ }
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
  }
@@ -1,23 +1,31 @@
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
- function unionRangeOfAllEdits(edits) {
6
- let start = Number.MAX_SAFE_INTEGER;
7
- let end = Number.MIN_SAFE_INTEGER;
8
- let startCharacter = 0;
9
- let endCharacter = 0;
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;
5
+ function unionRangeOfAllEdits(ranges) {
6
+ let startLine = Number.MAX_SAFE_INTEGER;
7
+ let endLine = Number.MIN_SAFE_INTEGER;
8
+ let startCharacter = Number.MAX_SAFE_INTEGER;
9
+ let endCharacter = Number.MIN_SAFE_INTEGER;
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
+ } else {
15
+ startLine = start.line;
16
+ startCharacter = 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 (endLine <= end.line) {
20
+ if (endLine == end.line) {
21
+ endCharacter = Math.max(end.character, endCharacter);
22
+ } else {
23
+ endLine = end.line;
24
+ endCharacter = end.character;
25
+ }
18
26
  }
19
27
  }
20
- return Range.create(start, startCharacter, end, endCharacter);
28
+ return Range.create(startLine, startCharacter, endLine, endCharacter);
21
29
  }
22
30
  export class LikeC4ModelChanges {
23
31
  constructor(services) {
@@ -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) {
@@ -52,7 +60,7 @@ export class LikeC4ModelChanges {
52
60
  }
53
61
  result = {
54
62
  uri: textDocument.uri,
55
- range: unionRangeOfAllEdits(edits)
63
+ range: unionRangeOfAllEdits(edits.map((edit) => edit.range))
56
64
  };
57
65
  });
58
66
  return result;
@@ -62,32 +70,27 @@ export class LikeC4ModelChanges {
62
70
  if (!lookup) {
63
71
  throw new Error(`View not found: ${viewId}`);
64
72
  }
73
+ const ranges = [];
65
74
  const edits = [];
66
75
  for (const change of changes) {
67
76
  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, {
77
+ case "change-element-style": {
78
+ const { edits: elementEdits, modifiedRange } = changeElementStyle(this.services, {
79
79
  ...lookup,
80
80
  targets: change.targets,
81
- key: "shape",
82
- value: change.shape
83
- }));
81
+ style: change.style
82
+ });
83
+ ranges.push(modifiedRange);
84
+ edits.push(...elementEdits);
84
85
  break;
85
86
  }
86
87
  case "change-autolayout": {
87
- edits.push(...changeViewLayout(this.services, {
88
+ const edit = changeViewLayout(this.services, {
88
89
  ...lookup,
89
90
  layout: change.layout
90
- }));
91
+ });
92
+ edits.push(edit);
93
+ ranges.push(edit.range);
91
94
  break;
92
95
  }
93
96
  default:
@@ -96,6 +99,7 @@ export class LikeC4ModelChanges {
96
99
  }
97
100
  return {
98
101
  doc: lookup.doc,
102
+ ranges,
99
103
  edits
100
104
  };
101
105
  }