@adecore/editor-core 0.39.0 → 0.40.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/block-folds.js +5 -2
- package/dist/bracket-tracker.js +4 -3
- package/dist/brackets.d.ts +2 -12
- package/dist/brackets.js +3 -15
- package/dist/closer-tracker.js +0 -1
- package/dist/doc-comments.js +0 -1
- package/dist/document.d.ts +2 -0
- package/dist/document.js +33 -35
- package/dist/edit-regions.js +3 -4
- package/dist/enter.d.ts +2 -0
- package/dist/enter.js +11 -9
- package/dist/fold-roles.js +2 -2
- package/dist/lexical-folds.d.ts +0 -1
- package/dist/lexical-folds.js +1 -4
- package/dist/lexical.d.ts +11 -5
- package/dist/lexical.js +13 -17
- package/dist/line-commands.js +7 -15
- package/dist/line-text.d.ts +2 -0
- package/dist/line-text.js +4 -0
- package/dist/markup-regions.js +2 -3
- package/dist/paste.js +2 -7
- package/dist/structure.js +2 -1
- package/dist/typing-context.js +5 -15
- package/package.json +2 -2
- package/src/block-folds.ts +39 -2
- package/src/bracket-tracker.ts +55 -12
- package/src/brackets.ts +35 -42
- package/src/closer-tracker.ts +6 -1
- package/src/comments.ts +103 -0
- package/src/doc-comments.ts +19 -1
- package/src/document.ts +505 -36
- package/src/edit-regions.ts +26 -4
- package/src/enter.ts +104 -9
- package/src/fold-roles.ts +34 -2
- package/src/index.ts +2 -0
- package/src/languages.ts +11 -0
- package/src/lexical-folds.ts +21 -5
- package/src/lexical.ts +31 -23
- package/src/line-commands.ts +81 -17
- package/src/line-text.ts +7 -0
- package/src/markup-regions.ts +101 -3
- package/src/offsets.ts +10 -0
- package/src/paste.ts +20 -7
- package/src/php-markup.ts +23 -0
- package/src/preserve-case.ts +12 -0
- package/src/rope.ts +70 -0
- package/src/script-source.ts +10 -0
- package/src/search.ts +38 -0
- package/src/structure.ts +29 -1
- package/src/text-span.ts +53 -0
- package/src/typing-context.ts +65 -41
- package/src/typing-handlers.ts +28 -0
- 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 }
|
package/src/doc-comments.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|