@adecore/editor-core 0.38.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.
- 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/edit-regions.ts
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
1
|
import type { EditSource } from './edit-source.ts';
|
|
2
2
|
import { type EditRegion, vueRegionAt } from './languages.ts';
|
|
3
|
+
import { textStart } from './line-text.ts';
|
|
3
4
|
import { hasJsx, jsxChildren } from './markup-regions.ts';
|
|
4
5
|
|
|
5
6
|
export type EditRegions = (line: number, offset?: number) => EditRegion | null;
|
|
6
7
|
|
|
7
8
|
const SCAN_LIMIT = 2_000_000;
|
|
9
|
+
|
|
8
10
|
export function phpLiteralAt(source: EditSource, offset: number): boolean {
|
|
9
11
|
const context = source.context(offset);
|
|
12
|
+
|
|
10
13
|
return context.mode === 'heredoc' || context.mode === 'quote' || (context.embedded !== undefined && context.mode === 'template');
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export function phpTagAt(source: EditSource, offset: number): boolean {
|
|
14
17
|
const context = source.context(offset);
|
|
18
|
+
|
|
15
19
|
return context.mode === 'html' && (context.markup === 'tag' || context.markup === 'equals' || context.markup === 'value');
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
function firstContent(source: EditSource, line: number): number {
|
|
19
|
-
|
|
20
|
-
return bounds.start + bounds.text.length - bounds.text.trimStart().length;
|
|
23
|
+
return textStart(source.line(line));
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
function mixedPhpLine(source: EditSource, line: number): boolean {
|
|
24
27
|
const bounds = source.line(line);
|
|
25
28
|
let work = SCAN_LIMIT;
|
|
29
|
+
|
|
26
30
|
for (const match of bounds.text.matchAll(/<\?(?:php\b|=)|\?>/g)) {
|
|
27
31
|
work -= bounds.text.length + 1;
|
|
32
|
+
|
|
28
33
|
if (work < 0) {
|
|
29
34
|
return true;
|
|
30
35
|
}
|
|
36
|
+
|
|
31
37
|
const at = bounds.start + match.index;
|
|
32
|
-
const
|
|
33
|
-
|
|
38
|
+
const { mode, embedded } = source.context(at);
|
|
39
|
+
|
|
34
40
|
if (match[0] === '?>') {
|
|
35
41
|
if (mode !== 'html' && embedded === undefined) {
|
|
36
42
|
return true;
|
|
@@ -39,28 +45,36 @@ function mixedPhpLine(source: EditSource, line: number): boolean {
|
|
|
39
45
|
return true;
|
|
40
46
|
}
|
|
41
47
|
}
|
|
48
|
+
|
|
42
49
|
return false;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
export function editRegions(source: EditSource, language: string): EditRegions {
|
|
46
53
|
const id = language.toLowerCase();
|
|
47
54
|
const end = source.line(source.lineCount - 1).end;
|
|
55
|
+
|
|
48
56
|
if (id === 'vue') {
|
|
49
57
|
return (line) => (/^<\/?(?:template|script|style)\b/.test(source.line(line).text) ? 'mixed' : vueRegionAt((index) => source.line(index).text, line));
|
|
50
58
|
}
|
|
59
|
+
|
|
51
60
|
if (hasJsx(id)) {
|
|
52
61
|
let children: ((offset: number) => boolean) | undefined;
|
|
62
|
+
|
|
53
63
|
return (line, offset) => {
|
|
54
64
|
if (end > SCAN_LIMIT) {
|
|
55
65
|
return null;
|
|
56
66
|
}
|
|
67
|
+
|
|
57
68
|
children ??= jsxChildren(source.slice(0, end));
|
|
69
|
+
|
|
58
70
|
return children(offset ?? firstContent(source, line)) ? 'jsx' : null;
|
|
59
71
|
};
|
|
60
72
|
}
|
|
73
|
+
|
|
61
74
|
if (id === 'php') {
|
|
62
75
|
return (line, offset) => {
|
|
63
76
|
const bounds = source.line(line);
|
|
77
|
+
|
|
64
78
|
if (
|
|
65
79
|
offset === undefined &&
|
|
66
80
|
(phpLiteralAt(source, bounds.start) ||
|
|
@@ -71,26 +85,34 @@ export function editRegions(source: EditSource, language: string): EditRegions {
|
|
|
71
85
|
) {
|
|
72
86
|
return 'mixed';
|
|
73
87
|
}
|
|
88
|
+
|
|
74
89
|
const at = offset ?? firstContent(source, line);
|
|
75
90
|
const context = source.context(at);
|
|
91
|
+
|
|
76
92
|
if (phpTagAt(source, at)) {
|
|
77
93
|
return 'mixed';
|
|
78
94
|
}
|
|
95
|
+
|
|
79
96
|
if (context.mode !== 'html' && context.embedded === undefined) {
|
|
80
97
|
return null;
|
|
81
98
|
}
|
|
99
|
+
|
|
82
100
|
if (context.markup === 'comment' || (offset === undefined && source.context(bounds.end).markup === 'comment')) {
|
|
83
101
|
return 'mixed';
|
|
84
102
|
}
|
|
103
|
+
|
|
85
104
|
if (context.markup === 'raw') {
|
|
86
105
|
const closing = /^<\/(script|style|textarea|title)(?=[\s/>]|$)/i.exec(source.slice(at, at + 11));
|
|
106
|
+
|
|
87
107
|
// The closing tag belongs to markup, while the preceding raw text keeps its syntax.
|
|
88
108
|
if (closing?.[1]?.toLowerCase() !== context.element) {
|
|
89
109
|
return context.element === 'script' || context.element === 'style' ? context.element : 'mixed';
|
|
90
110
|
}
|
|
91
111
|
}
|
|
112
|
+
|
|
92
113
|
return 'template';
|
|
93
114
|
};
|
|
94
115
|
}
|
|
116
|
+
|
|
95
117
|
return () => null;
|
|
96
118
|
}
|
package/src/enter.ts
CHANGED
|
@@ -28,15 +28,18 @@ export interface EnterPlan {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const concatenation = new Map<string, string>();
|
|
31
|
+
|
|
31
32
|
for (const id of 'typescript javascript typescriptreact javascriptreact tsx jsx ts js java csharp cs kotlin scala dart swift go groovy'.split(' ')) {
|
|
32
33
|
concatenation.set(id, '+');
|
|
33
34
|
}
|
|
35
|
+
|
|
34
36
|
concatenation.set('php', '.');
|
|
35
37
|
|
|
36
38
|
const markupLanguages = /^(html|xml|svg|vue|svelte|astro|mdx|jsx|tsx|javascriptreact|typescriptreact)$/i;
|
|
37
39
|
const voidElements = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i;
|
|
38
40
|
const trailingOperator = /(?:=>|&&|\|\||\?\?|[=*%?|&]|(?<!\+)\+|(?<!-)-)$/;
|
|
39
41
|
const lookBack = 50;
|
|
42
|
+
export const caseLabel = /^(?:case\b[^]*|default)\s*:$/;
|
|
40
43
|
/* The longest text a JSX attribute is looked for in on an Enter. */
|
|
41
44
|
const jsxScanLimit = 2_000_000;
|
|
42
45
|
/* How far past the caret a comment is searched for its end. */
|
|
@@ -51,23 +54,34 @@ function escapeForPattern(text: string): string {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/*
|
|
54
|
-
* Whether the code
|
|
57
|
+
* Whether the code in `text` leaves its statement open: after the parenthesis of a braceless
|
|
55
58
|
* `if` or `for`, after `else`, `=>` or a binary operator. Languages with braces only.
|
|
56
59
|
*/
|
|
57
60
|
export function continues(text: string, context: TypingContext, language: string): boolean {
|
|
58
61
|
const effectiveLanguage = context.embedded ?? language;
|
|
62
|
+
|
|
59
63
|
if (!isBraced(effectiveLanguage) || context.mode !== 'code') {
|
|
60
64
|
return false;
|
|
61
65
|
}
|
|
66
|
+
|
|
62
67
|
if (context.word === 'control-close' || context.word === 'else' || context.word === 'do') {
|
|
63
68
|
return true;
|
|
64
69
|
}
|
|
70
|
+
|
|
65
71
|
if (isPhp(effectiveLanguage) && /(?<!\.)\.$/.test(text)) {
|
|
66
72
|
return true;
|
|
67
73
|
}
|
|
74
|
+
|
|
68
75
|
return trailingOperator.test(text);
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
export function lineContinues(source: EditSource, language: string, index: number): boolean {
|
|
79
|
+
const line = source.line(index);
|
|
80
|
+
const text = line.text.trimEnd();
|
|
81
|
+
|
|
82
|
+
return text.length > 0 && continues(text, source.context(line.start + text.length), language);
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
class Enter {
|
|
72
86
|
private readonly source: EditSource;
|
|
73
87
|
private readonly options: EnterOptions;
|
|
@@ -80,14 +94,16 @@ class Enter {
|
|
|
80
94
|
plan(from: number, to: number): EnterPlan {
|
|
81
95
|
const { source, options } = this;
|
|
82
96
|
const index = source.lineAt(from);
|
|
83
|
-
|
|
84
|
-
const before = source.slice(line.start, from);
|
|
85
|
-
const leading = whitespaceOf(before);
|
|
86
|
-
const newline = source.newline(index);
|
|
97
|
+
|
|
87
98
|
if (!options.smart) {
|
|
99
|
+
const leading = whitespaceOf(source.slice(source.line(index).start, from));
|
|
100
|
+
const newline = source.newline(index);
|
|
101
|
+
|
|
88
102
|
return { from, to, text: newline + leading, caret: newline.length + leading.length };
|
|
89
103
|
}
|
|
104
|
+
|
|
90
105
|
const context = source.context(from);
|
|
106
|
+
|
|
91
107
|
return this.inLiteral(from, to, context, index) ?? this.inCode(from, to, context, index);
|
|
92
108
|
}
|
|
93
109
|
|
|
@@ -96,12 +112,15 @@ class Enter {
|
|
|
96
112
|
if (context.mode === 'block-comment') {
|
|
97
113
|
return this.inBlockComment(from, to, context, index);
|
|
98
114
|
}
|
|
115
|
+
|
|
99
116
|
if (from !== to) {
|
|
100
117
|
return null;
|
|
101
118
|
}
|
|
119
|
+
|
|
102
120
|
if (context.mode === 'line-comment') {
|
|
103
121
|
return this.inLineComment(from, context, index);
|
|
104
122
|
}
|
|
123
|
+
|
|
105
124
|
return context.mode === 'quote' ? this.inString(from, context, index) : null;
|
|
106
125
|
}
|
|
107
126
|
|
|
@@ -110,9 +129,11 @@ class Enter {
|
|
|
110
129
|
const line = this.source.line(index);
|
|
111
130
|
const own = whitespaceOf(line.text);
|
|
112
131
|
const previous = this.previousCodeLine(index);
|
|
132
|
+
|
|
113
133
|
if (previous === null || depth > lookBack || !this.lineContinues(previous)) {
|
|
114
134
|
return own;
|
|
115
135
|
}
|
|
136
|
+
|
|
116
137
|
return this.baseIndent(previous, depth + 1);
|
|
117
138
|
}
|
|
118
139
|
|
|
@@ -122,13 +143,12 @@ class Enter {
|
|
|
122
143
|
return candidate;
|
|
123
144
|
}
|
|
124
145
|
}
|
|
146
|
+
|
|
125
147
|
return null;
|
|
126
148
|
}
|
|
127
149
|
|
|
128
150
|
private lineContinues(index: number): boolean {
|
|
129
|
-
|
|
130
|
-
const text = line.text.trimEnd();
|
|
131
|
-
return text.length > 0 && continues(text, this.source.context(line.start + text.length), this.options.language);
|
|
151
|
+
return lineContinues(this.source, this.options.language, index);
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
private inCode(from: number, to: number, lineContext: TypingContext, index: number): EnterPlan {
|
|
@@ -149,15 +169,19 @@ class Enter {
|
|
|
149
169
|
const next = source.charAt(afterWhitespace);
|
|
150
170
|
const base = context.mode === 'code' ? this.baseIndent(index) : leading;
|
|
151
171
|
const start = leading.length < before.length ? base : leading;
|
|
172
|
+
|
|
152
173
|
const plain = (indent: string): EnterPlan => ({ from, to: afterWhitespace, text: newline + indent, caret: newline.length + indent.length });
|
|
153
174
|
|
|
154
175
|
if (context.mode === 'code' || (isPhp(language) && context.mode === 'html')) {
|
|
155
176
|
const tag = this.openTag(trimmed, index);
|
|
177
|
+
|
|
156
178
|
if (tag) {
|
|
157
179
|
const inner = start + unit;
|
|
180
|
+
|
|
158
181
|
if (new RegExp(`^</${escapeForPattern(tag)}\\s*>`, 'i').test(source.slice(afterWhitespace, line.end))) {
|
|
159
182
|
return { from, to: afterWhitespace, text: newline + inner + newline + start, caret: newline.length + inner.length };
|
|
160
183
|
}
|
|
184
|
+
|
|
161
185
|
return plain(inner);
|
|
162
186
|
}
|
|
163
187
|
}
|
|
@@ -167,13 +191,17 @@ class Enter {
|
|
|
167
191
|
}
|
|
168
192
|
|
|
169
193
|
const opener = openers[last] !== undefined && context.bracket?.close === openers[last] && context.bracket.at === line.start + trimmed.length - 1;
|
|
194
|
+
|
|
170
195
|
if (opener) {
|
|
171
196
|
const inner = start + unit;
|
|
197
|
+
|
|
172
198
|
if (next === openers[last]) {
|
|
173
199
|
return { from, to: afterWhitespace, text: newline + inner + newline + start, caret: newline.length + inner.length };
|
|
174
200
|
}
|
|
201
|
+
|
|
175
202
|
const closing =
|
|
176
203
|
last === '{' && source.unclosedBrace(context.bracket!.at) ? this.closeBrace(from, afterWhitespace, line, rest, newline, start, inner) : null;
|
|
204
|
+
|
|
177
205
|
return closing ?? plain(inner);
|
|
178
206
|
}
|
|
179
207
|
|
|
@@ -184,8 +212,10 @@ class Enter {
|
|
|
184
212
|
if (continues(trimmed, context, language)) {
|
|
185
213
|
const previous = this.previousCodeLine(index);
|
|
186
214
|
const continued = previous !== null && this.lineContinues(previous);
|
|
215
|
+
|
|
187
216
|
return plain(continued ? leading : start + unit);
|
|
188
217
|
}
|
|
218
|
+
|
|
189
219
|
return plain(start);
|
|
190
220
|
}
|
|
191
221
|
|
|
@@ -200,53 +230,69 @@ class Enter {
|
|
|
200
230
|
inner: string
|
|
201
231
|
): EnterPlan | null {
|
|
202
232
|
const tail = rest.trimStart();
|
|
233
|
+
|
|
203
234
|
if (tail === '' || /^[)\];,%<?]/.test(tail)) {
|
|
204
235
|
const text = newline + inner + newline + base + '}';
|
|
236
|
+
|
|
205
237
|
return { from, to: afterWhitespace, text, caret: newline.length + inner.length };
|
|
206
238
|
}
|
|
239
|
+
|
|
207
240
|
if (!this.options.reach) {
|
|
208
241
|
return null;
|
|
209
242
|
}
|
|
243
|
+
|
|
210
244
|
const text = newline + inner + tail.trimEnd() + newline + base + '}';
|
|
245
|
+
|
|
211
246
|
return { from, to: line.end, text, caret: newline.length + inner.length };
|
|
212
247
|
}
|
|
213
248
|
|
|
214
249
|
/* The name of the tag the text ends in, when it is an open tag with children to come. */
|
|
215
250
|
private openTag(trimmed: string, index: number): string | null {
|
|
216
251
|
const { language } = this.options;
|
|
252
|
+
|
|
217
253
|
if (!markupLanguages.test(language) && !isPhp(language)) {
|
|
218
254
|
return null;
|
|
219
255
|
}
|
|
256
|
+
|
|
220
257
|
if (/^vue$/i.test(language) && this.source.region(index) === 'script') {
|
|
221
258
|
return null;
|
|
222
259
|
}
|
|
260
|
+
|
|
223
261
|
const match = /(?:^|[^\w$)\]])<([A-Za-z][\w:.-]*)(?:\s(?:[^<>]|=>)*)?>$/.exec(trimmed);
|
|
262
|
+
|
|
224
263
|
if (!match || trimmed.endsWith('/>') || (voidElements.test(match[1]!) && !/^xml$/i.test(language))) {
|
|
225
264
|
return null;
|
|
226
265
|
}
|
|
266
|
+
|
|
227
267
|
if (isPhp(language)) {
|
|
228
268
|
const offset = this.source.line(index).start + trimmed.indexOf('<', match.index);
|
|
229
269
|
// Only earlier tags establish this opener's region; its content can be script or style.
|
|
230
270
|
const regions = editRegions(this.source, language);
|
|
271
|
+
|
|
231
272
|
if (regions(index, offset) !== 'template') {
|
|
232
273
|
return null;
|
|
233
274
|
}
|
|
234
275
|
}
|
|
276
|
+
|
|
235
277
|
return match[1]!;
|
|
236
278
|
}
|
|
237
279
|
|
|
238
280
|
/* Python's `def f():`, a YAML `key:` and a `case x:`. */
|
|
239
281
|
private opensIndentedBlock(trimmed: string, context: TypingContext): boolean {
|
|
240
282
|
const { language } = this.options;
|
|
283
|
+
|
|
241
284
|
if (/^(python|py)$/i.test(language)) {
|
|
242
285
|
return trimmed.endsWith(':') && context.bracket === undefined;
|
|
243
286
|
}
|
|
287
|
+
|
|
244
288
|
if (/^(yaml|yml)$/i.test(language)) {
|
|
245
289
|
return /(?::|[|>][+-]?)$/.test(trimmed) && !trimmed.trimStart().startsWith('#');
|
|
246
290
|
}
|
|
291
|
+
|
|
247
292
|
if (isBraced(language)) {
|
|
248
|
-
return (context.bracket === undefined || context.bracket.close === '}') &&
|
|
293
|
+
return (context.bracket === undefined || context.bracket.close === '}') && caseLabel.test(trimmed.trimStart());
|
|
249
294
|
}
|
|
295
|
+
|
|
250
296
|
return false;
|
|
251
297
|
}
|
|
252
298
|
|
|
@@ -255,7 +301,9 @@ class Enter {
|
|
|
255
301
|
if (!/^(yaml|yml)$/i.test(this.options.language)) {
|
|
256
302
|
return start;
|
|
257
303
|
}
|
|
304
|
+
|
|
258
305
|
const dashes = /^[\t ]*(?:- +)+/.exec(text)?.[0];
|
|
306
|
+
|
|
259
307
|
return dashes ? start + ' '.repeat(dashes.length - whitespaceOf(text).length) : start;
|
|
260
308
|
}
|
|
261
309
|
|
|
@@ -266,35 +314,46 @@ class Enter {
|
|
|
266
314
|
const syntax = commentSyntax(options.language, source.region(index));
|
|
267
315
|
// A hash comment of a language that has both, as in PHP, continues with its own marker.
|
|
268
316
|
const token = commentStart !== undefined && hasHashComments(options.language) && source.charAt(commentStart) === '#' ? '#' : syntax.line;
|
|
317
|
+
|
|
269
318
|
if (commentStart === undefined || commentStart < line.start || token === null) {
|
|
270
319
|
return null;
|
|
271
320
|
}
|
|
321
|
+
|
|
272
322
|
const marker = new RegExp(`^${escapeForPattern(token)}${token === '//' ? '[/!]?' : token === '#' ? '#*' : ''}`).exec(
|
|
273
323
|
source.slice(commentStart, line.end)
|
|
274
324
|
)?.[0];
|
|
325
|
+
|
|
275
326
|
if (marker === undefined || from < commentStart + marker.length) {
|
|
276
327
|
return null;
|
|
277
328
|
}
|
|
329
|
+
|
|
278
330
|
const rest = source.slice(from, line.end);
|
|
279
331
|
const textStart = from + whitespaceOf(rest).length;
|
|
332
|
+
|
|
280
333
|
if (textStart >= line.end) {
|
|
281
334
|
return null;
|
|
282
335
|
}
|
|
336
|
+
|
|
283
337
|
const newline = source.newline(index);
|
|
284
338
|
const leading = whitespaceOf(source.slice(line.start, from));
|
|
339
|
+
|
|
285
340
|
if (source.slice(textStart, textStart + token.length) === token) {
|
|
286
341
|
return { from, to: textStart, text: newline + leading, caret: newline.length + leading.length };
|
|
287
342
|
}
|
|
343
|
+
|
|
288
344
|
const code = source.slice(line.start, commentStart);
|
|
289
345
|
const onlyComment = isIndentOnly(code);
|
|
290
346
|
let spacing = ' ';
|
|
347
|
+
|
|
291
348
|
if (onlyComment) {
|
|
292
349
|
const gap = whitespaceOf(source.slice(commentStart + marker.length, line.end));
|
|
293
350
|
spacing = gap === '' ? ' ' : gap;
|
|
294
351
|
} else if (source.charAt(from) === ' ') {
|
|
295
352
|
spacing = '';
|
|
296
353
|
}
|
|
354
|
+
|
|
297
355
|
const text = newline + leading + marker + spacing;
|
|
356
|
+
|
|
298
357
|
return { from, to: textStart, text, caret: text.length };
|
|
299
358
|
}
|
|
300
359
|
|
|
@@ -302,21 +361,28 @@ class Enter {
|
|
|
302
361
|
const { source, options } = this;
|
|
303
362
|
const operator = concatenation.get(context.embedded ?? options.language.toLowerCase());
|
|
304
363
|
const quote = context.quote;
|
|
364
|
+
|
|
305
365
|
if (operator === undefined || (quote !== '"' && quote !== "'")) {
|
|
306
366
|
return null;
|
|
307
367
|
}
|
|
368
|
+
|
|
308
369
|
const line = source.line(index);
|
|
309
370
|
const opener = this.stringStart(line, from, quote);
|
|
371
|
+
|
|
310
372
|
if (opener !== null && (this.isModulePath(line, opener, index) || this.isMarkupAttribute(opener))) {
|
|
311
373
|
return null;
|
|
312
374
|
}
|
|
375
|
+
|
|
313
376
|
let at = from;
|
|
377
|
+
|
|
314
378
|
if (context.escaped) {
|
|
315
379
|
at += /^(?:u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|.)/.exec(source.slice(from, line.end))?.[0].length ?? 0;
|
|
316
380
|
}
|
|
381
|
+
|
|
317
382
|
if (!this.closesOnLine(at, quote, line)) {
|
|
318
383
|
return null;
|
|
319
384
|
}
|
|
385
|
+
|
|
320
386
|
const newline = source.newline(index);
|
|
321
387
|
const previous = this.previousCodeLine(index);
|
|
322
388
|
const leading = whitespaceOf(line.text);
|
|
@@ -324,14 +390,17 @@ class Enter {
|
|
|
324
390
|
const indent = continued ? leading : leading + options.unit;
|
|
325
391
|
const text = `${quote} ${operator}${newline}${indent}${quote}`;
|
|
326
392
|
const rest = source.slice(at, line.end);
|
|
393
|
+
|
|
327
394
|
return { from: at, to: at + whitespaceOf(rest).length, text, caret: text.length };
|
|
328
395
|
}
|
|
329
396
|
|
|
330
397
|
/* Where the string the caret is in opened, when it opened on this line. */
|
|
331
398
|
private stringStart(line: DocumentLine, from: number, quote: string): number | null {
|
|
332
399
|
let open: { at: number; quote: string } | null = null;
|
|
400
|
+
|
|
333
401
|
for (let at = line.start; at < from; at++) {
|
|
334
402
|
const char = this.source.charAt(at);
|
|
403
|
+
|
|
335
404
|
if (open === null) {
|
|
336
405
|
if (char === '"' || char === "'" || char === '`') {
|
|
337
406
|
open = { at, quote: char };
|
|
@@ -342,28 +411,34 @@ class Enter {
|
|
|
342
411
|
open = null;
|
|
343
412
|
}
|
|
344
413
|
}
|
|
414
|
+
|
|
345
415
|
return open?.quote === quote ? open.at : null;
|
|
346
416
|
}
|
|
347
417
|
|
|
348
418
|
/* A path an import, a `require` or an `export ... from` names, where a break and a concatenation would name another file. */
|
|
349
419
|
private isModulePath(line: DocumentLine, opener: number, index: number): boolean {
|
|
350
420
|
const before = this.source.slice(line.start, opener).trimEnd();
|
|
421
|
+
|
|
351
422
|
if (/(?:^|[^\w$.])(?:import|from|require|require_once|include|include_once)\s*\(?$/.test(before)) {
|
|
352
423
|
return true;
|
|
353
424
|
}
|
|
425
|
+
|
|
354
426
|
return /^go$/i.test(this.options.language) && before === '' && this.inGoImportBlock(index);
|
|
355
427
|
}
|
|
356
428
|
|
|
357
429
|
private inGoImportBlock(index: number): boolean {
|
|
358
430
|
for (let candidate = index - 1; candidate >= 0 && candidate >= index - lookBack; candidate--) {
|
|
359
431
|
const text = this.source.line(candidate).text.trim();
|
|
432
|
+
|
|
360
433
|
if (/^import\s*\($/.test(text)) {
|
|
361
434
|
return true;
|
|
362
435
|
}
|
|
436
|
+
|
|
363
437
|
if (text.startsWith(')')) {
|
|
364
438
|
return false;
|
|
365
439
|
}
|
|
366
440
|
}
|
|
441
|
+
|
|
367
442
|
return false;
|
|
368
443
|
}
|
|
369
444
|
|
|
@@ -372,12 +447,15 @@ class Enter {
|
|
|
372
447
|
if (!hasJsx(this.options.language)) {
|
|
373
448
|
return false;
|
|
374
449
|
}
|
|
450
|
+
|
|
375
451
|
const last = this.source.line(this.source.lineCount - 1).end;
|
|
452
|
+
|
|
376
453
|
return last <= jsxScanLimit && isJsxAttributeValue(this.source.slice(0, last), opener);
|
|
377
454
|
}
|
|
378
455
|
|
|
379
456
|
private closesOnLine(from: number, quote: string, line: DocumentLine): boolean {
|
|
380
457
|
const text = this.source.slice(from, line.end);
|
|
458
|
+
|
|
381
459
|
for (let at = 0; at < text.length; at++) {
|
|
382
460
|
if (text[at] === '\\') {
|
|
383
461
|
at++;
|
|
@@ -385,6 +463,7 @@ class Enter {
|
|
|
385
463
|
return true;
|
|
386
464
|
}
|
|
387
465
|
}
|
|
466
|
+
|
|
388
467
|
return false;
|
|
389
468
|
}
|
|
390
469
|
|
|
@@ -392,9 +471,11 @@ class Enter {
|
|
|
392
471
|
const { source, options } = this;
|
|
393
472
|
const syntax = commentSyntax(options.language, source.region(index));
|
|
394
473
|
const open = context.commentStart;
|
|
474
|
+
|
|
395
475
|
if (syntax.block?.open !== '/*' || open === undefined || from < open + 2) {
|
|
396
476
|
return null;
|
|
397
477
|
}
|
|
478
|
+
|
|
398
479
|
const startIndex = source.lineAt(open);
|
|
399
480
|
const startLine = source.line(startIndex);
|
|
400
481
|
const line = source.line(index);
|
|
@@ -413,12 +494,16 @@ class Enter {
|
|
|
413
494
|
const star = onOpener || onStar ? this.starPrefix(line, lead, onStar, isDoc) : whitespaceOf(line.text);
|
|
414
495
|
const tail = source.slice(textStart, line.end).trimEnd();
|
|
415
496
|
const closer = `${newline}${lead} */`;
|
|
497
|
+
|
|
416
498
|
if (options.reach) {
|
|
417
499
|
const text = newline + star + tail + closer;
|
|
500
|
+
|
|
418
501
|
return { from, to: line.end, text, caret: newline.length + star.length };
|
|
419
502
|
}
|
|
503
|
+
|
|
420
504
|
return { from, to: textStart, text: newline + star, caret: newline.length + star.length };
|
|
421
505
|
}
|
|
506
|
+
|
|
422
507
|
if (
|
|
423
508
|
isDoc &&
|
|
424
509
|
onOpener &&
|
|
@@ -428,14 +513,19 @@ class Enter {
|
|
|
428
513
|
) {
|
|
429
514
|
// Nothing left to say after the caret: the closer gets a line of its own, under the opener's star.
|
|
430
515
|
const star = this.starPrefix(line, lead, false, isDoc);
|
|
516
|
+
|
|
431
517
|
return { from, to: line.end, text: `${newline}${star}${newline}${lead} */`, caret: newline.length + star.length };
|
|
432
518
|
}
|
|
519
|
+
|
|
433
520
|
const second = source.line(startIndex + 1).text.trimStart();
|
|
434
521
|
const continuesStars = onOpener ? isDoc : second.startsWith('*') && !second.startsWith('*/');
|
|
522
|
+
|
|
435
523
|
if (!continuesStars) {
|
|
436
524
|
return null;
|
|
437
525
|
}
|
|
526
|
+
|
|
438
527
|
const star = this.starPrefix(line, lead, onStar, isDoc);
|
|
528
|
+
|
|
439
529
|
return { from, to: textStart, text: newline + star, caret: newline.length + star.length };
|
|
440
530
|
}
|
|
441
531
|
|
|
@@ -443,6 +533,7 @@ class Enter {
|
|
|
443
533
|
private commentLead(code: string): string {
|
|
444
534
|
const indent = whitespaceOf(code);
|
|
445
535
|
const gap = indentationColumn(code, this.options.tabSize) - indentationColumn(indent, this.options.tabSize);
|
|
536
|
+
|
|
446
537
|
return indent + ' '.repeat(gap);
|
|
447
538
|
}
|
|
448
539
|
|
|
@@ -451,7 +542,9 @@ class Enter {
|
|
|
451
542
|
if (!onStar) {
|
|
452
543
|
return `${lead} * `;
|
|
453
544
|
}
|
|
545
|
+
|
|
454
546
|
const inside = /^\*([\t ]*)/.exec(line.text.trimStart())?.[1] ?? '';
|
|
547
|
+
|
|
455
548
|
return `${whitespaceOf(line.text)}*${isDoc && inside !== '' ? inside : ' '}`;
|
|
456
549
|
}
|
|
457
550
|
|
|
@@ -459,9 +552,11 @@ class Enter {
|
|
|
459
552
|
private closingOf(open: number, from: number): number | null {
|
|
460
553
|
const text = this.source.slice(from, from + commentReach);
|
|
461
554
|
const close = text.indexOf('*/');
|
|
555
|
+
|
|
462
556
|
if (close < 0) {
|
|
463
557
|
return null;
|
|
464
558
|
}
|
|
559
|
+
|
|
465
560
|
return this.source.slice(open + 2, from + close).includes('/*') ? null : from + close;
|
|
466
561
|
}
|
|
467
562
|
}
|