@adecore/editor-core 0.44.0 → 0.44.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/dist/document.d.ts +1 -0
- package/dist/document.js +32 -2
- package/dist/types.d.ts +1 -0
- package/dist/typing-handlers.d.ts +6 -0
- package/dist/typing-handlers.js +63 -0
- package/package.json +1 -1
- package/src/document.ts +42 -2
- package/src/types.ts +2 -0
- package/src/typing-handlers.ts +83 -0
package/dist/document.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export declare class DocumentModel {
|
|
|
97
97
|
typeText(text: string, options?: TypeTextOptions): boolean;
|
|
98
98
|
private takeBackArrows;
|
|
99
99
|
private completeArrows;
|
|
100
|
+
private completeTemplates;
|
|
100
101
|
paste(text: string, options?: CommandOptions & {
|
|
101
102
|
wholeLines?: boolean;
|
|
102
103
|
}): boolean;
|
package/dist/document.js
CHANGED
|
@@ -14,7 +14,7 @@ import { blockFolds } from './block-folds.js';
|
|
|
14
14
|
import { applyFoldHints, markFileHeader } from './fold-roles.js';
|
|
15
15
|
import { lexicalFolds } from './lexical-folds.js';
|
|
16
16
|
import { rangeOf, whitespaceOf } from './line-text.js';
|
|
17
|
-
import { isQuote, replacesComparison, surround, swapQuotes } from './typing-handlers.js';
|
|
17
|
+
import { isQuote, replacesComparison, surround, swapQuotes, templateLiteralEdits, templateString } from './typing-handlers.js';
|
|
18
18
|
import { scanBrackets } from './brackets.js';
|
|
19
19
|
import { BracketTracker } from './bracket-tracker.js';
|
|
20
20
|
import { hasSmartSemicolon, isPhp } from './lexical.js';
|
|
@@ -58,6 +58,10 @@ function trailingBlankLength(text) {
|
|
|
58
58
|
function changesText(rope, edit) {
|
|
59
59
|
return edit.text.length !== edit.to - edit.from || rope.slice(edit.from, edit.to) !== edit.text;
|
|
60
60
|
}
|
|
61
|
+
/* Where an offset stands after edits that end at or before it moved it; an insertion at the offset pushes it on. */
|
|
62
|
+
function shifted(edits, offset) {
|
|
63
|
+
return edits.reduce((moved, edit) => (edit.to <= offset ? moved + edit.text.length - (edit.to - edit.from) : moved), offset);
|
|
64
|
+
}
|
|
61
65
|
/* Merges the carets' edits into one simultaneous batch and works out where each caret lands in the result. */
|
|
62
66
|
function selectionTransaction(plans) {
|
|
63
67
|
const edits = [];
|
|
@@ -591,7 +595,8 @@ export class DocumentModel {
|
|
|
591
595
|
/*
|
|
592
596
|
* Types `text` at every caret as one keystroke would: it wraps a selection in a bracket or quote,
|
|
593
597
|
* closes an opener, types over a closer that is already there and, in TypeScript, JavaScript and
|
|
594
|
-
* PHP, moves a `;` to the end of a statement. In PHP a `-` after a variable becomes `->`.
|
|
598
|
+
* PHP, moves a `;` to the end of a statement. In PHP a `-` after a variable becomes `->`. In JavaScript and
|
|
599
|
+
* TypeScript a `{` that completes `${` in a quoted string makes it a template literal. Comments
|
|
595
600
|
* and string contents are left alone. Returns whether anything changed, a swallowed closer included.
|
|
596
601
|
*/
|
|
597
602
|
typeText(text, options = {}) {
|
|
@@ -617,6 +622,7 @@ export class DocumentModel {
|
|
|
617
622
|
surround: options.surroundSelection !== false && single,
|
|
618
623
|
smartSemicolon: options.smartSemicolon !== false && hasSmartSemicolon(language),
|
|
619
624
|
smartArrow: options.smartArrow !== false && single && isPhp(language),
|
|
625
|
+
templateLiterals: options.templateLiterals !== false && text === '{',
|
|
620
626
|
overArrows: text === '>' ? arrows : new Set(),
|
|
621
627
|
dedent: options.smartEnter !== false
|
|
622
628
|
};
|
|
@@ -630,6 +636,7 @@ export class DocumentModel {
|
|
|
630
636
|
this.rememberStatementClosers(plans, transaction, language);
|
|
631
637
|
this.rememberPairs(plans, transaction);
|
|
632
638
|
this.completeArrows(plans, transaction);
|
|
639
|
+
this.completeTemplates(plans, transaction, language);
|
|
633
640
|
}
|
|
634
641
|
return changed || plans.some((plan) => plan.consumed);
|
|
635
642
|
}
|
|
@@ -654,6 +661,23 @@ export class DocumentModel {
|
|
|
654
661
|
this.arrows = new Set(at.map(moved));
|
|
655
662
|
}
|
|
656
663
|
}
|
|
664
|
+
/* Turns the quoted strings a `${` was typed in into template literals, as a step of their own so the first undo leaves the quotes back. */
|
|
665
|
+
completeTemplates(plans, transaction, language) {
|
|
666
|
+
// Carets in one string find the same quotes.
|
|
667
|
+
const strings = new Map();
|
|
668
|
+
for (const plan of plans) {
|
|
669
|
+
if (plan.template) {
|
|
670
|
+
strings.set(shifted(transaction.edits, plan.template.open), shifted(transaction.edits, plan.template.close));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const source = this.editSource(language);
|
|
674
|
+
const edits = [...strings].flatMap(([open, close]) => templateLiteralEdits(source, open, close));
|
|
675
|
+
if (edits.length > 0) {
|
|
676
|
+
// A caret right before a closing quote stays there, which the default mapping of an edit that replaces the quote would not do.
|
|
677
|
+
const selections = this.selections.map((selection) => ({ anchor: shifted(edits, selection.anchor), head: shifted(edits, selection.head) }));
|
|
678
|
+
this.applyEdits(edits, { source: 'input', selections });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
657
681
|
/*
|
|
658
682
|
* Pastes `text` at every selection. With as many lines as carets each caret takes one; `wholeLines`
|
|
659
683
|
* marks text copied from a bare caret, which goes above the lines of bare carets.
|
|
@@ -844,6 +868,12 @@ export class DocumentModel {
|
|
|
844
868
|
return wrapped;
|
|
845
869
|
}
|
|
846
870
|
}
|
|
871
|
+
if (input.templateLiterals && from === to) {
|
|
872
|
+
const template = templateString(this.editSource(language), from, language);
|
|
873
|
+
if (template) {
|
|
874
|
+
return { from, to, text: input.brackets ? '{}' : '{', anchor: 1, head: 1, paired: input.brackets, template };
|
|
875
|
+
}
|
|
876
|
+
}
|
|
847
877
|
if (pairing && pairs[text] !== undefined && from === to && this.shouldClosePair(from, text, after, context, input)) {
|
|
848
878
|
return { from, to, text: text + pairs[text], anchor: 1, head: 1, paired: true };
|
|
849
879
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EditSource } from './edit-source.ts';
|
|
2
|
+
import type { TextEdit } from './types.ts';
|
|
2
3
|
export interface HandlerEdit {
|
|
3
4
|
from: number;
|
|
4
5
|
to: number;
|
|
@@ -13,3 +14,8 @@ export declare function surround(selected: string, typed: string): {
|
|
|
13
14
|
text: string;
|
|
14
15
|
inner: string;
|
|
15
16
|
};
|
|
17
|
+
export declare function templateString(source: EditSource, offset: number, language: string): {
|
|
18
|
+
open: number;
|
|
19
|
+
close: number;
|
|
20
|
+
} | null;
|
|
21
|
+
export declare function templateLiteralEdits(source: EditSource, open: number, close: number): TextEdit[];
|
package/dist/typing-handlers.js
CHANGED
|
@@ -123,3 +123,66 @@ export function surround(selected, typed) {
|
|
|
123
123
|
}
|
|
124
124
|
return { text: typed + inner + matchingDelimiter(typed), inner };
|
|
125
125
|
}
|
|
126
|
+
const scriptLanguages = /^(javascript|typescript|jsx|tsx|javascriptreact|typescriptreact|js|ts|mjs|cjs|mts|cts)$/i;
|
|
127
|
+
const moduleSpecifier = /(?:\bfrom|\bimport|\brequire)\s*\(?\s*$/;
|
|
128
|
+
/*
|
|
129
|
+
* The quotes of the string a `${` is typed in when that makes it a template literal: a single- or
|
|
130
|
+
* double-quoted string of a script, closed on its line and not the specifier of a module.
|
|
131
|
+
*/
|
|
132
|
+
export function templateString(source, offset, language) {
|
|
133
|
+
const line = source.lineAt(offset);
|
|
134
|
+
const script = scriptLanguages.test(language) || (/^vue$/i.test(language) && source.region(line) === 'script');
|
|
135
|
+
const { start, end } = source.line(line);
|
|
136
|
+
const before = source.slice(start, offset);
|
|
137
|
+
if (!script || !before.endsWith('$') || escapedIn(before, before.length - 1)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const context = source.context(offset);
|
|
141
|
+
if (context.mode !== 'quote' || context.escaped || (context.quote !== "'" && context.quote !== '"')) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
let open = before.length - 1;
|
|
145
|
+
while (open >= 0 && (before[open] !== context.quote || escapedIn(before, open))) {
|
|
146
|
+
open--;
|
|
147
|
+
}
|
|
148
|
+
if (open < 0 || moduleSpecifier.test(before.slice(0, open))) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const after = source.slice(offset, end);
|
|
152
|
+
for (let at = 0; at < after.length; at++) {
|
|
153
|
+
if (after[at] === '\\') {
|
|
154
|
+
at++;
|
|
155
|
+
}
|
|
156
|
+
else if (after[at] === context.quote) {
|
|
157
|
+
return { open: start + open, close: offset + at };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function escapedIn(text, index) {
|
|
163
|
+
let slashes = 0;
|
|
164
|
+
while (index - slashes - 1 >= 0 && text[index - slashes - 1] === '\\') {
|
|
165
|
+
slashes++;
|
|
166
|
+
}
|
|
167
|
+
return slashes % 2 === 1;
|
|
168
|
+
}
|
|
169
|
+
/* The edits that make the string between the quotes at `open` and `close` a template literal: its backticks are escaped and its escaped old quotes are not. */
|
|
170
|
+
export function templateLiteralEdits(source, open, close) {
|
|
171
|
+
const quote = source.charAt(open);
|
|
172
|
+
const body = source.slice(open + 1, close);
|
|
173
|
+
const edits = [{ from: open, to: open + 1, text: '`' }];
|
|
174
|
+
for (let index = 0; index < body.length; index++) {
|
|
175
|
+
const at = open + 1 + index;
|
|
176
|
+
if (body[index] === '\\') {
|
|
177
|
+
if (body[index + 1] === quote) {
|
|
178
|
+
edits.push({ from: at, to: at + 1, text: '' });
|
|
179
|
+
}
|
|
180
|
+
index++;
|
|
181
|
+
}
|
|
182
|
+
else if (body[index] === '`') {
|
|
183
|
+
edits.push({ from: at, to: at + 1, text: '\\`' });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
edits.push({ from: close, to: close + 1, text: '`' });
|
|
187
|
+
return edits;
|
|
188
|
+
}
|
package/package.json
CHANGED
package/src/document.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { blockFolds } from './block-folds.ts';
|
|
|
16
16
|
import { applyFoldHints, markFileHeader } from './fold-roles.ts';
|
|
17
17
|
import { lexicalFolds } from './lexical-folds.ts';
|
|
18
18
|
import { rangeOf, whitespaceOf } from './line-text.ts';
|
|
19
|
-
import { isQuote, replacesComparison, surround, swapQuotes } from './typing-handlers.ts';
|
|
19
|
+
import { isQuote, replacesComparison, surround, swapQuotes, templateLiteralEdits, templateString } from './typing-handlers.ts';
|
|
20
20
|
import { scanBrackets } from './brackets.ts';
|
|
21
21
|
import type { BracketIndex, BracketMatch } from './brackets.ts';
|
|
22
22
|
import { BracketTracker } from './bracket-tracker.ts';
|
|
@@ -109,6 +109,8 @@ interface SelectionEdit extends TextEdit {
|
|
|
109
109
|
paired?: boolean;
|
|
110
110
|
/* A `-` the editor completes to `->` in a step of its own, once the `-` is in. */
|
|
111
111
|
arrow?: boolean;
|
|
112
|
+
/* A `${` typed in a quoted string, which becomes a template literal in a step of its own; where its quotes stand before the edit. */
|
|
113
|
+
template?: { open: number; close: number };
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
interface SelectionTransaction {
|
|
@@ -130,6 +132,7 @@ interface TypingInput {
|
|
|
130
132
|
surround: boolean;
|
|
131
133
|
smartSemicolon: boolean;
|
|
132
134
|
smartArrow: boolean;
|
|
135
|
+
templateLiterals: boolean;
|
|
133
136
|
/* The carets right after a `->` the editor completed, where a typed `>` goes over the one that is there. */
|
|
134
137
|
overArrows: ReadonlySet<number>;
|
|
135
138
|
/* A closing bracket typed on a line of its own goes back to the indentation of its opener. */
|
|
@@ -187,6 +190,11 @@ function changesText(rope: TextRope, edit: TextEdit): boolean {
|
|
|
187
190
|
return edit.text.length !== edit.to - edit.from || rope.slice(edit.from, edit.to) !== edit.text;
|
|
188
191
|
}
|
|
189
192
|
|
|
193
|
+
/* Where an offset stands after edits that end at or before it moved it; an insertion at the offset pushes it on. */
|
|
194
|
+
function shifted(edits: readonly TextEdit[], offset: number): number {
|
|
195
|
+
return edits.reduce((moved, edit) => (edit.to <= offset ? moved + edit.text.length - (edit.to - edit.from) : moved), offset);
|
|
196
|
+
}
|
|
197
|
+
|
|
190
198
|
/* Merges the carets' edits into one simultaneous batch and works out where each caret lands in the result. */
|
|
191
199
|
function selectionTransaction(plans: readonly SelectionEdit[]): SelectionTransaction {
|
|
192
200
|
const edits: TextEdit[] = [];
|
|
@@ -883,7 +891,8 @@ export class DocumentModel {
|
|
|
883
891
|
/*
|
|
884
892
|
* Types `text` at every caret as one keystroke would: it wraps a selection in a bracket or quote,
|
|
885
893
|
* closes an opener, types over a closer that is already there and, in TypeScript, JavaScript and
|
|
886
|
-
* PHP, moves a `;` to the end of a statement. In PHP a `-` after a variable becomes `->`.
|
|
894
|
+
* PHP, moves a `;` to the end of a statement. In PHP a `-` after a variable becomes `->`. In JavaScript and
|
|
895
|
+
* TypeScript a `{` that completes `${` in a quoted string makes it a template literal. Comments
|
|
887
896
|
* and string contents are left alone. Returns whether anything changed, a swallowed closer included.
|
|
888
897
|
*/
|
|
889
898
|
typeText(text: string, options: TypeTextOptions = {}): boolean {
|
|
@@ -914,6 +923,7 @@ export class DocumentModel {
|
|
|
914
923
|
surround: options.surroundSelection !== false && single,
|
|
915
924
|
smartSemicolon: options.smartSemicolon !== false && hasSmartSemicolon(language),
|
|
916
925
|
smartArrow: options.smartArrow !== false && single && isPhp(language),
|
|
926
|
+
templateLiterals: options.templateLiterals !== false && text === '{',
|
|
917
927
|
overArrows: text === '>' ? arrows : new Set(),
|
|
918
928
|
dedent: options.smartEnter !== false
|
|
919
929
|
};
|
|
@@ -935,6 +945,7 @@ export class DocumentModel {
|
|
|
935
945
|
this.rememberStatementClosers(plans, transaction, language);
|
|
936
946
|
this.rememberPairs(plans, transaction);
|
|
937
947
|
this.completeArrows(plans, transaction);
|
|
948
|
+
this.completeTemplates(plans, transaction, language);
|
|
938
949
|
}
|
|
939
950
|
|
|
940
951
|
return changed || plans.some((plan) => plan.consumed);
|
|
@@ -972,6 +983,27 @@ export class DocumentModel {
|
|
|
972
983
|
}
|
|
973
984
|
}
|
|
974
985
|
|
|
986
|
+
/* Turns the quoted strings a `${` was typed in into template literals, as a step of their own so the first undo leaves the quotes back. */
|
|
987
|
+
private completeTemplates(plans: readonly SelectionEdit[], transaction: SelectionTransaction, language: string): void {
|
|
988
|
+
// Carets in one string find the same quotes.
|
|
989
|
+
const strings = new Map<number, number>();
|
|
990
|
+
|
|
991
|
+
for (const plan of plans) {
|
|
992
|
+
if (plan.template) {
|
|
993
|
+
strings.set(shifted(transaction.edits, plan.template.open), shifted(transaction.edits, plan.template.close));
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const source = this.editSource(language);
|
|
998
|
+
const edits = [...strings].flatMap(([open, close]) => templateLiteralEdits(source, open, close));
|
|
999
|
+
|
|
1000
|
+
if (edits.length > 0) {
|
|
1001
|
+
// A caret right before a closing quote stays there, which the default mapping of an edit that replaces the quote would not do.
|
|
1002
|
+
const selections = this.selections.map((selection) => ({ anchor: shifted(edits, selection.anchor), head: shifted(edits, selection.head) }));
|
|
1003
|
+
this.applyEdits(edits, { source: 'input', selections });
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
975
1007
|
/*
|
|
976
1008
|
* Pastes `text` at every selection. With as many lines as carets each caret takes one; `wholeLines`
|
|
977
1009
|
* marks text copied from a bare caret, which goes above the lines of bare carets.
|
|
@@ -1233,6 +1265,14 @@ export class DocumentModel {
|
|
|
1233
1265
|
}
|
|
1234
1266
|
}
|
|
1235
1267
|
|
|
1268
|
+
if (input.templateLiterals && from === to) {
|
|
1269
|
+
const template = templateString(this.editSource(language), from, language);
|
|
1270
|
+
|
|
1271
|
+
if (template) {
|
|
1272
|
+
return { from, to, text: input.brackets ? '{}' : '{', anchor: 1, head: 1, paired: input.brackets, template };
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1236
1276
|
if (pairing && pairs[text] !== undefined && from === to && this.shouldClosePair(from, text, after, context, input)) {
|
|
1237
1277
|
return { from, to, text: text + pairs[text], anchor: 1, head: 1, paired: true };
|
|
1238
1278
|
}
|
package/src/types.ts
CHANGED
|
@@ -147,6 +147,8 @@ export interface CommandOptions {
|
|
|
147
147
|
smartSemicolon?: boolean;
|
|
148
148
|
/* In PHP a `-` typed after a variable, a member or a `)` or `]` becomes `->`. On unless false. */
|
|
149
149
|
smartArrow?: boolean;
|
|
150
|
+
/* In JavaScript and TypeScript a `${` typed in a quoted string makes it a template literal, in a second undo step. On unless false. */
|
|
151
|
+
templateLiterals?: boolean;
|
|
150
152
|
/* Tab steps over a closer the editor inserted. On unless false. */
|
|
151
153
|
tabOutOfClosers?: boolean;
|
|
152
154
|
/* Enter computes the indentation of the new line, continues comments and closes braces. On unless false. */
|
package/src/typing-handlers.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EditSource } from './edit-source.ts';
|
|
2
|
+
import type { TextEdit } from './types.ts';
|
|
2
3
|
|
|
3
4
|
/* A change of text in one place and where the caret stands in what replaces it. */
|
|
4
5
|
export interface HandlerEdit {
|
|
@@ -169,3 +170,85 @@ export function surround(selected: string, typed: string): { text: string; inner
|
|
|
169
170
|
|
|
170
171
|
return { text: typed + inner + matchingDelimiter(typed), inner };
|
|
171
172
|
}
|
|
173
|
+
|
|
174
|
+
const scriptLanguages = /^(javascript|typescript|jsx|tsx|javascriptreact|typescriptreact|js|ts|mjs|cjs|mts|cts)$/i;
|
|
175
|
+
const moduleSpecifier = /(?:\bfrom|\bimport|\brequire)\s*\(?\s*$/;
|
|
176
|
+
|
|
177
|
+
/*
|
|
178
|
+
* The quotes of the string a `${` is typed in when that makes it a template literal: a single- or
|
|
179
|
+
* double-quoted string of a script, closed on its line and not the specifier of a module.
|
|
180
|
+
*/
|
|
181
|
+
export function templateString(source: EditSource, offset: number, language: string): { open: number; close: number } | null {
|
|
182
|
+
const line = source.lineAt(offset);
|
|
183
|
+
const script = scriptLanguages.test(language) || (/^vue$/i.test(language) && source.region(line) === 'script');
|
|
184
|
+
|
|
185
|
+
const { start, end } = source.line(line);
|
|
186
|
+
const before = source.slice(start, offset);
|
|
187
|
+
|
|
188
|
+
if (!script || !before.endsWith('$') || escapedIn(before, before.length - 1)) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const context = source.context(offset);
|
|
193
|
+
|
|
194
|
+
if (context.mode !== 'quote' || context.escaped || (context.quote !== "'" && context.quote !== '"')) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let open = before.length - 1;
|
|
199
|
+
|
|
200
|
+
while (open >= 0 && (before[open] !== context.quote || escapedIn(before, open))) {
|
|
201
|
+
open--;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (open < 0 || moduleSpecifier.test(before.slice(0, open))) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const after = source.slice(offset, end);
|
|
209
|
+
|
|
210
|
+
for (let at = 0; at < after.length; at++) {
|
|
211
|
+
if (after[at] === '\\') {
|
|
212
|
+
at++;
|
|
213
|
+
} else if (after[at] === context.quote) {
|
|
214
|
+
return { open: start + open, close: offset + at };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function escapedIn(text: string, index: number): boolean {
|
|
222
|
+
let slashes = 0;
|
|
223
|
+
|
|
224
|
+
while (index - slashes - 1 >= 0 && text[index - slashes - 1] === '\\') {
|
|
225
|
+
slashes++;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return slashes % 2 === 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* The edits that make the string between the quotes at `open` and `close` a template literal: its backticks are escaped and its escaped old quotes are not. */
|
|
232
|
+
export function templateLiteralEdits(source: EditSource, open: number, close: number): TextEdit[] {
|
|
233
|
+
const quote = source.charAt(open);
|
|
234
|
+
const body = source.slice(open + 1, close);
|
|
235
|
+
const edits: TextEdit[] = [{ from: open, to: open + 1, text: '`' }];
|
|
236
|
+
|
|
237
|
+
for (let index = 0; index < body.length; index++) {
|
|
238
|
+
const at = open + 1 + index;
|
|
239
|
+
|
|
240
|
+
if (body[index] === '\\') {
|
|
241
|
+
if (body[index + 1] === quote) {
|
|
242
|
+
edits.push({ from: at, to: at + 1, text: '' });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
index++;
|
|
246
|
+
} else if (body[index] === '`') {
|
|
247
|
+
edits.push({ from: at, to: at + 1, text: '\\`' });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
edits.push({ from: close, to: close + 1, text: '`' });
|
|
252
|
+
|
|
253
|
+
return edits;
|
|
254
|
+
}
|