@codemirror/autocomplete 6.4.2 → 6.5.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/CHANGELOG.md +14 -0
- package/dist/index.cjs +120 -74
- package/dist/index.d.ts +48 -2
- package/dist/index.js +121 -75
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 6.5.0 (2023-04-13)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
When `closeBrackets` skips a bracket, it now generates a change that overwrites the bracket.
|
|
6
|
+
|
|
7
|
+
Replace the entire selected range when picking a completion with a non-cursor selection active.
|
|
8
|
+
|
|
9
|
+
### New features
|
|
10
|
+
|
|
11
|
+
Completions can now provide a `section` field that is used to group them into sections.
|
|
12
|
+
|
|
13
|
+
The new `positionInfo` option can be used to provide custom logic for positioning the info tooltips.
|
|
14
|
+
|
|
1
15
|
## 6.4.2 (2023-02-17)
|
|
2
16
|
|
|
3
17
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -137,13 +137,14 @@ function ifNotIn(nodes, source) {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
class Option {
|
|
140
|
-
constructor(completion, source, match) {
|
|
140
|
+
constructor(completion, source, match, score) {
|
|
141
141
|
this.completion = completion;
|
|
142
142
|
this.source = source;
|
|
143
143
|
this.match = match;
|
|
144
|
+
this.score = score;
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
|
-
function cur(state) { return state.selection.main.
|
|
147
|
+
function cur(state) { return state.selection.main.from; }
|
|
147
148
|
// Make sure the given regexp has a $ at its end and, if `start` is
|
|
148
149
|
// true, a ^ at its start.
|
|
149
150
|
function ensureAnchor(expr, start) {
|
|
@@ -165,18 +166,13 @@ completion's text in the main selection range, and any other
|
|
|
165
166
|
selection range that has the same text in front of it.
|
|
166
167
|
*/
|
|
167
168
|
function insertCompletionText(state$1, text, from, to) {
|
|
169
|
+
let { main } = state$1.selection, len = to - from;
|
|
168
170
|
return Object.assign(Object.assign({}, state$1.changeByRange(range => {
|
|
169
|
-
if (range
|
|
170
|
-
|
|
171
|
-
changes: { from: from, to: to, insert: text },
|
|
172
|
-
range: state.EditorSelection.cursor(from + text.length)
|
|
173
|
-
};
|
|
174
|
-
let len = to - from;
|
|
175
|
-
if (!range.empty ||
|
|
176
|
-
len && state$1.sliceDoc(range.from - len, range.from) != state$1.sliceDoc(from, to))
|
|
171
|
+
if (range != main && len &&
|
|
172
|
+
state$1.sliceDoc(range.from - len, range.from + to - main.from) != state$1.sliceDoc(from, to))
|
|
177
173
|
return { range };
|
|
178
174
|
return {
|
|
179
|
-
changes: { from: range.from - len, to: range.from, insert: text },
|
|
175
|
+
changes: { from: range.from - len, to: to == main.from ? range.to : range.from + to - main.from, insert: text },
|
|
180
176
|
range: state.EditorSelection.cursor(range.from - len + text.length)
|
|
181
177
|
};
|
|
182
178
|
})), { userEvent: "input.complete" });
|
|
@@ -343,6 +339,7 @@ const completionConfig = state.Facet.define({
|
|
|
343
339
|
aboveCursor: false,
|
|
344
340
|
icons: true,
|
|
345
341
|
addToOptions: [],
|
|
342
|
+
positionInfo: defaultPositionInfo,
|
|
346
343
|
compareCompletions: (a, b) => a.label.localeCompare(b.label),
|
|
347
344
|
interactionDelay: 75
|
|
348
345
|
}, {
|
|
@@ -358,6 +355,36 @@ const completionConfig = state.Facet.define({
|
|
|
358
355
|
function joinClass(a, b) {
|
|
359
356
|
return a ? b ? a + " " + b : a : b;
|
|
360
357
|
}
|
|
358
|
+
function defaultPositionInfo(view$1, list, option, info, space) {
|
|
359
|
+
let rtl = view$1.textDirection == view.Direction.RTL, left = rtl, narrow = false;
|
|
360
|
+
let side = "top", offset, maxWidth;
|
|
361
|
+
let spaceLeft = list.left - space.left, spaceRight = space.right - list.right;
|
|
362
|
+
let infoWidth = info.right - info.left, infoHeight = info.bottom - info.top;
|
|
363
|
+
if (left && spaceLeft < Math.min(infoWidth, spaceRight))
|
|
364
|
+
left = false;
|
|
365
|
+
else if (!left && spaceRight < Math.min(infoWidth, spaceLeft))
|
|
366
|
+
left = true;
|
|
367
|
+
if (infoWidth <= (left ? spaceLeft : spaceRight)) {
|
|
368
|
+
offset = Math.max(space.top, Math.min(option.top, space.bottom - infoHeight)) - list.top;
|
|
369
|
+
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
narrow = true;
|
|
373
|
+
maxWidth = Math.min(400 /* Info.Width */, (rtl ? list.right : space.right - list.left) - 30 /* Info.Margin */);
|
|
374
|
+
let spaceBelow = space.bottom - list.bottom;
|
|
375
|
+
if (spaceBelow >= infoHeight || spaceBelow > list.top) { // Below the completion
|
|
376
|
+
offset = option.bottom - list.top;
|
|
377
|
+
}
|
|
378
|
+
else { // Above it
|
|
379
|
+
side = "bottom";
|
|
380
|
+
offset = list.bottom - option.top;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
style: `${side}: ${offset}px; max-width: ${maxWidth}px`,
|
|
385
|
+
class: "cm-completionInfo-" + (narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right")
|
|
386
|
+
};
|
|
387
|
+
}
|
|
361
388
|
|
|
362
389
|
function optionContent(config) {
|
|
363
390
|
let content = config.addToOptions.slice();
|
|
@@ -422,9 +449,9 @@ class CompletionTooltip {
|
|
|
422
449
|
this.view = view;
|
|
423
450
|
this.stateField = stateField;
|
|
424
451
|
this.info = null;
|
|
425
|
-
this.
|
|
452
|
+
this.placeInfoReq = {
|
|
426
453
|
read: () => this.measureInfo(),
|
|
427
|
-
write: (pos) => this.
|
|
454
|
+
write: (pos) => this.placeInfo(pos),
|
|
428
455
|
key: this
|
|
429
456
|
};
|
|
430
457
|
this.space = null;
|
|
@@ -451,7 +478,7 @@ class CompletionTooltip {
|
|
|
451
478
|
this.list = this.dom.appendChild(this.createListBox(options, cState.id, this.range));
|
|
452
479
|
this.list.addEventListener("scroll", () => {
|
|
453
480
|
if (this.info)
|
|
454
|
-
this.view.requestMeasure(this.
|
|
481
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
455
482
|
});
|
|
456
483
|
}
|
|
457
484
|
mount() { this.updateSel(); }
|
|
@@ -481,7 +508,7 @@ class CompletionTooltip {
|
|
|
481
508
|
positioned(space) {
|
|
482
509
|
this.space = space;
|
|
483
510
|
if (this.info)
|
|
484
|
-
this.view.requestMeasure(this.
|
|
511
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
485
512
|
}
|
|
486
513
|
updateSel() {
|
|
487
514
|
let cState = this.view.state.field(this.stateField), open = cState.open;
|
|
@@ -491,7 +518,7 @@ class CompletionTooltip {
|
|
|
491
518
|
this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range));
|
|
492
519
|
this.list.addEventListener("scroll", () => {
|
|
493
520
|
if (this.info)
|
|
494
|
-
this.view.requestMeasure(this.
|
|
521
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
495
522
|
});
|
|
496
523
|
}
|
|
497
524
|
if (this.updateSelectedOption(open.selected)) {
|
|
@@ -522,12 +549,15 @@ class CompletionTooltip {
|
|
|
522
549
|
dom.className = "cm-tooltip cm-completionInfo";
|
|
523
550
|
dom.appendChild(content);
|
|
524
551
|
this.dom.appendChild(dom);
|
|
525
|
-
this.view.requestMeasure(this.
|
|
552
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
526
553
|
}
|
|
527
554
|
updateSelectedOption(selected) {
|
|
528
555
|
let set = null;
|
|
529
556
|
for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
|
|
530
|
-
if (
|
|
557
|
+
if (opt.nodeName != "LI" || !opt.id) {
|
|
558
|
+
i--; // A section header
|
|
559
|
+
}
|
|
560
|
+
else if (i == selected) {
|
|
531
561
|
if (!opt.hasAttribute("aria-selected")) {
|
|
532
562
|
opt.setAttribute("aria-selected", "true");
|
|
533
563
|
set = opt;
|
|
@@ -557,41 +587,17 @@ class CompletionTooltip {
|
|
|
557
587
|
if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
|
|
558
588
|
selRect.bottom < Math.max(space.top, listRect.top) + 10)
|
|
559
589
|
return null;
|
|
560
|
-
|
|
561
|
-
let top = "", bottom = "";
|
|
562
|
-
let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
|
|
563
|
-
if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
|
|
564
|
-
left = false;
|
|
565
|
-
else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
|
|
566
|
-
left = true;
|
|
567
|
-
if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
|
|
568
|
-
top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
|
|
569
|
-
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
narrow = true;
|
|
573
|
-
maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
|
|
574
|
-
let spaceBelow = space.bottom - listRect.bottom;
|
|
575
|
-
if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
|
|
576
|
-
top = (selRect.bottom - listRect.top) + "px";
|
|
577
|
-
else // Above it
|
|
578
|
-
bottom = (listRect.bottom - selRect.top) + "px";
|
|
579
|
-
}
|
|
580
|
-
return {
|
|
581
|
-
top, bottom, maxWidth,
|
|
582
|
-
class: narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right",
|
|
583
|
-
};
|
|
590
|
+
return this.view.state.facet(completionConfig).positionInfo(this.view, listRect, selRect, infoRect, space);
|
|
584
591
|
}
|
|
585
|
-
|
|
592
|
+
placeInfo(pos) {
|
|
586
593
|
if (this.info) {
|
|
587
594
|
if (pos) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
this.info.
|
|
591
|
-
this.info.className = "cm-tooltip cm-completionInfo cm-completionInfo-" + pos.class;
|
|
595
|
+
if (pos.style)
|
|
596
|
+
this.info.style.cssText = pos.style;
|
|
597
|
+
this.info.className = "cm-tooltip cm-completionInfo " + (pos.class || "");
|
|
592
598
|
}
|
|
593
599
|
else {
|
|
594
|
-
this.info.style.
|
|
600
|
+
this.info.style.cssText = "top: -1e6px";
|
|
595
601
|
}
|
|
596
602
|
}
|
|
597
603
|
}
|
|
@@ -601,8 +607,22 @@ class CompletionTooltip {
|
|
|
601
607
|
ul.setAttribute("role", "listbox");
|
|
602
608
|
ul.setAttribute("aria-expanded", "true");
|
|
603
609
|
ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
|
|
610
|
+
let curSection = null;
|
|
604
611
|
for (let i = range.from; i < range.to; i++) {
|
|
605
|
-
let { completion, match } = options[i];
|
|
612
|
+
let { completion, match } = options[i], { section } = completion;
|
|
613
|
+
if (section) {
|
|
614
|
+
let name = typeof section == "string" ? section : section.name;
|
|
615
|
+
if (name != curSection && (i > range.from || range.from == 0)) {
|
|
616
|
+
curSection = name;
|
|
617
|
+
if (typeof section != "string" && section.header) {
|
|
618
|
+
ul.appendChild(section.header(section));
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
let header = ul.appendChild(document.createElement("completion-section"));
|
|
622
|
+
header.textContent = name;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
606
626
|
const li = ul.appendChild(document.createElement("li"));
|
|
607
627
|
li.id = id + "-" + i;
|
|
608
628
|
li.setAttribute("role", "option");
|
|
@@ -643,32 +663,55 @@ function score(option) {
|
|
|
643
663
|
(option.type ? 1 : 0);
|
|
644
664
|
}
|
|
645
665
|
function sortOptions(active, state) {
|
|
646
|
-
let options = []
|
|
666
|
+
let options = [];
|
|
667
|
+
let sections = null;
|
|
668
|
+
let addOption = (option) => {
|
|
669
|
+
options.push(option);
|
|
670
|
+
let { section } = option.completion;
|
|
671
|
+
if (section) {
|
|
672
|
+
if (!sections)
|
|
673
|
+
sections = [];
|
|
674
|
+
let name = typeof section == "string" ? section : section.name;
|
|
675
|
+
if (!sections.some(s => s.name == name))
|
|
676
|
+
sections.push(typeof section == "string" ? { name } : section);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
647
679
|
for (let a of active)
|
|
648
680
|
if (a.hasResult()) {
|
|
649
681
|
if (a.result.filter === false) {
|
|
650
682
|
let getMatch = a.result.getMatch;
|
|
651
683
|
for (let option of a.result.options) {
|
|
652
|
-
let match = [1e9 -
|
|
684
|
+
let match = [1e9 - options.length];
|
|
653
685
|
if (getMatch)
|
|
654
686
|
for (let n of getMatch(option))
|
|
655
687
|
match.push(n);
|
|
656
|
-
|
|
688
|
+
addOption(new Option(option, a, match, match[0]));
|
|
657
689
|
}
|
|
658
690
|
}
|
|
659
691
|
else {
|
|
660
692
|
let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
|
|
661
693
|
for (let option of a.result.options)
|
|
662
694
|
if (match = matcher.match(option.label)) {
|
|
663
|
-
|
|
664
|
-
match[0] += option.boost;
|
|
665
|
-
options.push(new Option(option, a, match));
|
|
695
|
+
addOption(new Option(option, a, match, match[0] + (option.boost || 0)));
|
|
666
696
|
}
|
|
667
697
|
}
|
|
668
698
|
}
|
|
699
|
+
if (sections) {
|
|
700
|
+
let sectionOrder = Object.create(null), pos = 0;
|
|
701
|
+
let cmp = (a, b) => { var _a, _b; return ((_a = a.rank) !== null && _a !== void 0 ? _a : 1e9) - ((_b = b.rank) !== null && _b !== void 0 ? _b : 1e9) || (a.name < b.name ? -1 : 1); };
|
|
702
|
+
for (let s of sections.sort(cmp)) {
|
|
703
|
+
pos -= 1e5;
|
|
704
|
+
sectionOrder[s.name] = pos;
|
|
705
|
+
}
|
|
706
|
+
for (let option of options) {
|
|
707
|
+
let { section } = option.completion;
|
|
708
|
+
if (section)
|
|
709
|
+
option.score += sectionOrder[typeof section == "string" ? section : section.name];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
669
712
|
let result = [], prev = null;
|
|
670
713
|
let compare = state.facet(completionConfig).compareCompletions;
|
|
671
|
-
for (let opt of options.sort((a, b) => (b.
|
|
714
|
+
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
|
|
672
715
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
673
716
|
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
674
717
|
prev.apply != opt.completion.apply)
|
|
@@ -1103,13 +1146,21 @@ const baseTheme = view.EditorView.baseTheme({
|
|
|
1103
1146
|
listStyle: "none",
|
|
1104
1147
|
margin: 0,
|
|
1105
1148
|
padding: 0,
|
|
1149
|
+
"& > li, & > completion-section": {
|
|
1150
|
+
padding: "1px 3px",
|
|
1151
|
+
lineHeight: 1.2
|
|
1152
|
+
},
|
|
1106
1153
|
"& > li": {
|
|
1107
1154
|
overflowX: "hidden",
|
|
1108
1155
|
textOverflow: "ellipsis",
|
|
1109
|
-
cursor: "pointer"
|
|
1110
|
-
padding: "1px 3px",
|
|
1111
|
-
lineHeight: 1.2
|
|
1156
|
+
cursor: "pointer"
|
|
1112
1157
|
},
|
|
1158
|
+
"& > completion-section": {
|
|
1159
|
+
display: "list-item",
|
|
1160
|
+
borderBottom: "1px solid silver",
|
|
1161
|
+
paddingLeft: "0.5em",
|
|
1162
|
+
opacity: 0.7
|
|
1163
|
+
}
|
|
1113
1164
|
}
|
|
1114
1165
|
},
|
|
1115
1166
|
"&light .cm-tooltip-autocomplete ul li[aria-selected]": {
|
|
@@ -1543,9 +1594,6 @@ const closeBracketEffect = state.StateEffect.define({
|
|
|
1543
1594
|
return mapped == null ? undefined : mapped;
|
|
1544
1595
|
}
|
|
1545
1596
|
});
|
|
1546
|
-
const skipBracketEffect = state.StateEffect.define({
|
|
1547
|
-
map(value, mapping) { return mapping.mapPos(value); }
|
|
1548
|
-
});
|
|
1549
1597
|
const closedBracket = new class extends state.RangeValue {
|
|
1550
1598
|
};
|
|
1551
1599
|
closedBracket.startSide = 1;
|
|
@@ -1560,12 +1608,9 @@ const bracketState = state.StateField.define({
|
|
|
1560
1608
|
value = state.RangeSet.empty;
|
|
1561
1609
|
}
|
|
1562
1610
|
value = value.map(tr.changes);
|
|
1563
|
-
for (let effect of tr.effects)
|
|
1611
|
+
for (let effect of tr.effects)
|
|
1564
1612
|
if (effect.is(closeBracketEffect))
|
|
1565
1613
|
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1566
|
-
else if (effect.is(skipBracketEffect))
|
|
1567
|
-
value = value.update({ filter: from => from != effect.value });
|
|
1568
|
-
}
|
|
1569
1614
|
return value;
|
|
1570
1615
|
}
|
|
1571
1616
|
});
|
|
@@ -1693,15 +1738,15 @@ function handleOpen(state$1, open, close, closeBefore) {
|
|
|
1693
1738
|
});
|
|
1694
1739
|
}
|
|
1695
1740
|
function handleClose(state$1, _open, close) {
|
|
1696
|
-
let dont = null,
|
|
1741
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1697
1742
|
if (range.empty && nextChar(state$1.doc, range.head) == close)
|
|
1698
|
-
return
|
|
1699
|
-
|
|
1743
|
+
return { changes: { from: range.head, to: range.head + close.length, insert: close },
|
|
1744
|
+
range: state.EditorSelection.cursor(range.head + close.length) };
|
|
1745
|
+
return dont = { range };
|
|
1700
1746
|
});
|
|
1701
|
-
return dont ? null : state$1.update({
|
|
1702
|
-
selection: state.EditorSelection.create(moved, state$1.selection.mainIndex),
|
|
1747
|
+
return dont ? null : state$1.update(changes, {
|
|
1703
1748
|
scrollIntoView: true,
|
|
1704
|
-
|
|
1749
|
+
userEvent: "input.type"
|
|
1705
1750
|
});
|
|
1706
1751
|
}
|
|
1707
1752
|
// Handles cases where the open and close token are the same, and
|
|
@@ -1722,8 +1767,9 @@ function handleSame(state$1, token, allowTriple, config) {
|
|
|
1722
1767
|
}
|
|
1723
1768
|
else if (closedBracketAt(state$1, pos)) {
|
|
1724
1769
|
let isTriple = allowTriple && state$1.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1725
|
-
|
|
1726
|
-
|
|
1770
|
+
let content = isTriple ? token + token + token : token;
|
|
1771
|
+
return { changes: { from: pos, to: pos + content.length, insert: content },
|
|
1772
|
+
range: state.EditorSelection.cursor(pos + content.length) };
|
|
1727
1773
|
}
|
|
1728
1774
|
}
|
|
1729
1775
|
else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _codemirror_state from '@codemirror/state';
|
|
2
2
|
import { EditorState, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
|
|
3
|
-
import { EditorView, KeyBinding, Command } from '@codemirror/view';
|
|
3
|
+
import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
|
|
4
4
|
import * as _lezer_common from '@lezer/common';
|
|
5
5
|
|
|
6
6
|
interface CompletionConfig {
|
|
@@ -79,6 +79,19 @@ interface CompletionConfig {
|
|
|
79
79
|
position: number;
|
|
80
80
|
}[];
|
|
81
81
|
/**
|
|
82
|
+
By default, [info](https://codemirror.net/6/docs/ref/#autocomplet.Completion.info) tooltips are
|
|
83
|
+
placed to the side of the selected. This option can be used to
|
|
84
|
+
override that. It will be given rectangles for the list of
|
|
85
|
+
completions, the selected option, the info element, and the
|
|
86
|
+
availble [tooltip space](https://codemirror.net/6/docs/ref/#view.tooltips^config.tooltipSpace),
|
|
87
|
+
and should return style and/or class strings for the info
|
|
88
|
+
element.
|
|
89
|
+
*/
|
|
90
|
+
positionInfo?: (view: EditorView, list: Rect, option: Rect, info: Rect, space: Rect) => {
|
|
91
|
+
style?: string;
|
|
92
|
+
class?: string;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
82
95
|
The comparison function to use when sorting completions with the same
|
|
83
96
|
match score. Defaults to using
|
|
84
97
|
[`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
|
|
@@ -143,6 +156,39 @@ interface Completion {
|
|
|
143
156
|
down the list, a positive number moves it up.
|
|
144
157
|
*/
|
|
145
158
|
boost?: number;
|
|
159
|
+
/**
|
|
160
|
+
Can be used to divide the completion list into sections.
|
|
161
|
+
Completions in a given section (matched by name) will be grouped
|
|
162
|
+
together, with a heading above them. Options without section
|
|
163
|
+
will appear above all sections. A string value is equivalent to
|
|
164
|
+
a `{name}` object.
|
|
165
|
+
*/
|
|
166
|
+
section?: string | CompletionSection;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
Object used to describe a completion
|
|
170
|
+
[section](https://codemirror.net/6/docs/ref/#autocomplete.Completion.section). It is recommended to
|
|
171
|
+
create a shared object used by all the completions in a given
|
|
172
|
+
section.
|
|
173
|
+
*/
|
|
174
|
+
interface CompletionSection {
|
|
175
|
+
/**
|
|
176
|
+
The name of the section. If no `render` method is present, this
|
|
177
|
+
will be displayed above the options.
|
|
178
|
+
*/
|
|
179
|
+
name: string;
|
|
180
|
+
/**
|
|
181
|
+
An optional function that renders the section header. Since the
|
|
182
|
+
headers are shown inside a list, you should make sure the
|
|
183
|
+
resulting element has a `display: list-item` style.
|
|
184
|
+
*/
|
|
185
|
+
header?: (section: CompletionSection) => HTMLElement;
|
|
186
|
+
/**
|
|
187
|
+
By default, sections are ordered alphabetically by name. To
|
|
188
|
+
specify an explicit order, `rank` can be used. Sections with a
|
|
189
|
+
lower rank will be shown above sections with a higher rank.
|
|
190
|
+
*/
|
|
191
|
+
rank?: number;
|
|
146
192
|
}
|
|
147
193
|
/**
|
|
148
194
|
An instance of this is passed to completion source functions.
|
|
@@ -488,4 +534,4 @@ the currently selected completion.
|
|
|
488
534
|
*/
|
|
489
535
|
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
|
|
490
536
|
|
|
491
|
-
export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
|
537
|
+
export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSection, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Annotation, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateEffect, StateField, Prec, Text, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
|
|
2
|
-
import {
|
|
2
|
+
import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
|
|
3
3
|
import { syntaxTree, indentUnit } from '@codemirror/language';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -133,13 +133,14 @@ function ifNotIn(nodes, source) {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
class Option {
|
|
136
|
-
constructor(completion, source, match) {
|
|
136
|
+
constructor(completion, source, match, score) {
|
|
137
137
|
this.completion = completion;
|
|
138
138
|
this.source = source;
|
|
139
139
|
this.match = match;
|
|
140
|
+
this.score = score;
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
|
-
function cur(state) { return state.selection.main.
|
|
143
|
+
function cur(state) { return state.selection.main.from; }
|
|
143
144
|
// Make sure the given regexp has a $ at its end and, if `start` is
|
|
144
145
|
// true, a ^ at its start.
|
|
145
146
|
function ensureAnchor(expr, start) {
|
|
@@ -161,18 +162,13 @@ completion's text in the main selection range, and any other
|
|
|
161
162
|
selection range that has the same text in front of it.
|
|
162
163
|
*/
|
|
163
164
|
function insertCompletionText(state, text, from, to) {
|
|
165
|
+
let { main } = state.selection, len = to - from;
|
|
164
166
|
return Object.assign(Object.assign({}, state.changeByRange(range => {
|
|
165
|
-
if (range
|
|
166
|
-
|
|
167
|
-
changes: { from: from, to: to, insert: text },
|
|
168
|
-
range: EditorSelection.cursor(from + text.length)
|
|
169
|
-
};
|
|
170
|
-
let len = to - from;
|
|
171
|
-
if (!range.empty ||
|
|
172
|
-
len && state.sliceDoc(range.from - len, range.from) != state.sliceDoc(from, to))
|
|
167
|
+
if (range != main && len &&
|
|
168
|
+
state.sliceDoc(range.from - len, range.from + to - main.from) != state.sliceDoc(from, to))
|
|
173
169
|
return { range };
|
|
174
170
|
return {
|
|
175
|
-
changes: { from: range.from - len, to: range.from, insert: text },
|
|
171
|
+
changes: { from: range.from - len, to: to == main.from ? range.to : range.from + to - main.from, insert: text },
|
|
176
172
|
range: EditorSelection.cursor(range.from - len + text.length)
|
|
177
173
|
};
|
|
178
174
|
})), { userEvent: "input.complete" });
|
|
@@ -339,6 +335,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
|
|
|
339
335
|
aboveCursor: false,
|
|
340
336
|
icons: true,
|
|
341
337
|
addToOptions: [],
|
|
338
|
+
positionInfo: defaultPositionInfo,
|
|
342
339
|
compareCompletions: (a, b) => a.label.localeCompare(b.label),
|
|
343
340
|
interactionDelay: 75
|
|
344
341
|
}, {
|
|
@@ -354,6 +351,36 @@ const completionConfig = /*@__PURE__*/Facet.define({
|
|
|
354
351
|
function joinClass(a, b) {
|
|
355
352
|
return a ? b ? a + " " + b : a : b;
|
|
356
353
|
}
|
|
354
|
+
function defaultPositionInfo(view, list, option, info, space) {
|
|
355
|
+
let rtl = view.textDirection == Direction.RTL, left = rtl, narrow = false;
|
|
356
|
+
let side = "top", offset, maxWidth;
|
|
357
|
+
let spaceLeft = list.left - space.left, spaceRight = space.right - list.right;
|
|
358
|
+
let infoWidth = info.right - info.left, infoHeight = info.bottom - info.top;
|
|
359
|
+
if (left && spaceLeft < Math.min(infoWidth, spaceRight))
|
|
360
|
+
left = false;
|
|
361
|
+
else if (!left && spaceRight < Math.min(infoWidth, spaceLeft))
|
|
362
|
+
left = true;
|
|
363
|
+
if (infoWidth <= (left ? spaceLeft : spaceRight)) {
|
|
364
|
+
offset = Math.max(space.top, Math.min(option.top, space.bottom - infoHeight)) - list.top;
|
|
365
|
+
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
narrow = true;
|
|
369
|
+
maxWidth = Math.min(400 /* Info.Width */, (rtl ? list.right : space.right - list.left) - 30 /* Info.Margin */);
|
|
370
|
+
let spaceBelow = space.bottom - list.bottom;
|
|
371
|
+
if (spaceBelow >= infoHeight || spaceBelow > list.top) { // Below the completion
|
|
372
|
+
offset = option.bottom - list.top;
|
|
373
|
+
}
|
|
374
|
+
else { // Above it
|
|
375
|
+
side = "bottom";
|
|
376
|
+
offset = list.bottom - option.top;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
style: `${side}: ${offset}px; max-width: ${maxWidth}px`,
|
|
381
|
+
class: "cm-completionInfo-" + (narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right")
|
|
382
|
+
};
|
|
383
|
+
}
|
|
357
384
|
|
|
358
385
|
function optionContent(config) {
|
|
359
386
|
let content = config.addToOptions.slice();
|
|
@@ -418,9 +445,9 @@ class CompletionTooltip {
|
|
|
418
445
|
this.view = view;
|
|
419
446
|
this.stateField = stateField;
|
|
420
447
|
this.info = null;
|
|
421
|
-
this.
|
|
448
|
+
this.placeInfoReq = {
|
|
422
449
|
read: () => this.measureInfo(),
|
|
423
|
-
write: (pos) => this.
|
|
450
|
+
write: (pos) => this.placeInfo(pos),
|
|
424
451
|
key: this
|
|
425
452
|
};
|
|
426
453
|
this.space = null;
|
|
@@ -447,7 +474,7 @@ class CompletionTooltip {
|
|
|
447
474
|
this.list = this.dom.appendChild(this.createListBox(options, cState.id, this.range));
|
|
448
475
|
this.list.addEventListener("scroll", () => {
|
|
449
476
|
if (this.info)
|
|
450
|
-
this.view.requestMeasure(this.
|
|
477
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
451
478
|
});
|
|
452
479
|
}
|
|
453
480
|
mount() { this.updateSel(); }
|
|
@@ -477,7 +504,7 @@ class CompletionTooltip {
|
|
|
477
504
|
positioned(space) {
|
|
478
505
|
this.space = space;
|
|
479
506
|
if (this.info)
|
|
480
|
-
this.view.requestMeasure(this.
|
|
507
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
481
508
|
}
|
|
482
509
|
updateSel() {
|
|
483
510
|
let cState = this.view.state.field(this.stateField), open = cState.open;
|
|
@@ -487,7 +514,7 @@ class CompletionTooltip {
|
|
|
487
514
|
this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range));
|
|
488
515
|
this.list.addEventListener("scroll", () => {
|
|
489
516
|
if (this.info)
|
|
490
|
-
this.view.requestMeasure(this.
|
|
517
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
491
518
|
});
|
|
492
519
|
}
|
|
493
520
|
if (this.updateSelectedOption(open.selected)) {
|
|
@@ -518,12 +545,15 @@ class CompletionTooltip {
|
|
|
518
545
|
dom.className = "cm-tooltip cm-completionInfo";
|
|
519
546
|
dom.appendChild(content);
|
|
520
547
|
this.dom.appendChild(dom);
|
|
521
|
-
this.view.requestMeasure(this.
|
|
548
|
+
this.view.requestMeasure(this.placeInfoReq);
|
|
522
549
|
}
|
|
523
550
|
updateSelectedOption(selected) {
|
|
524
551
|
let set = null;
|
|
525
552
|
for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
|
|
526
|
-
if (
|
|
553
|
+
if (opt.nodeName != "LI" || !opt.id) {
|
|
554
|
+
i--; // A section header
|
|
555
|
+
}
|
|
556
|
+
else if (i == selected) {
|
|
527
557
|
if (!opt.hasAttribute("aria-selected")) {
|
|
528
558
|
opt.setAttribute("aria-selected", "true");
|
|
529
559
|
set = opt;
|
|
@@ -553,41 +583,17 @@ class CompletionTooltip {
|
|
|
553
583
|
if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
|
|
554
584
|
selRect.bottom < Math.max(space.top, listRect.top) + 10)
|
|
555
585
|
return null;
|
|
556
|
-
|
|
557
|
-
let top = "", bottom = "";
|
|
558
|
-
let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
|
|
559
|
-
if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
|
|
560
|
-
left = false;
|
|
561
|
-
else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
|
|
562
|
-
left = true;
|
|
563
|
-
if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
|
|
564
|
-
top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
|
|
565
|
-
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
narrow = true;
|
|
569
|
-
maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
|
|
570
|
-
let spaceBelow = space.bottom - listRect.bottom;
|
|
571
|
-
if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
|
|
572
|
-
top = (selRect.bottom - listRect.top) + "px";
|
|
573
|
-
else // Above it
|
|
574
|
-
bottom = (listRect.bottom - selRect.top) + "px";
|
|
575
|
-
}
|
|
576
|
-
return {
|
|
577
|
-
top, bottom, maxWidth,
|
|
578
|
-
class: narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right",
|
|
579
|
-
};
|
|
586
|
+
return this.view.state.facet(completionConfig).positionInfo(this.view, listRect, selRect, infoRect, space);
|
|
580
587
|
}
|
|
581
|
-
|
|
588
|
+
placeInfo(pos) {
|
|
582
589
|
if (this.info) {
|
|
583
590
|
if (pos) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
this.info.
|
|
587
|
-
this.info.className = "cm-tooltip cm-completionInfo cm-completionInfo-" + pos.class;
|
|
591
|
+
if (pos.style)
|
|
592
|
+
this.info.style.cssText = pos.style;
|
|
593
|
+
this.info.className = "cm-tooltip cm-completionInfo " + (pos.class || "");
|
|
588
594
|
}
|
|
589
595
|
else {
|
|
590
|
-
this.info.style.
|
|
596
|
+
this.info.style.cssText = "top: -1e6px";
|
|
591
597
|
}
|
|
592
598
|
}
|
|
593
599
|
}
|
|
@@ -597,8 +603,22 @@ class CompletionTooltip {
|
|
|
597
603
|
ul.setAttribute("role", "listbox");
|
|
598
604
|
ul.setAttribute("aria-expanded", "true");
|
|
599
605
|
ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
|
|
606
|
+
let curSection = null;
|
|
600
607
|
for (let i = range.from; i < range.to; i++) {
|
|
601
|
-
let { completion, match } = options[i];
|
|
608
|
+
let { completion, match } = options[i], { section } = completion;
|
|
609
|
+
if (section) {
|
|
610
|
+
let name = typeof section == "string" ? section : section.name;
|
|
611
|
+
if (name != curSection && (i > range.from || range.from == 0)) {
|
|
612
|
+
curSection = name;
|
|
613
|
+
if (typeof section != "string" && section.header) {
|
|
614
|
+
ul.appendChild(section.header(section));
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
let header = ul.appendChild(document.createElement("completion-section"));
|
|
618
|
+
header.textContent = name;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
602
622
|
const li = ul.appendChild(document.createElement("li"));
|
|
603
623
|
li.id = id + "-" + i;
|
|
604
624
|
li.setAttribute("role", "option");
|
|
@@ -639,32 +659,55 @@ function score(option) {
|
|
|
639
659
|
(option.type ? 1 : 0);
|
|
640
660
|
}
|
|
641
661
|
function sortOptions(active, state) {
|
|
642
|
-
let options = []
|
|
662
|
+
let options = [];
|
|
663
|
+
let sections = null;
|
|
664
|
+
let addOption = (option) => {
|
|
665
|
+
options.push(option);
|
|
666
|
+
let { section } = option.completion;
|
|
667
|
+
if (section) {
|
|
668
|
+
if (!sections)
|
|
669
|
+
sections = [];
|
|
670
|
+
let name = typeof section == "string" ? section : section.name;
|
|
671
|
+
if (!sections.some(s => s.name == name))
|
|
672
|
+
sections.push(typeof section == "string" ? { name } : section);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
643
675
|
for (let a of active)
|
|
644
676
|
if (a.hasResult()) {
|
|
645
677
|
if (a.result.filter === false) {
|
|
646
678
|
let getMatch = a.result.getMatch;
|
|
647
679
|
for (let option of a.result.options) {
|
|
648
|
-
let match = [1e9 -
|
|
680
|
+
let match = [1e9 - options.length];
|
|
649
681
|
if (getMatch)
|
|
650
682
|
for (let n of getMatch(option))
|
|
651
683
|
match.push(n);
|
|
652
|
-
|
|
684
|
+
addOption(new Option(option, a, match, match[0]));
|
|
653
685
|
}
|
|
654
686
|
}
|
|
655
687
|
else {
|
|
656
688
|
let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
|
|
657
689
|
for (let option of a.result.options)
|
|
658
690
|
if (match = matcher.match(option.label)) {
|
|
659
|
-
|
|
660
|
-
match[0] += option.boost;
|
|
661
|
-
options.push(new Option(option, a, match));
|
|
691
|
+
addOption(new Option(option, a, match, match[0] + (option.boost || 0)));
|
|
662
692
|
}
|
|
663
693
|
}
|
|
664
694
|
}
|
|
695
|
+
if (sections) {
|
|
696
|
+
let sectionOrder = Object.create(null), pos = 0;
|
|
697
|
+
let cmp = (a, b) => { var _a, _b; return ((_a = a.rank) !== null && _a !== void 0 ? _a : 1e9) - ((_b = b.rank) !== null && _b !== void 0 ? _b : 1e9) || (a.name < b.name ? -1 : 1); };
|
|
698
|
+
for (let s of sections.sort(cmp)) {
|
|
699
|
+
pos -= 1e5;
|
|
700
|
+
sectionOrder[s.name] = pos;
|
|
701
|
+
}
|
|
702
|
+
for (let option of options) {
|
|
703
|
+
let { section } = option.completion;
|
|
704
|
+
if (section)
|
|
705
|
+
option.score += sectionOrder[typeof section == "string" ? section : section.name];
|
|
706
|
+
}
|
|
707
|
+
}
|
|
665
708
|
let result = [], prev = null;
|
|
666
709
|
let compare = state.facet(completionConfig).compareCompletions;
|
|
667
|
-
for (let opt of options.sort((a, b) => (b.
|
|
710
|
+
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
|
|
668
711
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
669
712
|
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
670
713
|
prev.apply != opt.completion.apply)
|
|
@@ -1099,13 +1142,21 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
|
1099
1142
|
listStyle: "none",
|
|
1100
1143
|
margin: 0,
|
|
1101
1144
|
padding: 0,
|
|
1145
|
+
"& > li, & > completion-section": {
|
|
1146
|
+
padding: "1px 3px",
|
|
1147
|
+
lineHeight: 1.2
|
|
1148
|
+
},
|
|
1102
1149
|
"& > li": {
|
|
1103
1150
|
overflowX: "hidden",
|
|
1104
1151
|
textOverflow: "ellipsis",
|
|
1105
|
-
cursor: "pointer"
|
|
1106
|
-
padding: "1px 3px",
|
|
1107
|
-
lineHeight: 1.2
|
|
1152
|
+
cursor: "pointer"
|
|
1108
1153
|
},
|
|
1154
|
+
"& > completion-section": {
|
|
1155
|
+
display: "list-item",
|
|
1156
|
+
borderBottom: "1px solid silver",
|
|
1157
|
+
paddingLeft: "0.5em",
|
|
1158
|
+
opacity: 0.7
|
|
1159
|
+
}
|
|
1109
1160
|
}
|
|
1110
1161
|
},
|
|
1111
1162
|
"&light .cm-tooltip-autocomplete ul li[aria-selected]": {
|
|
@@ -1539,9 +1590,6 @@ const closeBracketEffect = /*@__PURE__*/StateEffect.define({
|
|
|
1539
1590
|
return mapped == null ? undefined : mapped;
|
|
1540
1591
|
}
|
|
1541
1592
|
});
|
|
1542
|
-
const skipBracketEffect = /*@__PURE__*/StateEffect.define({
|
|
1543
|
-
map(value, mapping) { return mapping.mapPos(value); }
|
|
1544
|
-
});
|
|
1545
1593
|
const closedBracket = /*@__PURE__*/new class extends RangeValue {
|
|
1546
1594
|
};
|
|
1547
1595
|
closedBracket.startSide = 1;
|
|
@@ -1556,12 +1604,9 @@ const bracketState = /*@__PURE__*/StateField.define({
|
|
|
1556
1604
|
value = RangeSet.empty;
|
|
1557
1605
|
}
|
|
1558
1606
|
value = value.map(tr.changes);
|
|
1559
|
-
for (let effect of tr.effects)
|
|
1607
|
+
for (let effect of tr.effects)
|
|
1560
1608
|
if (effect.is(closeBracketEffect))
|
|
1561
1609
|
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1562
|
-
else if (effect.is(skipBracketEffect))
|
|
1563
|
-
value = value.update({ filter: from => from != effect.value });
|
|
1564
|
-
}
|
|
1565
1610
|
return value;
|
|
1566
1611
|
}
|
|
1567
1612
|
});
|
|
@@ -1689,15 +1734,15 @@ function handleOpen(state, open, close, closeBefore) {
|
|
|
1689
1734
|
});
|
|
1690
1735
|
}
|
|
1691
1736
|
function handleClose(state, _open, close) {
|
|
1692
|
-
let dont = null,
|
|
1737
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1693
1738
|
if (range.empty && nextChar(state.doc, range.head) == close)
|
|
1694
|
-
return
|
|
1695
|
-
|
|
1739
|
+
return { changes: { from: range.head, to: range.head + close.length, insert: close },
|
|
1740
|
+
range: EditorSelection.cursor(range.head + close.length) };
|
|
1741
|
+
return dont = { range };
|
|
1696
1742
|
});
|
|
1697
|
-
return dont ? null : state.update({
|
|
1698
|
-
selection: EditorSelection.create(moved, state.selection.mainIndex),
|
|
1743
|
+
return dont ? null : state.update(changes, {
|
|
1699
1744
|
scrollIntoView: true,
|
|
1700
|
-
|
|
1745
|
+
userEvent: "input.type"
|
|
1701
1746
|
});
|
|
1702
1747
|
}
|
|
1703
1748
|
// Handles cases where the open and close token are the same, and
|
|
@@ -1718,8 +1763,9 @@ function handleSame(state, token, allowTriple, config) {
|
|
|
1718
1763
|
}
|
|
1719
1764
|
else if (closedBracketAt(state, pos)) {
|
|
1720
1765
|
let isTriple = allowTriple && state.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1721
|
-
|
|
1722
|
-
|
|
1766
|
+
let content = isTriple ? token + token + token : token;
|
|
1767
|
+
return { changes: { from: pos, to: pos + content.length, insert: content },
|
|
1768
|
+
range: EditorSelection.cursor(pos + content.length) };
|
|
1723
1769
|
}
|
|
1724
1770
|
}
|
|
1725
1771
|
else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|