@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 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.isViewRuleStyle,
63
+ ast.isCustomElementExprBody,
64
64
  ast.isViewRulePredicateExpr,
65
- ast.isViewRulePredicate,
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
- const opacity = parseAstOpacityProperty(prop);
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), Object.assign(c4Specification.relationships, spec.relationships);
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
- ...parsed
52
+ id,
53
+ kind,
54
+ title,
55
+ description,
56
+ technology
53
57
  }) => {
54
58
  try {
55
- const kind = c4Specification.kinds[parsed.kind];
56
- if (!kind) {
57
- logger.warn(`No kind '${parsed.kind}' found for ${parsed.id}`);
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 ??= kind.color;
61
- shape ??= kind.shape;
62
- icon ??= kind.icon;
63
- opacity ??= kind.opacity;
64
- border ??= kind.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
- ...parsed
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
- flatMap(docs, (d) => d.c4Elements.map(toModelElement(d))),
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 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
- }
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
- ...links && { links: resolveLinks(doc, links) },
130
- ...model
133
+ kind,
134
+ id
131
135
  };
132
136
  }
133
- return null;
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(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;
@@ -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(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;
@@ -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);
@@ -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, 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());
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.0",
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.0",
78
- "@likec4/graph": "1.1.0",
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
- }