@adecore/editor-core 0.39.0 → 0.40.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 (53) hide show
  1. package/dist/block-folds.js +5 -2
  2. package/dist/bracket-tracker.js +4 -3
  3. package/dist/brackets.d.ts +2 -12
  4. package/dist/brackets.js +3 -15
  5. package/dist/closer-tracker.js +0 -1
  6. package/dist/doc-comments.js +0 -1
  7. package/dist/document.d.ts +2 -0
  8. package/dist/document.js +33 -35
  9. package/dist/edit-regions.js +3 -4
  10. package/dist/enter.d.ts +2 -0
  11. package/dist/enter.js +11 -9
  12. package/dist/fold-roles.js +2 -2
  13. package/dist/lexical-folds.d.ts +0 -1
  14. package/dist/lexical-folds.js +1 -4
  15. package/dist/lexical.d.ts +11 -5
  16. package/dist/lexical.js +13 -17
  17. package/dist/line-commands.js +7 -15
  18. package/dist/line-text.d.ts +2 -0
  19. package/dist/line-text.js +4 -0
  20. package/dist/markup-regions.js +2 -3
  21. package/dist/paste.js +2 -7
  22. package/dist/structure.js +2 -1
  23. package/dist/typing-context.js +5 -15
  24. package/package.json +2 -2
  25. package/src/block-folds.ts +39 -2
  26. package/src/bracket-tracker.ts +55 -12
  27. package/src/brackets.ts +35 -42
  28. package/src/closer-tracker.ts +6 -1
  29. package/src/comments.ts +103 -0
  30. package/src/doc-comments.ts +19 -1
  31. package/src/document.ts +505 -36
  32. package/src/edit-regions.ts +26 -4
  33. package/src/enter.ts +104 -9
  34. package/src/fold-roles.ts +34 -2
  35. package/src/index.ts +2 -0
  36. package/src/languages.ts +11 -0
  37. package/src/lexical-folds.ts +21 -5
  38. package/src/lexical.ts +31 -23
  39. package/src/line-commands.ts +81 -17
  40. package/src/line-text.ts +7 -0
  41. package/src/markup-regions.ts +101 -3
  42. package/src/offsets.ts +10 -0
  43. package/src/paste.ts +20 -7
  44. package/src/php-markup.ts +23 -0
  45. package/src/preserve-case.ts +12 -0
  46. package/src/rope.ts +70 -0
  47. package/src/script-source.ts +10 -0
  48. package/src/search.ts +38 -0
  49. package/src/structure.ts +29 -1
  50. package/src/text-span.ts +53 -0
  51. package/src/typing-context.ts +65 -41
  52. package/src/typing-handlers.ts +28 -0
  53. package/src/words.ts +10 -0
