@adecore/editor-core 0.0.1 → 0.17.0-beta.2
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/LICENSE +110 -0
- package/LICENSE.apache-2.0.txt +201 -0
- package/NOTICE +11 -0
- package/README.md +40 -0
- package/dist/block-folds.d.ts +3 -0
- package/dist/block-folds.js +112 -0
- package/dist/brackets.d.ts +5 -0
- package/dist/brackets.js +136 -0
- package/dist/closer-tracker.d.ts +9 -0
- package/dist/closer-tracker.js +46 -0
- package/dist/comments.d.ts +14 -0
- package/dist/comments.js +344 -0
- package/dist/document.d.ts +148 -0
- package/dist/document.js +1729 -0
- package/dist/edit-source.d.ts +19 -0
- package/dist/edit-source.js +1 -0
- package/dist/enter.d.ts +17 -0
- package/dist/enter.js +387 -0
- package/dist/fold-roles.d.ts +6 -0
- package/dist/fold-roles.js +142 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/languages.d.ts +12 -0
- package/dist/languages.js +51 -0
- package/dist/lexical-folds.d.ts +10 -0
- package/dist/lexical-folds.js +116 -0
- package/dist/lexical.d.ts +16 -0
- package/dist/lexical.js +74 -0
- package/dist/line-commands.d.ts +9 -0
- package/dist/line-commands.js +306 -0
- package/dist/markup-regions.d.ts +8 -0
- package/dist/markup-regions.js +331 -0
- package/dist/offsets.d.ts +2 -0
- package/dist/offsets.js +14 -0
- package/dist/paste.d.ts +9 -0
- package/dist/paste.js +108 -0
- package/dist/preserve-case.d.ts +1 -0
- package/dist/preserve-case.js +61 -0
- package/dist/rope.d.ts +22 -0
- package/dist/rope.js +246 -0
- package/dist/search.d.ts +26 -0
- package/dist/search.js +85 -0
- package/dist/structure.d.ts +50 -0
- package/dist/structure.js +124 -0
- package/dist/text-span.d.ts +7 -0
- package/dist/text-span.js +211 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +1 -0
- package/dist/typing-context.d.ts +30 -0
- package/dist/typing-context.js +270 -0
- package/dist/typing-handlers.d.ts +15 -0
- package/dist/typing-handlers.js +125 -0
- package/dist/words.d.ts +7 -0
- package/dist/words.js +72 -0
- package/package.json +34 -3
- package/src/block-folds.ts +117 -0
- package/src/brackets.ts +159 -0
- package/src/closer-tracker.ts +60 -0
- package/src/comments.ts +411 -0
- package/src/document.ts +2021 -0
- package/src/edit-source.ts +25 -0
- package/src/enter.ts +452 -0
- package/src/fold-roles.ts +166 -0
- package/src/index.ts +22 -0
- package/src/languages.ts +77 -0
- package/src/lexical-folds.ts +138 -0
- package/src/lexical.ts +113 -0
- package/src/line-commands.ts +356 -0
- package/src/markup-regions.ts +333 -0
- package/src/offsets.ts +16 -0
- package/src/paste.ts +126 -0
- package/src/preserve-case.ts +62 -0
- package/src/rope.ts +278 -0
- package/src/search.ts +119 -0
- package/src/structure.ts +223 -0
- package/src/text-span.ts +230 -0
- package/src/types.ts +141 -0
- package/src/typing-context.ts +329 -0
- package/src/typing-handlers.ts +143 -0
- package/src/words.ts +83 -0
package/src/comments.ts
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import type { EditPlan, EditSource } from './edit-source.ts';
|
|
2
|
+
import { type BlockComment, type CommentSyntax, commentSyntax } from './languages.ts';
|
|
3
|
+
import { mapOffset } from './offsets.ts';
|
|
4
|
+
import { indentationColumn } from './structure.ts';
|
|
5
|
+
import type { Selection, TextEdit } from './types.ts';
|
|
6
|
+
|
|
7
|
+
export interface CommentOptions {
|
|
8
|
+
language: string;
|
|
9
|
+
tabSize: number;
|
|
10
|
+
insertSpaces: boolean;
|
|
11
|
+
/* Replaces the language's own line marker. */
|
|
12
|
+
lineToken?: string;
|
|
13
|
+
/* The lines that stand on one row of the screen, so a collapsed fold is commented as a whole. */
|
|
14
|
+
lineSpan?: (line: number) => { first: number; last: number };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Block {
|
|
18
|
+
first: number;
|
|
19
|
+
last: number;
|
|
20
|
+
/* Indices into the selections. */
|
|
21
|
+
carets: number[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Geometry {
|
|
25
|
+
start: number;
|
|
26
|
+
length: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function whitespaceOf(text: string): string {
|
|
30
|
+
return /^[\t ]*/.exec(text)![0];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isBlank(text: string): boolean {
|
|
34
|
+
return text.trim() === '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function syntaxOf(source: EditSource, options: CommentOptions, line: number): CommentSyntax {
|
|
38
|
+
const syntax = commentSyntax(options.language, source.region(line));
|
|
39
|
+
return options.lineToken === undefined ? syntax : { ...syntax, line: options.lineToken };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function rangeOf(selection: Selection): { from: number; to: number } {
|
|
43
|
+
return { from: Math.min(selection.anchor, selection.head), to: Math.max(selection.anchor, selection.head) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* The runs of adjacent lines the selections touch. A selection that ends at the start of a line does not touch it. */
|
|
47
|
+
function blocksOf(source: EditSource, selections: readonly Selection[], span?: CommentOptions['lineSpan']): Block[] {
|
|
48
|
+
const blocks: Block[] = [];
|
|
49
|
+
const ordered = selections.map((selection, index) => ({ ...rangeOf(selection), index })).sort((left, right) => left.from - right.from);
|
|
50
|
+
for (const { from, to, index } of ordered) {
|
|
51
|
+
let first = source.lineAt(from);
|
|
52
|
+
let last = source.lineAt(to);
|
|
53
|
+
if (last > first && source.line(last).start === to) {
|
|
54
|
+
last--;
|
|
55
|
+
}
|
|
56
|
+
if (span) {
|
|
57
|
+
first = span(first).first;
|
|
58
|
+
last = Math.max(last, span(last).last);
|
|
59
|
+
}
|
|
60
|
+
const previous = blocks.at(-1);
|
|
61
|
+
if (previous && first <= previous.last + 1) {
|
|
62
|
+
previous.last = Math.max(previous.last, last);
|
|
63
|
+
previous.carets.push(index);
|
|
64
|
+
} else {
|
|
65
|
+
blocks.push({ first, last, carets: [index] });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return blocks;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isCommented(text: string, syntax: CommentSyntax, multiline: boolean): boolean {
|
|
72
|
+
const trimmed = text.trim();
|
|
73
|
+
if (syntax.line !== null) {
|
|
74
|
+
return trimmed.startsWith(syntax.line.trimEnd());
|
|
75
|
+
}
|
|
76
|
+
const block = syntax.block!;
|
|
77
|
+
if (trimmed === '') {
|
|
78
|
+
return multiline;
|
|
79
|
+
}
|
|
80
|
+
return trimmed.length >= block.open.length + block.close.length && trimmed.startsWith(block.open) && trimmed.endsWith(block.close);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function indentText(width: number, options: CommentOptions): string {
|
|
84
|
+
if (options.insertSpaces) {
|
|
85
|
+
return ' '.repeat(width);
|
|
86
|
+
}
|
|
87
|
+
return '\t'.repeat(Math.floor(width / options.tabSize)) + ' '.repeat(width % options.tabSize);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* The new place of a line after the edits, which never cross a line break. */
|
|
91
|
+
function geometryOf(source: EditSource, edits: readonly TextEdit[], index: number): Geometry {
|
|
92
|
+
const bounds = source.line(index);
|
|
93
|
+
const result = { start: bounds.start, length: bounds.end - bounds.start };
|
|
94
|
+
for (const edit of edits) {
|
|
95
|
+
const delta = edit.text.length - (edit.to - edit.from);
|
|
96
|
+
if (edit.from < bounds.start) {
|
|
97
|
+
result.start += delta;
|
|
98
|
+
} else if (edit.to <= bounds.end) {
|
|
99
|
+
result.length += delta;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Comments the lines the selections touch, or takes the comment off when every non-blank one has it.
|
|
107
|
+
* The markers go in at the smallest indentation of the lines, and a lone caret moves to the next line.
|
|
108
|
+
*/
|
|
109
|
+
export function planLineComments(source: EditSource, selections: readonly Selection[], options: CommentOptions): EditPlan | null {
|
|
110
|
+
const blocks = blocksOf(source, selections, options.lineSpan);
|
|
111
|
+
const syntaxes = blocks.map((block) => syntaxOf(source, options, block.first));
|
|
112
|
+
if (syntaxes.some((syntax) => syntax.line === null && syntax.block === null)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const lineBased = syntaxes.map((syntax): CommentSyntax => (syntax.line === null ? syntax : { ...syntax, block: null }));
|
|
116
|
+
let allCommented = true;
|
|
117
|
+
let anyCommented = false;
|
|
118
|
+
for (const [position, block] of blocks.entries()) {
|
|
119
|
+
const single = block.first === block.last;
|
|
120
|
+
for (let line = block.first; line <= block.last; line++) {
|
|
121
|
+
const text = source.line(line).text;
|
|
122
|
+
const commented = isCommented(text, lineBased[position]!, !single);
|
|
123
|
+
if (commented && !isBlank(text)) {
|
|
124
|
+
anyCommented = true;
|
|
125
|
+
}
|
|
126
|
+
if (!commented && (single || !isBlank(text))) {
|
|
127
|
+
allCommented = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (allCommented && !single && !anyCommented) {
|
|
131
|
+
allCommented = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const edits: TextEdit[] = [];
|
|
136
|
+
const placed = new Set<number>();
|
|
137
|
+
for (const [position, block] of blocks.entries()) {
|
|
138
|
+
const syntax = lineBased[position]!;
|
|
139
|
+
const multiline = block.first !== block.last;
|
|
140
|
+
const startingNewLine =
|
|
141
|
+
!multiline &&
|
|
142
|
+
block.carets.length === 1 &&
|
|
143
|
+
isBlank(source.line(block.first).text) &&
|
|
144
|
+
selections[block.carets[0]!]!.anchor === selections[block.carets[0]!]!.head;
|
|
145
|
+
const minimum = allCommented ? 0 : minimumIndent(source, block, syntax, options);
|
|
146
|
+
for (let line = block.first; line <= block.last; line++) {
|
|
147
|
+
const bounds = source.line(line);
|
|
148
|
+
const edit = allCommented
|
|
149
|
+
? uncommentLine(bounds.start, bounds.text, syntax)
|
|
150
|
+
: commentLine(bounds.start, bounds.text, syntax, minimum, multiline, startingNewLine, options);
|
|
151
|
+
if (edit) {
|
|
152
|
+
edits.push(edit);
|
|
153
|
+
if (startingNewLine && !allCommented) {
|
|
154
|
+
placed.add(line);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (edits.length === 0) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return { edits, selections: finalSelections(source, selections, blocks, edits, allCommented, placed, lineBased, options.lineSpan) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function minimumIndent(source: EditSource, block: Block, syntax: CommentSyntax, options: CommentOptions): number {
|
|
166
|
+
let minimum = Number.POSITIVE_INFINITY;
|
|
167
|
+
for (let line = block.first; line <= block.last; line++) {
|
|
168
|
+
const text = source.line(line).text;
|
|
169
|
+
if (!isBlank(text)) {
|
|
170
|
+
minimum = Math.min(minimum, indentationColumn(whitespaceOf(text), options.tabSize));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (block.first > 0) {
|
|
174
|
+
const previous = source.line(block.first - 1).text;
|
|
175
|
+
const marker = syntax.line ?? syntax.block?.open;
|
|
176
|
+
if (marker && previous.trimStart().startsWith(marker)) {
|
|
177
|
+
minimum = Math.min(minimum, indentationColumn(whitespaceOf(previous), options.tabSize));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return Number.isFinite(minimum) ? minimum : 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function commentLine(
|
|
184
|
+
start: number,
|
|
185
|
+
text: string,
|
|
186
|
+
syntax: CommentSyntax,
|
|
187
|
+
minimum: number,
|
|
188
|
+
multiline: boolean,
|
|
189
|
+
startingNewLine: boolean,
|
|
190
|
+
options: CommentOptions
|
|
191
|
+
): TextEdit | null {
|
|
192
|
+
const blank = isBlank(text);
|
|
193
|
+
if (syntax.line === null && blank && multiline) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const leading = whitespaceOf(text);
|
|
197
|
+
let kept = 0;
|
|
198
|
+
let width = 0;
|
|
199
|
+
while (kept < leading.length && width < minimum) {
|
|
200
|
+
width += leading[kept] === '\t' ? options.tabSize - (width % options.tabSize) : 1;
|
|
201
|
+
kept++;
|
|
202
|
+
}
|
|
203
|
+
// A line whose whitespace ends before the indent does is padded out to it.
|
|
204
|
+
const short = width < minimum;
|
|
205
|
+
const padding = short ? indentText(minimum, options) : '';
|
|
206
|
+
const markerAt = start + (short ? leading.length : kept);
|
|
207
|
+
if (syntax.line !== null) {
|
|
208
|
+
const space = !blank || startingNewLine ? ' ' : '';
|
|
209
|
+
return { from: short ? start : markerAt, to: markerAt, text: padding + syntax.line + space };
|
|
210
|
+
}
|
|
211
|
+
const { open, close } = syntax.block!;
|
|
212
|
+
const end = start + text.trimEnd().length;
|
|
213
|
+
const body = blank ? '' : text.slice(markerAt - start, end - start);
|
|
214
|
+
return { from: short ? start : markerAt, to: blank ? start + text.length : end, text: padding + open + (blank ? ' ' : ` ${body} `) + close };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function uncommentLine(start: number, text: string, syntax: CommentSyntax): TextEdit | null {
|
|
218
|
+
const trimmed = text.trim();
|
|
219
|
+
if (trimmed === '') {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const leading = whitespaceOf(text);
|
|
223
|
+
let rest: string;
|
|
224
|
+
if (syntax.line !== null) {
|
|
225
|
+
const marker = syntax.line.trimEnd();
|
|
226
|
+
if (!trimmed.startsWith(marker)) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
const body = text.slice(leading.length + marker.length);
|
|
230
|
+
rest = body.startsWith(' ') ? body.slice(1) : body;
|
|
231
|
+
} else {
|
|
232
|
+
const { open, close } = syntax.block!;
|
|
233
|
+
if (!trimmed.startsWith(open) || !trimmed.endsWith(close)) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const inner = text.trimEnd().slice(leading.length + open.length, -close.length);
|
|
237
|
+
rest = (inner.startsWith(' ') ? inner.slice(1) : inner).replace(/ $/, '');
|
|
238
|
+
rest += text.slice(text.trimEnd().length);
|
|
239
|
+
}
|
|
240
|
+
const kept = leading + rest;
|
|
241
|
+
if (kept.trim() === '') {
|
|
242
|
+
return { from: start, to: start + text.length, text: '' };
|
|
243
|
+
}
|
|
244
|
+
return { from: start + leading.length, to: start + text.length, text: rest };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function finalSelections(
|
|
248
|
+
source: EditSource,
|
|
249
|
+
selections: readonly Selection[],
|
|
250
|
+
blocks: readonly Block[],
|
|
251
|
+
edits: readonly TextEdit[],
|
|
252
|
+
allCommented: boolean,
|
|
253
|
+
placed: ReadonlySet<number>,
|
|
254
|
+
syntaxes: readonly CommentSyntax[],
|
|
255
|
+
span: CommentOptions['lineSpan']
|
|
256
|
+
): Selection[] {
|
|
257
|
+
const sorted = [...edits].sort((left, right) => left.from - right.from);
|
|
258
|
+
const result = selections.map((selection) => ({
|
|
259
|
+
anchor: mapOffset(selection.anchor, sorted, true),
|
|
260
|
+
head: mapOffset(selection.head, sorted, true)
|
|
261
|
+
}));
|
|
262
|
+
const moveCarets = blocks.every((block) => block.carets.length === 1 || block.first === block.last);
|
|
263
|
+
if (!moveCarets) {
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
for (const [position, block] of blocks.entries()) {
|
|
267
|
+
for (const index of block.carets) {
|
|
268
|
+
const selection = selections[index]!;
|
|
269
|
+
const { from, to } = rangeOf(selection);
|
|
270
|
+
const line = source.lineAt(from);
|
|
271
|
+
if (from === to) {
|
|
272
|
+
result[index] = caretAfter(source, sorted, line, span?.(line).last ?? line, selection.head, allCommented, placed, syntaxes[position]!);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const lastLine = source.lineAt(to - 1);
|
|
276
|
+
const whole = source.line(line).start === from && source.line(lastLine).next === to;
|
|
277
|
+
if (whole) {
|
|
278
|
+
const start = geometryOf(source, sorted, line).start;
|
|
279
|
+
result[index] =
|
|
280
|
+
selection.anchor <= selection.head ? { anchor: start, head: result[index]!.head } : { anchor: result[index]!.anchor, head: start };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function caretAfter(
|
|
288
|
+
source: EditSource,
|
|
289
|
+
edits: readonly TextEdit[],
|
|
290
|
+
line: number,
|
|
291
|
+
lastLine: number,
|
|
292
|
+
offset: number,
|
|
293
|
+
allCommented: boolean,
|
|
294
|
+
placed: ReadonlySet<number>,
|
|
295
|
+
syntax: CommentSyntax
|
|
296
|
+
): Selection {
|
|
297
|
+
const geometry = geometryOf(source, edits, line);
|
|
298
|
+
if (placed.has(line) && !allCommented) {
|
|
299
|
+
const caret = syntax.line === null ? geometry.start + geometry.length - syntax.block!.close.length - 1 : geometry.start + geometry.length;
|
|
300
|
+
return { anchor: caret, head: caret };
|
|
301
|
+
}
|
|
302
|
+
const mapped = mapOffset(offset, edits);
|
|
303
|
+
if (lastLine >= source.lineCount - 1) {
|
|
304
|
+
return { anchor: mapped, head: mapped };
|
|
305
|
+
}
|
|
306
|
+
const next = geometryOf(source, edits, lastLine + 1);
|
|
307
|
+
const column = Math.min(mapped - geometry.start, next.length);
|
|
308
|
+
return { anchor: next.start + column, head: next.start + column };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Where the comment that holds `offset` runs, for the markers the lexer knows. Null outside a comment. */
|
|
312
|
+
function commentAround(source: EditSource, offset: number, block: BlockComment): { from: number; to: number } | null {
|
|
313
|
+
const context = source.context(offset);
|
|
314
|
+
if ((context.mode !== 'block-comment' && context.mode !== 'html-comment') || context.commentStart === undefined) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const from = context.commentStart;
|
|
318
|
+
if (source.slice(from, from + block.open.length) !== block.open) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const close = source.slice(from + block.open.length, from + block.open.length + 200_000).indexOf(block.close);
|
|
322
|
+
return close < 0 ? null : { from, to: from + block.open.length + close + block.close.length };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* The comment a selection is, markers and all, or null. */
|
|
326
|
+
function commentOf(source: EditSource, from: number, to: number, block: BlockComment): { from: number; to: number } | null {
|
|
327
|
+
const text = source.slice(from, to);
|
|
328
|
+
const start = text.length - text.trimStart().length;
|
|
329
|
+
const end = text.trimEnd().length;
|
|
330
|
+
const body = text.slice(start, end);
|
|
331
|
+
if (body.length >= block.open.length + block.close.length && body.startsWith(block.open) && body.endsWith(block.close)) {
|
|
332
|
+
return { from: from + start, to: from + end };
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/*
|
|
338
|
+
* Wraps each selection in the block markers, or takes them off a comment the selection is or sits in.
|
|
339
|
+
* With nothing selected it opens an empty comment around the caret.
|
|
340
|
+
*/
|
|
341
|
+
export function planBlockComment(source: EditSource, selections: readonly Selection[], options: CommentOptions): EditPlan | null {
|
|
342
|
+
const edits: TextEdit[] = [];
|
|
343
|
+
const result: Selection[] = [];
|
|
344
|
+
let offsetDelta = 0;
|
|
345
|
+
for (const selection of selections) {
|
|
346
|
+
const { from, to } = rangeOf(selection);
|
|
347
|
+
const block = syntaxOf(source, options, source.lineAt(from)).block;
|
|
348
|
+
if (!block) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const found = commentOf(source, from, to, block) ?? commentAround(source, from, block);
|
|
352
|
+
if (found && from !== to && !(found.from >= from && found.to <= to)) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
if (found) {
|
|
356
|
+
const innerStart = found.from + block.open.length + (source.charAt(found.from + block.open.length) === ' ' ? 1 : 0);
|
|
357
|
+
const closeAt = found.to - block.close.length;
|
|
358
|
+
const innerEnd = closeAt - (source.charAt(closeAt - 1) === ' ' && closeAt - 1 >= innerStart ? 1 : 0);
|
|
359
|
+
edits.push({ from: found.from, to: innerStart, text: '' }, { from: innerEnd, to: found.to, text: '' });
|
|
360
|
+
const head = Math.min(Math.max(selection.head, innerStart), innerEnd);
|
|
361
|
+
const anchor = Math.min(Math.max(selection.anchor, innerStart), innerEnd);
|
|
362
|
+
const removedBefore = innerStart - found.from;
|
|
363
|
+
result.push({ anchor: anchor - removedBefore + offsetDelta, head: head - removedBefore + offsetDelta });
|
|
364
|
+
offsetDelta -= innerStart - found.from + (found.to - innerEnd);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (source.slice(from, to).includes(block.close)) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
if (from === to) {
|
|
371
|
+
const text = `${block.open} ${block.close}`;
|
|
372
|
+
edits.push({ from, to, text });
|
|
373
|
+
const caret = from + offsetDelta + block.open.length + 1;
|
|
374
|
+
result.push({ anchor: caret, head: caret });
|
|
375
|
+
offsetDelta += text.length;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const wrapped = wrapRange(source, from, to, block, options);
|
|
379
|
+
edits.push(...wrapped);
|
|
380
|
+
const added = wrapped.reduce((sum, edit) => sum + edit.text.length, 0);
|
|
381
|
+
result.push({ anchor: from + offsetDelta, head: to + added + offsetDelta });
|
|
382
|
+
offsetDelta += added;
|
|
383
|
+
}
|
|
384
|
+
return { edits, selections: result };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function wrapRange(source: EditSource, from: number, to: number, block: BlockComment, options: CommentOptions): TextEdit[] {
|
|
388
|
+
const first = source.lineAt(from);
|
|
389
|
+
const last = source.lineAt(to - 1);
|
|
390
|
+
const endsLine = source.charAt(to - 1) === '\n' || to === source.line(source.lineCount - 1).end;
|
|
391
|
+
if (from !== source.line(first).start || !endsLine) {
|
|
392
|
+
return [
|
|
393
|
+
{ from, to: from, text: `${block.open} ` },
|
|
394
|
+
{ from: to, to, text: ` ${block.close}` }
|
|
395
|
+
];
|
|
396
|
+
}
|
|
397
|
+
let minimum = Number.POSITIVE_INFINITY;
|
|
398
|
+
for (let line = first; line <= last; line++) {
|
|
399
|
+
const text = source.line(line).text;
|
|
400
|
+
if (!isBlank(text)) {
|
|
401
|
+
minimum = Math.min(minimum, indentationColumn(whitespaceOf(text), options.tabSize));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const space = indentText(Number.isFinite(minimum) ? minimum : 0, options);
|
|
405
|
+
const newline = source.newline(first);
|
|
406
|
+
const closing = source.charAt(to - 1) === '\n' ? `${space}${block.close}${newline}` : `${newline}${space}${block.close}`;
|
|
407
|
+
return [
|
|
408
|
+
{ from, to: from, text: `${space}${block.open}${newline}` },
|
|
409
|
+
{ from: to, to, text: closing }
|
|
410
|
+
];
|
|
411
|
+
}
|