@likec4/language-server 1.9.0 → 1.10.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 (48) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +1 -1
  4. package/dist/browser.d.mts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/browser.mjs +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.mjs +1 -1
  14. package/dist/node.cjs +1 -1
  15. package/dist/node.d.cts +1 -1
  16. package/dist/node.d.mts +1 -1
  17. package/dist/node.d.ts +1 -1
  18. package/dist/node.mjs +1 -1
  19. package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.CjFzaJwI.d.cts} +42 -13
  20. package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CtKHXJDD.d.ts} +42 -13
  21. package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.D-84I33F.d.mts} +42 -13
  22. package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.DBJJUUgF.mjs} +485 -108
  23. package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.DtBRb9os.mjs} +166 -116
  24. package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DwyCJvXm.cjs} +164 -114
  25. package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.JWkqVjGv.cjs} +481 -104
  26. package/package.json +6 -5
  27. package/src/ast.ts +8 -6
  28. package/src/formatting/LikeC4Formatter.ts +388 -0
  29. package/src/formatting/utils.ts +26 -0
  30. package/src/generated/ast.ts +104 -10
  31. package/src/generated/grammar.ts +1 -1
  32. package/src/like-c4.langium +34 -7
  33. package/src/lsp/DocumentLinkProvider.ts +27 -15
  34. package/src/lsp/SemanticTokenProvider.ts +1 -1
  35. package/src/lsp/index.ts +1 -1
  36. package/src/model/fqn-index.ts +0 -1
  37. package/src/model/model-builder.ts +43 -32
  38. package/src/model/model-parser.ts +43 -21
  39. package/src/model-graph/compute-view/compute.ts +104 -78
  40. package/src/model-graph/compute-view/predicates.ts +3 -5
  41. package/src/model-graph/dynamic-view/compute.ts +96 -60
  42. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  43. package/src/module.ts +6 -9
  44. package/src/test/testServices.ts +27 -7
  45. package/src/validation/index.ts +2 -1
  46. package/src/validation/property-checks.ts +13 -1
  47. package/src/validation/specification.ts +3 -3
  48. package/src/view-utils/resolve-relative-paths.ts +14 -17
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.9.0",
4
+ "version": "1.10.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -102,13 +102,14 @@
102
102
  "lint": "run -T eslint src/ --fix",
103
103
  "clean": "run -T rimraf dist contrib",
104
104
  "test": "vitest run --no-isolate",
105
+ "test-dbg": "vitest run --no-isolate -t formating",
105
106
  "vitest:ui": "vitest --no-isolate --ui",
106
107
  "test:watch": "vitest"
107
108
  },
108
109
  "dependencies": {
109
110
  "@dagrejs/graphlib": "^2.2.4",
110
- "@likec4/core": "1.9.0",
111
- "@likec4/log": "1.9.0",
111
+ "@likec4/core": "1.10.0",
112
+ "@likec4/log": "1.10.0",
112
113
  "@msgpack/msgpack": "^3.0.0-beta2",
113
114
  "@smithy/util-base64": "^3.0.0",
114
115
  "fast-equals": "^5.0.1",
@@ -128,8 +129,8 @@
128
129
  "vscode-uri": "3.0.8"
129
130
  },