package/src/comments.ts CHANGED
@@ -31,6 +31,7 @@ interface Geometry {
31
31
 
32
32
  function syntaxOf(regions: EditRegions, options: CommentOptions, line: number, offset?: number): CommentSyntax {
33
33
  const syntax = commentSyntax(options.language, regions(line, offset));
34
+
34
35
  return options.lineToken === undefined ? syntax : { ...syntax, line: options.lineToken };
35
36
  }
36
37
 
@@ -38,17 +39,22 @@ function syntaxOf(regions: EditRegions, options: CommentOptions, line: number, o
38
39
  function blocksOf(source: EditSource, selections: readonly Selection[], span?: CommentOptions['lineSpan']): Block[] {
39
40
  const blocks: Block[] = [];
40
41
  const ordered = selections.map((selection, index) => ({ ...rangeOf(selection), index })).sort((left, right) => left.from - right.from);
42
+
41
43
  for (const { from, to, index } of ordered) {
42
44
  let first = source.lineAt(from);
43
45
  let last = source.lineAt(to);
46
+
44
47
  if (last > first && source.line(last).start === to) {
45
48
  last--;
46
49
  }
50
+
47
51
  if (span) {
48
52
  first = span(first).first;
49
53
  last = Math.max(last, span(last).last);
50
54
  }
55
+
51
56
  const previous = blocks.at(-1);
57
+
52
58
  if (previous && first <= previous.last + 1) {
53
59
  previous.last = Math.max(previous.last, last);
54
60
  previous.carets.push(index);
@@ -56,18 +62,23 @@ function blocksOf(source: EditSource, selections: readonly Selection[], span?: C
56
62
  blocks.push({ first, last, carets: [index] });
57
63
  }
58
64
  }
65
+
59
66
  return blocks;
60
67
  }
61
68
 
62
69
  function isCommented(text: string, syntax: CommentSyntax, multiline: boolean): boolean {
63
70
  const trimmed = text.trim();
71
+
64
72
  if (syntax.line !== null) {
65
73
  return trimmed.startsWith(syntax.line.trimEnd());
66
74
  }
75
+
67
76
  const block = syntax.block!;
77
+
68
78
  if (trimmed === '') {
69
79
  return multiline;
70
80
  }
81
+
71
82
  return trimmed.length >= block.open.length + block.close.length && trimmed.startsWith(block.open) && trimmed.endsWith(block.close);
72
83
  }
73
84
 
@@ -75,14 +86,17 @@ function isCommented(text: string, syntax: CommentSyntax, multiline: boolean): b
75
86
  function geometryOf(source: EditSource, edits: readonly TextEdit[], index: number): Geometry {
76
87
  const bounds = source.line(index);
77
88
  const result = { start: bounds.start, length: bounds.end - bounds.start };
89
+
78
90
  for (const edit of edits) {
79
91
  const delta = edit.text.length - (edit.to - edit.from);
92
+
80
93
  if (edit.from < bounds.start) {
81
94
  result.start += delta;
82
95
  } else if (edit.to <= bounds.end) {
83
96
  result.length += delta;
84
97
  }
85
98
  }
99
+
86
100
  return result;
87
101
  }
88
102
 
@@ -94,21 +108,26 @@ export function planLineComments(source: EditSource, selections: readonly Select
94
108
  const regions = editRegions(source, options.language);
95
109
  const blocks = blocksOf(source, selections, options.lineSpan);
96
110
  const lineBased = new Map<number, CommentSyntax>();
111
+
97
112
  for (const block of blocks) {
98
113
  let marker: CommentSyntax | undefined;
99
114
  let mixed = false;
100
115
  const firstSyntax = hasJsx(options.language) && block.first !== block.last ? syntaxOf(regions, options, block.first) : undefined;
116
+
101
117
  for (let line = block.first; line <= block.last; line++) {
102
118
  const syntax = firstSyntax ?? syntaxOf(regions, options, line);
103
119
  const effective = syntax.line === null ? syntax : { ...syntax, block: null };
104
120
  lineBased.set(line, effective);
121
+
105
122
  if (effective.line !== null || effective.block !== null) {
106
123
  mixed ||=
107
124
  marker !== undefined &&
108
125
  (marker.line !== effective.line || marker.block?.open !== effective.block?.open || marker.block?.close !== effective.block?.close);
126
+
109
127
  marker ??= effective;
110
128
  }
111
129
  }
130
+
112
131
  // Commenting a region opener would change the markers needed for the next toggle.
113
132
  if (mixed && /^(php|vue)$/i.test(options.language)) {
114
133
  for (let line = block.first; line <= block.last; line++) {
@@ -116,24 +135,32 @@ export function planLineComments(source: EditSource, selections: readonly Select
116
135
  }
117
136
  }
118
137
  }
138
+
119
139
  let allCommented = true;
120
140
  let anyCommented = false;
141
+
121
142
  for (const block of blocks) {
122
143
  const single = block.first === block.last;
144
+
123
145
  for (let line = block.first; line <= block.last; line++) {
124
146
  const text = source.line(line).text;
125
147
  const syntax = lineBased.get(line)!;
148
+
126
149
  if (syntax.line === null && syntax.block === null) {
127
150
  continue;
128
151
  }
152
+
129
153
  const commented = isCommented(text, syntax, !single);
154
+
130
155
  if (commented && !isBlank(text)) {
131
156
  anyCommented = true;
132
157
  }
158
+
133
159
  if (!commented && (single || !isBlank(text))) {
134
160
  allCommented = false;
135
161
  }
136
162
  }
163
+
137
164
  if (allCommented && !single && !anyCommented) {
138
165
  allCommented = false;
139
166
  }
@@ -141,59 +168,75 @@ export function planLineComments(source: EditSource, selections: readonly Select
141
168
 
142
169
  const edits: TextEdit[] = [];
143
170
  const placed = new Set<number>();
171
+
144
172
  for (const block of blocks) {
145
173
  const syntax = lineBased.get(block.first)!;
146
174
  const multiline = block.first !== block.last;
175
+
147
176
  const startingNewLine =
148
177
  !multiline &&
149
178
  block.carets.length === 1 &&
150
179
  isBlank(source.line(block.first).text) &&
151
180
  selections[block.carets[0]!]!.anchor === selections[block.carets[0]!]!.head;
181
+
152
182
  const minimum = allCommented ? 0 : minimumIndent(source, block, syntax, options);
183
+
153
184
  for (let line = block.first; line <= block.last; line++) {
154
185
  const bounds = source.line(line);
155
186
  const syntax = lineBased.get(line)!;
187
+
156
188
  if (syntax.line === null && syntax.block === null) {
157
189
  continue;
158
190
  }
191
+
159
192
  const edit = allCommented
160
193
  ? uncommentLine(bounds.start, bounds.text, syntax)
161
194
  : commentLine(bounds.start, bounds.text, syntax, minimum, multiline, startingNewLine, options);
195
+
162
196
  if (edit) {
163
197
  edits.push(edit);
198
+
164
199
  if (startingNewLine && !allCommented) {
165
200
  placed.add(line);
166
201
  }
167
202
  }
168
203
  }
169
204
  }
205
+
170
206
  if (edits.length === 0) {
171
207
  return null;
172
208
  }
209
+
173
210
  return { edits, selections: finalSelections(source, selections, blocks, edits, allCommented, placed, lineBased, options.lineSpan) };
174
211
  }
175
212
 
176
213
  /* The indentation of the least indented non-blank line, infinite when every line is blank. */
177
214
  function smallestIndent(source: EditSource, first: number, last: number, tabSize: number): number {
178
215
  let minimum = Number.POSITIVE_INFINITY;
216
+
179
217
  for (let line = first; line <= last; line++) {
180
218
  const text = source.line(line).text;
219
+
181
220
  if (!isBlank(text)) {
182
221
  minimum = Math.min(minimum, indentationColumn(whitespaceOf(text), tabSize));
183
222
  }
184
223
  }
224
+
185
225
  return minimum;
186
226
  }
187
227
 
188
228
  function minimumIndent(source: EditSource, block: Block, syntax: CommentSyntax, options: CommentOptions): number {
189
229
  let minimum = smallestIndent(source, block.first, block.last, options.tabSize);
230
+
190
231
  if (block.first > 0) {
191
232
  const previous = source.line(block.first - 1).text;
192
233
  const marker = syntax.line ?? syntax.block?.open;
234
+
193
235
  if (marker && previous.trimStart().startsWith(marker)) {
194
236
  minimum = Math.min(minimum, indentationColumn(whitespaceOf(previous), options.tabSize));
195
237
  }
196
238
  }
239
+
197
240
  return Number.isFinite(minimum) ? minimum : 0;
198
241
  }
199
242
 
@@ -207,57 +250,75 @@ function commentLine(
207
250
  options: CommentOptions
208
251
  ): TextEdit | null {
209
252
  const blank = isBlank(text);
253
+
210
254
  if (syntax.line === null && blank && multiline) {
211
255
  return null;
212
256
  }
257
+
213
258
  const leading = whitespaceOf(text);
214
259
  let kept = 0;
215
260
  let width = 0;
261
+
216
262
  while (kept < leading.length && width < minimum) {
217
263
  width += leading[kept] === '\t' ? options.tabSize - (width % options.tabSize) : 1;
218
264
  kept++;
219
265
  }
266
+
220
267
  // A line whose whitespace ends before the indent does is padded out to it.
221
268
  const short = width < minimum;
222
269
  const padding = short ? indentText(minimum, options) : '';
223
270
  const markerAt = start + (short ? leading.length : kept);
271
+
224
272
  if (syntax.line !== null) {
225
273
  const space = !blank || startingNewLine ? ' ' : '';
274
+
226
275
  return { from: short ? start : markerAt, to: markerAt, text: padding + syntax.line + space };
227
276
  }
277
+
228
278
  const { open, close } = syntax.block!;
229
279
  const end = start + text.trimEnd().length;
230
280
  const body = blank ? '' : text.slice(markerAt - start, end - start);
281
+
231
282
  return { from: short ? start : markerAt, to: blank ? start + text.length : end, text: padding + open + (blank ? ' ' : ` ${body} `) + close };
232
283
  }
233
284
 
234
285
  function uncommentLine(start: number, text: string, syntax: CommentSyntax): TextEdit | null {
235
286
  const trimmed = text.trim();
287
+
236
288
  if (trimmed === '') {
237
289
  return null;
238
290
  }
291
+
239
292
  const leading = whitespaceOf(text);
240
293
  let rest: string;
294
+
241
295
  if (syntax.line !== null) {
242
296
  const marker = syntax.line.trimEnd();
297
+
243
298
  if (!trimmed.startsWith(marker)) {
244
299
  return null;
245
300
  }
301
+
246
302
  const body = text.slice(leading.length + marker.length);
247
303
  rest = body.startsWith(' ') ? body.slice(1) : body;
248
304
  } else {
249
305
  const { open, close } = syntax.block!;
306
+
250
307
  if (!trimmed.startsWith(open) || !trimmed.endsWith(close)) {
251
308
  return null;
252
309
  }
310
+
253
311
  const inner = text.trimEnd().slice(leading.length + open.length, -close.length);
254
312
  rest = (inner.startsWith(' ') ? inner.slice(1) : inner).replace(/ $/, '');
255
313
  rest += text.slice(text.trimEnd().length);
256
314
  }
315
+
257
316
  const kept = leading + rest;
317
+
258
318
  if (kept.trim() === '') {
259
319
  return { from: start, to: start + text.length, text: '' };
260
320
  }
321
+
261
322
  return { from: start + leading.length, to: start + text.length, text: rest };
262
323
  }
263
324
 
@@ -272,36 +333,48 @@ function finalSelections(
272
333
  span: CommentOptions['lineSpan']
273
334
  ): Selection[] {
274
335
  const sorted = [...edits].sort((left, right) => left.from - right.from);
336
+
275
337
  const result = selections.map((selection) => ({
276
338
  anchor: mapOffset(selection.anchor, sorted, true),
277
339
  head: mapOffset(selection.head, sorted, true)
278
340
  }));
341
+
279
342
  const moveCarets = blocks.every((block) => block.carets.length === 1 || block.first === block.last);
343
+
280
344
  if (!moveCarets) {
281
345
  return result;
282
346
  }
347
+
283
348
  for (const block of blocks) {
284
349
  for (const index of block.carets) {
285
350
  const selection = selections[index]!;
286
351
  const { from, to } = rangeOf(selection);
287
352
  const line = source.lineAt(from);
353
+
288
354
  if (from === to) {
289
355
  const syntax = syntaxes.get(line)!;
356
+
290
357
  if (syntax.line === null && syntax.block === null) {
291
358
  continue;
292
359
  }
360
+
293
361
  result[index] = caretAfter(source, sorted, line, span?.(line).last ?? line, selection.head, allCommented, placed, syntax);
362
+
294
363
  continue;
295
364
  }
365
+
296
366
  const lastLine = source.lineAt(to - 1);
297
367
  const whole = source.line(line).start === from && source.line(lastLine).next === to;
368
+
298
369
  if (whole) {
299
370
  const start = geometryOf(source, sorted, line).start;
371
+
300
372
  result[index] =
301
373
  selection.anchor <= selection.head ? { anchor: start, head: result[index]!.head } : { anchor: result[index]!.anchor, head: start };
302
374
  }
303
375
  }
304
376
  }
377
+
305
378
  return result;
306
379
  }
307
380
 
@@ -316,30 +389,41 @@ function caretAfter(
316
389
  syntax: CommentSyntax
317
390
  ): Selection {
318
391
  const geometry = geometryOf(source, edits, line);
392
+
319
393
  if (placed.has(line) && !allCommented) {
320
394
  const caret = syntax.line === null ? geometry.start + geometry.length - syntax.block!.close.length - 1 : geometry.start + geometry.length;
395
+
321
396
  return { anchor: caret, head: caret };
322
397
  }
398
+
323
399
  const mapped = mapOffset(offset, edits);
400
+
324
401
  if (lastLine >= source.lineCount - 1) {
325
402
  return { anchor: mapped, head: mapped };
326
403
  }
404
+
327
405
  const next = geometryOf(source, edits, lastLine + 1);
328
406
  const column = Math.min(mapped - geometry.start, next.length);
407
+
329
408
  return { anchor: next.start + column, head: next.start + column };
330
409
  }
331
410
 
332
411
  /* Where the comment that holds `offset` runs, for the markers the lexer knows. Null outside a comment. */
333
412
  function commentAround(source: EditSource, offset: number, block: BlockComment): { from: number; to: number } | null {
334
413
  const context = source.context(offset);
414
+
335
415
  if ((context.mode !== 'block-comment' && context.mode !== 'html-comment') || context.commentStart === undefined) {
336
416
  return null;
337
417
  }
418
+
338
419
  const from = block.open === '{/*' && source.charAt(context.commentStart - 1) === '{' ? context.commentStart - 1 : context.commentStart;
420
+
339
421
  if (source.slice(from, from + block.open.length) !== block.open) {
340
422
  return null;
341
423
  }
424
+
342
425
  const close = source.slice(from + block.open.length, from + block.open.length + 200_000).indexOf(block.close);
426
+
343
427
  return close < 0 ? null : { from, to: from + block.open.length + close + block.close.length };
344
428
  }
345
429
 
@@ -349,9 +433,11 @@ function commentOf(source: EditSource, from: number, to: number, block: BlockCom
349
433
  const start = text.length - text.trimStart().length;
350
434
  const end = text.trimEnd().length;
351
435
  const body = text.slice(start, end);
436
+
352
437
  if (body.length >= block.open.length + block.close.length && body.startsWith(block.open) && body.endsWith(block.close)) {
353
438
  return { from: from + start, to: from + end };
354
439
  }
440
+
355
441
  return null;
356
442
  }
357
443
 
@@ -364,8 +450,10 @@ export function planBlockComment(source: EditSource, selections: readonly Select
364
450
  const edits: TextEdit[] = [];
365
451
  const result: Selection[] = [];
366
452
  let offsetDelta = 0;
453
+
367
454
  for (const selection of selections) {
368
455
  const { from, to } = rangeOf(selection);
456
+
369
457
  if (/^php$/i.test(options.language)) {
370
458
  const context = source.context(from);
371
459
  const { mode } = context;
@@ -373,6 +461,7 @@ export function planBlockComment(source: EditSource, selections: readonly Select
373
461
  const endMode = endContext.mode;
374
462
  const endsLineComment = mode === 'code' && endMode === 'line-comment' && source.slice(to, source.line(source.lineAt(to)).end).trim() === '';
375
463
  const text = source.slice(from, to);
464
+
376
465
  // Wrapping a PHP tag can move a closing marker into a different language.
377
466
  if (
378
467
  phpLiteralAt(source, from) ||
@@ -387,14 +476,19 @@ export function planBlockComment(source: EditSource, selections: readonly Select
387
476
  return null;
388
477
  }
389
478
  }
479
+
390
480
  const block = syntaxOf(regions, options, source.lineAt(from), from).block;
481
+
391
482
  if (!block) {
392
483
  return null;
393
484
  }
485
+
394
486
  const found = commentOf(source, from, to, block) ?? commentAround(source, from, block);
487
+
395
488
  if (found && from !== to && !(found.from >= from && found.to <= to)) {
396
489
  return null;
397
490
  }
491
+
398
492
  if (found) {
399
493
  const innerStart = found.from + block.open.length + (source.charAt(found.from + block.open.length) === ' ' ? 1 : 0);
400
494
  const closeAt = found.to - block.close.length;
@@ -405,25 +499,31 @@ export function planBlockComment(source: EditSource, selections: readonly Select
405
499
  const removedBefore = innerStart - found.from;
406
500
  result.push({ anchor: anchor - removedBefore + offsetDelta, head: head - removedBefore + offsetDelta });
407
501
  offsetDelta -= innerStart - found.from + (found.to - innerEnd);
502
+
408
503
  continue;
409
504
  }
505
+
410
506
  if (source.slice(from, to).includes(block.close)) {
411
507
  return null;
412
508
  }
509
+
413
510
  if (from === to) {
414
511
  const text = `${block.open} ${block.close}`;
415
512
  edits.push({ from, to, text });
416
513
  const caret = from + offsetDelta + block.open.length + 1;
417
514
  result.push({ anchor: caret, head: caret });
418
515
  offsetDelta += text.length;
516
+
419
517
  continue;
420
518
  }
519
+
421
520
  const wrapped = wrapRange(source, from, to, block, options);
422
521
  edits.push(...wrapped);
423
522
  const added = wrapped.reduce((sum, edit) => sum + edit.text.length, 0);
424
523
  result.push({ anchor: from + offsetDelta, head: to + added + offsetDelta });
425
524
  offsetDelta += added;
426
525
  }
526
+
427
527
  return { edits, selections: result };
428
528
  }
429
529
 
@@ -431,16 +531,19 @@ function wrapRange(source: EditSource, from: number, to: number, block: BlockCom
431
531
  const first = source.lineAt(from);
432
532
  const last = source.lineAt(to - 1);
433
533
  const endsLine = source.charAt(to - 1) === '\n' || to === source.line(source.lineCount - 1).end;
534
+
434
535
  if (from !== source.line(first).start || !endsLine) {
435
536
  return [
436
537
  { from, to: from, text: `${block.open} ` },
437
538
  { from: to, to, text: ` ${block.close}` }
438
539
  ];
439
540
  }
541
+
440
542
  const minimum = smallestIndent(source, first, last, options.tabSize);
441
543
  const space = indentText(Number.isFinite(minimum) ? minimum : 0, options);
442
544
  const newline = source.newline(first);
443
545
  const closing = source.charAt(to - 1) === '\n' ? `${space}${block.close}${newline}` : `${newline}${space}${block.close}`;
546
+
444
547
  return [
445
548
  { from, to: from, text: `${space}${block.open}${newline}` },
446
549
  { from: to, to, text: closing }
@@ -23,12 +23,15 @@ function bodyOf(inner: string): string {
23
23
  const lines = inner.split(/\r\n|\r|\n/).map((line, index) => (index === 0 ? line.replace(/^[ \t]+/, '') : line.replace(/^[ \t]*\*?[ \t]?/, '')));
24
24
  const last = lines.length - 1;
25
25
  lines[last] = lines[last]!.replace(/[ \t*]+$/, '');
26
+
26
27
  while (lines.length > 0 && lines[0]!.trim() === '') {
27
28
  lines.shift();
28
29
  }
30
+
29
31
  while (lines.length > 0 && lines.at(-1)!.trim() === '') {
30
32
  lines.pop();
31
33
  }
34
+
32
35
  return lines.map((line) => line.replace(/[ \t]+$/, '')).join('\n');
33
36
  }
34
37
 
@@ -37,12 +40,14 @@ const HEREDOC_OPENER = /<<<[ \t]*(?:'(\w+)'|"(\w+)"|(\w+))\r?\n/g;
37
40
  /* The bodies of PHP heredocs and nowdocs, which the structure scan reads as code. Offsets of the first character after the opener and of the closing label. */
38
41
  function heredocBodies(text: string): [number, number][] {
39
42
  const result: [number, number][] = [];
43
+
40
44
  for (const opener of text.matchAll(HEREDOC_OPENER)) {
41
45
  const label = opener[1] ?? opener[2] ?? opener[3]!;
42
46
  const from = opener.index + opener[0].length;
43
47
  const closer = new RegExp(`^[ \\t]*${label}\\b`, 'm').exec(text.slice(from));
44
48
  result.push([from, closer === null ? text.length : from + closer.index]);
45
49
  }
50
+
46
51
  return result;
47
52
  }
48
53
 
@@ -57,48 +62,61 @@ function heredocBodies(text: string): [number, number][] {
57
62
  export function findDocComments(text: string, language: string, limit = MAX_DOC_COMMENTS): DocComment[] {
58
63
  const vue = language.toLowerCase() === 'vue';
59
64
  const syntax = commentSyntax(language, vue ? 'script' : null);
65
+
60
66
  if (syntax.block?.open !== '/*' || syntax.line !== '//' || !text.includes('/**')) {
61
67
  return [];
62
68
  }
69
+
63
70
  const result: DocComment[] = [];
64
71
  const bodies = language.toLowerCase() === 'php' && text.includes('<<<') ? heredocBodies(text) : [];
65
72
  const lines = vue ? text.split(/\r\n|\r|\n/) : null;
66
73
  let counted = 0;
67
74
  let line = 0;
75
+
68
76
  for (const range of scanStructure(text)) {
69
77
  if (result.length >= limit) {
70
78
  break;
71
79
  }
80
+
72
81
  if (range.kind !== 'comment' || commentRole((from, to) => text.slice(from, to), range.from, range.to) === undefined) {
73
82
  continue;
74
83
  }
75
- // Three stars or more is a banner, not a comment about the code under it.
84
+
76
85
  if (text[range.from + 3] === '*' || bodies.some(([from, to]) => range.from >= from && range.from < to)) {
77
86
  continue;
78
87
  }
88
+
79
89
  const lineStart = Math.max(text.lastIndexOf('\n', range.from - 1), text.lastIndexOf('\r', range.from - 1)) + 1;
80
90
  const newline = text.slice(range.to).search(/[\r\n]/);
81
91
  const lineEnd = newline === -1 ? text.length : range.to + newline;
82
92
  const before = text.slice(lineStart, range.from);
93
+
83
94
  if (!/^[ \t]*$/.test(before) || !/^[ \t]*$/.test(text.slice(range.to, lineEnd))) {
84
95
  continue;
85
96
  }
97
+
86
98
  for (; counted < range.from; counted++) {
87
99
  if (text[counted] === '\n' || (text[counted] === '\r' && text[counted + 1] !== '\n')) {
88
100
  line++;
89
101
  }
90
102
  }
103
+
91
104
  const startLine = line;
105
+
92
106
  if (lines !== null && vueRegionAt((index) => lines[index] ?? '', startLine) !== 'script') {
93
107
  continue;
94
108
  }
109
+
95
110
  let endLine = startLine;
111
+
96
112
  for (let at = range.from; at < range.to; at++) {
97
113
  if (text[at] === '\n' || (text[at] === '\r' && text[at + 1] !== '\n')) {
98
114
  endLine++;
99
115
  }
100
116
  }
117
+
101
118
  result.push({ from: range.from, to: range.to, startLine, endLine, indent: before, body: bodyOf(text.slice(range.innerFrom + 1, range.innerTo)) });
102
119
  }
120
+
103
121
  return result;
104
122
  }