130
131
  "devDependencies": {
131
- "@likec4/icons": "1.9.0",
132
- "@likec4/tsconfig": "1.9.0",
132
+ "@likec4/icons": "1.10.0",
133
+ "@likec4/tsconfig": "1.10.0",
133
134
  "@types/node": "^20.16.1",
134
135
  "@types/object-hash": "^3.0.6",
135
136
  "@types/string-hash": "^1.1.3",
package/src/ast.ts CHANGED
@@ -89,6 +89,7 @@ export interface ParsedAstRelation {
89
89
  head?: c4.RelationshipArrowType
90
90
  tail?: c4.RelationshipArrowType
91
91
  links?: c4.NonEmptyArray<ParsedLink>
92
+ navigateTo?: c4.ViewID
92
93
  metadata?: { [key: string]: string }
93
94
  }
94
95
 
@@ -114,7 +115,7 @@ export interface ParsedAstDynamicView {
114
115
  description: string | null
115
116
  tags: c4.NonEmptyArray<c4.Tag> | null
116
117
  links: c4.NonEmptyArray<ParsedLink> | null
117
- steps: c4.DynamicViewStep[]
118
+ steps: c4.DynamicViewStepOrParallel[]
118
119
  rules: Array<c4.DynamicViewRule>
119
120
  manualLayout?: c4.ViewManualLayout
120
121
  }
@@ -194,14 +195,14 @@ export function cleanParsedModel(doc: LikeC4LangiumDocument) {
194
195
  return Object.assign(doc, props) as ParsedLikeC4LangiumDocument
195
196
  }
196
197
 
197
- export function isFqnIndexedDocument(doc: LangiumDocument): doc is FqnIndexedDocument {
198
- return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !!doc.c4fqnIndex
199
- }
200
-
201
198
  export function isLikeC4LangiumDocument(doc: LangiumDocument): doc is LikeC4LangiumDocument {
202
199
  return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId
203
200
  }
204
201
 
202
+ export function isFqnIndexedDocument(doc: LangiumDocument): doc is FqnIndexedDocument {
203
+ return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !!doc.c4fqnIndex
204
+ }
205
+
205
206
  export function isParsedLikeC4LangiumDocument(
206
207
  doc: LangiumDocument
207
208
  ): doc is ParsedLikeC4LangiumDocument {
@@ -230,6 +231,7 @@ const isValidatableAstNode = validatableAstNodeGuards([
230
231
  ast.isRelationPredicateWith,
231
232
  ast.isElementExpression,
232
233
  ast.isRelationExpression,
234
+ ast.isDynamicViewParallelSteps,
233
235
  ast.isDynamicViewStep,
234
236
  ast.isViewProperty,
235
237
  ast.isStyleProperty,
@@ -265,7 +267,7 @@ const findInvalidContainer = (node: LikeC4AstNode): ValidatableAstNode | undefin
265
267
  }
266
268
  nd = nd.$container
267
269
  }
268
- return
270
+ return undefined
269
271
  }
270
272
 
271
273
  export function checksFromDiagnostics(doc: LikeC4LangiumDocument) {
@@ -0,0 +1,388 @@
1
+ import { type AstNode, GrammarUtils } from 'langium'
2
+ import { AbstractFormatter, Formatting, type NodeFormatter } from 'langium/lsp'
3
+ import * as ast from '../generated/ast'
4
+ import * as utils from './utils'
5
+
6
+ const FormattingOptions = {
7
+ newLine: Formatting.newLine({ allowMore: true }),
8
+ oneSpace: Formatting.oneSpace(),
9
+ noSpace: Formatting.noSpace(),
10
+ indent: Formatting.indent({ allowMore: true }),
11
+ noIndent: Formatting.noIndent()
12
+ }
13
+ type Predicate<T extends AstNode> = (x: unknown) => x is T
14
+
15
+ export class LikeC4Formatter extends AbstractFormatter {
16
+ protected format(node: AstNode): void {
17
+ this.removeIndentFromTopLevelStatements(node)
18
+ this.indentContentInBraces(node)
19
+
20
+ this.formatSpecificationRule(node)
21
+ this.formatElementDeclaration(node)
22
+ this.formatRelation(node)
23
+ this.formatView(node)
24
+ this.formatViewRuleStyle(node)
25
+ this.formatIncludeExcludeExpressions(node)
26
+ this.formatWhereExpression(node)
27
+ this.formatWithPredicate(node)
28
+ this.formatLeafProperty(node)
29
+ this.formatMetadataProperty(node)
30
+ this.formatAutolayoutProperty(node)
31
+ this.formatLinkProperty(node)
32
+ this.formatNavigateToProperty(node)
33
+ this.formatTags(node)
34
+ }
35
+
36
+ protected formatTags(node: AstNode) {
37
+ this.on(node, ast.isTags, (n, f) => {
38
+ f.cst(GrammarUtils.findNodesForProperty(n.$cstNode, 'values').slice(1))
39
+ .prepend(FormattingOptions.oneSpace)
40
+
41
+ f.keywords(',')
42
+ .prepend(FormattingOptions.noSpace)
43
+ .append(FormattingOptions.oneSpace)
44
+ })
45
+ }
46
+
47
+ protected formatRelation(node: AstNode) {
48
+ this.on(node, ast.isRelation, (n, f) => {
49
+ f.property('source').append(FormattingOptions.oneSpace)
50
+ f.keywords(']->').prepend(FormattingOptions.noSpace)
51
+ f.keywords('-[').append(FormattingOptions.noSpace)
52
+
53
+ f.properties('target', 'title', 'technology', 'tags').prepend(FormattingOptions.oneSpace)
54
+ })
55
+
56
+ this.on(node, ast.isDynamicViewStep, (n, f) => {
57
+ f.properties('source').append(FormattingOptions.oneSpace)
58
+ f.keywords(']->').prepend(FormattingOptions.noSpace)
59
+ f.keywords('-[').append(FormattingOptions.noSpace)
60
+ f.properties('target', 'title').prepend(FormattingOptions.oneSpace)
61
+ })
62
+
63
+ this.on(node, ast.isDirectedRelationExpression)
64
+ ?.property('target').prepend(FormattingOptions.oneSpace)
65
+
66
+ this.on(node, ast.isOutgoingRelationExpression, (n, f) => {
67
+ f.property('from').append(FormattingOptions.oneSpace)
68
+ f.keywords(']->').prepend(FormattingOptions.noSpace)
69
+ f.keywords('-[').append(FormattingOptions.noSpace)
70
+ })
71
+
72
+ this.on(node, ast.isIncomingRelationExpression)
73
+ ?.keywords('->').append(FormattingOptions.oneSpace)
74
+
75
+ this.on(node, ast.isInOutRelationExpression)
76
+ ?.property('inout').append(FormattingOptions.oneSpace)
77
+ }
78
+
79
+ protected removeIndentFromTopLevelStatements(node: AstNode) {
80
+ if (
81
+ ast.isModel(node)
82
+ || ast.isSpecificationRule(node)
83
+ || ast.isModelViews(node)
84
+ || ast.isLikeC4Lib(node)
85
+ ) {
86
+ const formatter = this.getNodeFormatter(node)
87
+ formatter.keywords('specification', 'model', 'views', 'likec4lib')
88
+ .prepend(FormattingOptions.noIndent)
89
+ }
90
+ }
91
+
92
+ protected indentContentInBraces(node: AstNode) {
93
+ if (
94
+ ast.isLikeC4Lib(node)
95
+ || ast.isSpecificationRule(node)
96
+ || ast.isSpecificationElementKind(node)
97
+ || ast.isSpecificationRelationshipKind(node)
98
+ || ast.isModel(node)
99
+ || ast.isElementBody(node)
100
+ || ast.isExtendElementBody(node)
101
+ || ast.isRelationBody(node)
102
+ || ast.isRelationStyleProperty(node)
103
+ || ast.isMetadataBody(node)
104
+ || ast.isModelViews(node)
105
+ || ast.isElementViewBody(node)
106
+ || ast.isDynamicViewBody(node)
107
+ || ast.isViewRuleStyle(node)
108
+ || ast.isCustomElementProperties(node)
109
+ || ast.isCustomRelationProperties(node)
110
+ || ast.isElementStyleProperty(node)
111
+ ) {
112
+ const formatter = this.getNodeFormatter(node)
113
+ const openBrace = formatter.keywords('{')
114
+ const closeBrace = formatter.keywords('}')
115
+
116
+ const interiorNodes = formatter.interior(openBrace, closeBrace)
117
+
118
+ // Workaround for tags as they are parsed as overlapping regions.
119
+ // E.g. '#tag1, #tag2' will be parsed as two nodes: '#tag1' and '#tag1, #tag2'
120
+ let perviousNode = null
121
+ for (const interiorNode of interiorNodes.nodes) {
122
+ if (!perviousNode || !utils.areOverlap(perviousNode, interiorNode)) {
123
+ formatter.cst([interiorNode]).prepend(FormattingOptions.indent)
124
+ }
125
+ perviousNode = interiorNode
126
+ }
127
+
128
+ openBrace
129
+ .prepend(FormattingOptions.noIndent)
130
+ .prepend(FormattingOptions.oneSpace)
131
+ closeBrace
132
+ .prepend(FormattingOptions.noIndent)
133
+ .prepend(Formatting.newLine({ allowMore: true }))
134
+ }
135
+ }
136
+
137
+ protected appendKeywordsWithSpace(node: AstNode) {
138
+ this.on(node, ast.isElementKind)
139
+ ?.keywords('element').append(FormattingOptions.oneSpace)
140
+ }
141
+
142
+ protected formatView(node: AstNode) {
143
+ this.on(node, ast.isElementView, (n, f) => {
144
+ if (n.extends || n.viewOf || n.name) {
145
+ f.keywords('view').append(FormattingOptions.oneSpace)
146
+ }
147
+ f.keywords('of', 'extends').surround(FormattingOptions.oneSpace)
148
+ })
149
+
150
+ this.on(node, ast.isDynamicView)
151
+ ?.keywords('dynamic', 'view').append(FormattingOptions.oneSpace)
152
+ }
153
+
154
+ protected formatLeafProperty(node: AstNode) {
155
+ if (
156
+ ast.isElementStringProperty(node)
157
+ || ast.isRelationStringProperty(node)
158
+ || ast.isViewStringProperty(node)
159
+ || ast.isNotationProperty(node)
160
+ || ast.isSpecificationElementStringProperty(node)
161
+ || ast.isSpecificationRelationshipStringProperty(node)
162
+ || ast.isColorProperty(node)
163
+ || ast.isLineProperty(node)
164
+ || ast.isArrowProperty(node)
165
+ || ast.isIconProperty(node)
166
+ || ast.isShapeProperty(node)
167
+ || ast.isBorderProperty(node)
168
+ || ast.isOpacityProperty(node)
169
+ ) {
170
+ const formatter = this.getNodeFormatter(node)
171
+ formatter.keywords(
172
+ 'title',
173
+ 'description',
174
+ 'technology',
175
+ 'notation',
176
+ 'color',
177
+ 'line',
178
+ 'head',
179
+ 'tail',
180
+ 'icon',
181
+ 'shape',
182
+ 'border',
183
+ 'opacity'
184
+ )
185
+ .append(FormattingOptions.oneSpace)
186
+
187
+ formatter.keyword(':')
188
+ .prepend(FormattingOptions.noSpace)
189
+ .append(FormattingOptions.oneSpace)
190
+
191
+ formatter.keyword(';')
192
+ .prepend(FormattingOptions.noSpace)
193
+ .append(FormattingOptions.newLine)
194
+ }
195
+ }
196
+
197
+ protected formatLinkProperty(node: AstNode) {
198
+ this.on(node, ast.isLinkProperty, (n, f) => {
199
+ f.keyword('link').append(FormattingOptions.oneSpace)
200
+ f.property('value').append(FormattingOptions.oneSpace)
201
+ f.keyword(':')
202
+ .prepend(FormattingOptions.noSpace)
203
+ .append(FormattingOptions.oneSpace)
204
+
205
+ f.keyword(';')
206
+ .prepend(FormattingOptions.noSpace)
207
+ .append(FormattingOptions.newLine)
208
+ })
209
+ }
210
+
211
+ protected formatNavigateToProperty(node: AstNode) {
212
+ this.on(node, ast.isNavigateToProperty)
213
+ ?.property('key').append(FormattingOptions.oneSpace)
214
+ }
215
+
216
+ protected formatAutolayoutProperty(node: AstNode) {
217
+ this.on(node, ast.isViewRuleAutoLayout)
218
+ ?.keyword('autoLayout').append(FormattingOptions.oneSpace)
219
+ }
220
+
221
+ protected formatMetadataProperty(node: AstNode) {
222
+ this.on(node, ast.isMetadataAttribute, (n, f) => {
223
+ f.property('key').append(FormattingOptions.oneSpace)
224
+ f.keyword(':')
225
+ .prepend(FormattingOptions.noSpace)
226
+ .append(FormattingOptions.oneSpace)
227
+ f.keyword(';')
228
+ .prepend(FormattingOptions.noSpace)
229
+ .append(FormattingOptions.newLine)
230
+ })
231
+ }
232
+
233
+ protected formatElementDeclaration(node: AstNode) {
234
+ this.on(node, ast.isElement, (n, f) => {
235
+ const kind = GrammarUtils.findNodeForProperty(n.$cstNode, 'kind')
236
+ const name = GrammarUtils.findNodeForProperty(n.$cstNode, 'name')
237
+
238
+ if (name && kind) {
239
+ // system sys1
240
+ if (utils.compareRanges(name, kind) > 0) {
241
+ f.cst([kind]).append(FormattingOptions.oneSpace)
242
+ }
243
+ // sys1 = system
244
+ else {
245
+ f.cst([name]).append(FormattingOptions.oneSpace)
246
+ f.cst([kind]).prepend(FormattingOptions.oneSpace)
247
+ }
248
+ }
249
+
250
+ f.properties('props').prepend(FormattingOptions.oneSpace)
251
+ })
252
+ }
253
+
254
+ protected formatSpecificationRule(node: AstNode) {
255
+ if (
256
+ ast.isSpecificationElementKind(node)
257
+ || ast.isSpecificationRelationshipKind(node)
258
+ || ast.isSpecificationTag(node)
259
+ ) {
260
+ const formatter = this.getNodeFormatter(node)
261
+
262
+ formatter.keywords('element', 'relationship', 'tag')
263
+ .append(FormattingOptions.oneSpace)
264
+ }
265
+ if (
266
+ ast.isSpecificationColor(node)
267
+ ) {
268
+ const formatter = this.getNodeFormatter(node)
269
+ formatter.keyword('color').append(FormattingOptions.oneSpace)
270
+ formatter.property('name').append(FormattingOptions.oneSpace)
271
+ }
272
+ }
273
+
274
+ protected formatWithPredicate(node: AstNode) {
275
+ const formatter = this.getNodeFormatter(node)
276
+ if (
277
+ ast.isElementPredicateWith(node)
278
+ || ast.isRelationPredicateWith(node)
279
+ ) {
280
+ formatter.keyword('with').prepend(FormattingOptions.oneSpace)
281
+ }
282
+ }
283
+
284
+ protected formatViewRuleStyle(node: AstNode) {
285
+ this.on(node, ast.isViewRuleStyle)
286
+ ?.keyword('style').append(FormattingOptions.oneSpace)
287
+
288
+ this.on(node, ast.isElementExpressionsIterator)
289
+ ?.keyword(',')
290
+ .prepend(FormattingOptions.noSpace)
291
+ .append(FormattingOptions.oneSpace)
292
+ }
293
+
294
+ protected formatWhereExpression(node: AstNode) {
295
+ if (
296
+ ast.isRelationPredicateOrWhere(node)
297
+ || ast.isElementPredicateOrWhere(node)
298
+ ) {
299
+ const formatter = this.getNodeFormatter(node)
300
+ formatter.keyword('where').append(FormattingOptions.oneSpace)
301
+ }
302
+ if (
303
+ ast.isWhereRelationExpression(node)
304
+ || ast.isWhereElementExpression(node)
305
+ ) {
306
+ const formatter = this.getNodeFormatter(node)
307
+ formatter.property('operator').surround(FormattingOptions.oneSpace)
308
+ }
309
+ if (
310
+ ast.isWhereElementNegation(node)
311
+ || ast.isWhereRelationNegation(node)
312
+ ) {
313
+ const formatter = this.getNodeFormatter(node)
314
+ formatter.keyword('not').append(FormattingOptions.oneSpace)
315
+ }
316
+ if (
317
+ ast.isWhereElement(node)
318
+ || ast.isWhereElementTag(node)
319
+ || ast.isWhereElementKind(node)
320
+ || ast.isWhereRelation(node)
321
+ || ast.isWhereRelationTag(node)
322
+ || ast.isWhereRelationKind(node)
323
+ ) {
324
+ const formatter = this.getNodeFormatter(node)
325
+ formatter.property('operator').surround(FormattingOptions.oneSpace)
326
+ formatter.property('not').surround(FormattingOptions.oneSpace)
327
+ }
328
+ }
329
+
330
+ protected formatIncludeExcludeExpressions(node: AstNode) {
331
+ if (
332
+ ast.isDynamicViewRule(node)
333
+ || ast.isIncludePredicate(node)
334
+ || ast.isExcludePredicate(node)
335
+ ) {
336
+ const formatter = this.getNodeFormatter(node)
337
+
338
+ if (!node.$cstNode || !utils.isMultiline(node.$cstNode)) {
339
+ formatter.keywords('include', 'exclude')
340
+ .append(FormattingOptions.oneSpace)
341
+ }
342
+ }
343
+ if (
344
+ ast.isDynamicViewPredicateIterator(node)
345
+ || ast.isPredicates(node)
346
+ || ast.isPredicates(node)
347
+ ) {
348
+ const formatter = this.getNodeFormatter(node)
349
+ const parent = this.findPredicateExpressionRoot(node)
350
+ const isMultiline = parent?.$cstNode && utils.isMultiline(parent?.$cstNode)
351
+
352
+ if (isMultiline) {
353
+ formatter.property('value').prepend(FormattingOptions.indent)
354
+ }
355
+ formatter.keyword(',')
356
+ .prepend(FormattingOptions.noSpace)
357
+ .append(isMultiline ? FormattingOptions.newLine : FormattingOptions.oneSpace)
358
+ }
359
+ }
360
+
361
+ private findPredicateExpressionRoot(node: AstNode): AstNode | undefined {
362
+ let parent = node.$container
363
+ while (true) {
364
+ if (
365
+ !parent
366
+ || ast.isDynamicViewRule(parent)
367
+ || ast.isIncludePredicate(parent)
368
+ || ast.isExcludePredicate(parent)
369
+ ) {
370
+ return parent
371
+ }
372
+
373
+ parent = parent.$container
374
+ }
375
+ }
376
+
377
+ private on<T extends AstNode>(
378
+ node: AstNode,
379
+ predicate: Predicate<T>,
380
+ format?: (node: T, f: NodeFormatter<T>) => void
381
+ ): NodeFormatter<T> | undefined {
382
+ const formatter = predicate(node) ? this.getNodeFormatter(node) : undefined
383
+
384
+ format && formatter && format(node as T, formatter)
385
+
386
+ return formatter
387
+ }
388
+ }
@@ -0,0 +1,26 @@
1
+ import type { CstNode } from "langium"
2
+ import type { Position, Range } from "vscode-languageserver-types"
3
+
4
+
5
+ export function areOverlap(a: CstNode, b: CstNode): boolean {
6
+ ;[a, b] = compareRanges(a, b) > 0 ? [b, a] : [a, b]
7
+
8
+ return isInRagne(a.range, b.range.start)
9
+ }
10
+
11
+ export function compareRanges(a: CstNode, b: CstNode): number {
12
+ const lineDiff = a.range.start.line - b.range.start.line
13
+
14
+ return lineDiff !== 0 ? lineDiff : a.range.start.character - b.range.start.character
15
+ }
16
+
17
+ export function isInRagne(range: Range, pos: Position): boolean {
18
+ return !(pos.line < range.start.line
19
+ || pos.line > range.end.line
20
+ || pos.line == range.start.line && pos.character < range.start.character
21
+ || pos.line == range.end.line && pos.character > range.end.character)
22
+ }
23
+
24
+ export function isMultiline(node: CstNode): boolean {
25
+ return node.range.start.line != node.range.end.line
26
+ }