@codemirror/autocomplete 0.19.13 → 0.20.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 +34 -0
- package/dist/index.cjs +348 -70
- package/dist/index.d.ts +85 -16
- package/dist/index.js +326 -54
- package/package.json +5 -7
package/dist/index.cjs
CHANGED
|
@@ -4,9 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var state = require('@codemirror/state');
|
|
6
6
|
var view = require('@codemirror/view');
|
|
7
|
-
var tooltip = require('@codemirror/tooltip');
|
|
8
7
|
var language = require('@codemirror/language');
|
|
9
|
-
var text = require('@codemirror/text');
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
An instance of this is passed to completion source functions.
|
|
@@ -102,10 +100,10 @@ completes them.
|
|
|
102
100
|
*/
|
|
103
101
|
function completeFromList(list) {
|
|
104
102
|
let options = list.map(o => typeof o == "string" ? { label: o } : o);
|
|
105
|
-
let [
|
|
103
|
+
let [validFor, match] = options.every(o => /^\w+$/.test(o.label)) ? [/\w*$/, /\w+$/] : prefixMatch(options);
|
|
106
104
|
return (context) => {
|
|
107
105
|
let token = context.matchBefore(match);
|
|
108
|
-
return token || context.explicit ? { from: token ? token.from : context.pos, options,
|
|
106
|
+
return token || context.explicit ? { from: token ? token.from : context.pos, options, validFor } : null;
|
|
109
107
|
};
|
|
110
108
|
}
|
|
111
109
|
/**
|
|
@@ -156,12 +154,24 @@ picking a completion.
|
|
|
156
154
|
*/
|
|
157
155
|
const pickedCompletion = state.Annotation.define();
|
|
158
156
|
function applyCompletion(view, option) {
|
|
159
|
-
|
|
157
|
+
const apply = option.completion.apply || option.completion.label;
|
|
160
158
|
let result = option.source;
|
|
161
159
|
if (typeof apply == "string") {
|
|
162
|
-
view.dispatch({
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
view.dispatch(view.state.changeByRange(range => {
|
|
161
|
+
if (range == view.state.selection.main)
|
|
162
|
+
return {
|
|
163
|
+
changes: { from: result.from, to: result.to, insert: apply },
|
|
164
|
+
range: state.EditorSelection.cursor(result.from + apply.length)
|
|
165
|
+
};
|
|
166
|
+
let len = result.to - result.from;
|
|
167
|
+
if (!range.empty ||
|
|
168
|
+
len && view.state.sliceDoc(range.from - len, range.from) != view.state.sliceDoc(result.from, result.to))
|
|
169
|
+
return { range };
|
|
170
|
+
return {
|
|
171
|
+
changes: { from: range.from - len, to: range.from, insert: apply },
|
|
172
|
+
range: state.EditorSelection.cursor(range.from - len + apply.length)
|
|
173
|
+
};
|
|
174
|
+
}), {
|
|
165
175
|
userEvent: "input.complete",
|
|
166
176
|
annotations: pickedCompletion.of(option.completion)
|
|
167
177
|
});
|
|
@@ -194,10 +204,10 @@ class FuzzyMatcher {
|
|
|
194
204
|
this.precise = [];
|
|
195
205
|
this.byWord = [];
|
|
196
206
|
for (let p = 0; p < pattern.length;) {
|
|
197
|
-
let char =
|
|
207
|
+
let char = state.codePointAt(pattern, p), size = state.codePointSize(char);
|
|
198
208
|
this.chars.push(char);
|
|
199
209
|
let part = pattern.slice(p, p + size), upper = part.toUpperCase();
|
|
200
|
-
this.folded.push(
|
|
210
|
+
this.folded.push(state.codePointAt(upper == part ? part.toLowerCase() : upper, 0));
|
|
201
211
|
p += size;
|
|
202
212
|
}
|
|
203
213
|
this.astral = pattern.length != this.chars.length;
|
|
@@ -218,9 +228,9 @@ class FuzzyMatcher {
|
|
|
218
228
|
// For single-character queries, only match when they occur right
|
|
219
229
|
// at the start
|
|
220
230
|
if (chars.length == 1) {
|
|
221
|
-
let first =
|
|
222
|
-
return first == chars[0] ? [0, 0,
|
|
223
|
-
: first == folded[0] ? [-200 /* CaseFold */, 0,
|
|
231
|
+
let first = state.codePointAt(word, 0);
|
|
232
|
+
return first == chars[0] ? [0, 0, state.codePointSize(first)]
|
|
233
|
+
: first == folded[0] ? [-200 /* CaseFold */, 0, state.codePointSize(first)] : null;
|
|
224
234
|
}
|
|
225
235
|
let direct = word.indexOf(this.pattern);
|
|
226
236
|
if (direct == 0)
|
|
@@ -228,10 +238,10 @@ class FuzzyMatcher {
|
|
|
228
238
|
let len = chars.length, anyTo = 0;
|
|
229
239
|
if (direct < 0) {
|
|
230
240
|
for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
|
|
231
|
-
let next =
|
|
241
|
+
let next = state.codePointAt(word, i);
|
|
232
242
|
if (next == chars[anyTo] || next == folded[anyTo])
|
|
233
243
|
any[anyTo++] = i;
|
|
234
|
-
i +=
|
|
244
|
+
i += state.codePointSize(next);
|
|
235
245
|
}
|
|
236
246
|
// No match, exit immediately
|
|
237
247
|
if (anyTo < len)
|
|
@@ -249,7 +259,7 @@ class FuzzyMatcher {
|
|
|
249
259
|
let hasLower = /[a-z]/.test(word), wordAdjacent = true;
|
|
250
260
|
// Go over the option's text, scanning for the various kinds of matches
|
|
251
261
|
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* NonWord */; i < e && byWordTo < len;) {
|
|
252
|
-
let next =
|
|
262
|
+
let next = state.codePointAt(word, i);
|
|
253
263
|
if (direct < 0) {
|
|
254
264
|
if (preciseTo < len && next == chars[preciseTo])
|
|
255
265
|
precise[preciseTo++] = i;
|
|
@@ -267,7 +277,7 @@ class FuzzyMatcher {
|
|
|
267
277
|
}
|
|
268
278
|
let ch, type = next < 0xff
|
|
269
279
|
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Lower */ : next >= 65 && next <= 90 ? 1 /* Upper */ : 0 /* NonWord */)
|
|
270
|
-
: ((ch =
|
|
280
|
+
: ((ch = state.fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Upper */ : ch != ch.toUpperCase() ? 2 /* Lower */ : 0 /* NonWord */);
|
|
271
281
|
if (!i || type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) {
|
|
272
282
|
if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
|
|
273
283
|
byWord[byWordTo++] = i;
|
|
@@ -275,7 +285,7 @@ class FuzzyMatcher {
|
|
|
275
285
|
wordAdjacent = false;
|
|
276
286
|
}
|
|
277
287
|
prevType = type;
|
|
278
|
-
i +=
|
|
288
|
+
i += state.codePointSize(next);
|
|
279
289
|
}
|
|
280
290
|
if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
|
|
281
291
|
return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
|
|
@@ -293,7 +303,7 @@ class FuzzyMatcher {
|
|
|
293
303
|
result(score, positions, word) {
|
|
294
304
|
let result = [score - word.length], i = 1;
|
|
295
305
|
for (let pos of positions) {
|
|
296
|
-
let to = pos + (this.astral ?
|
|
306
|
+
let to = pos + (this.astral ? state.codePointSize(state.codePointAt(word, pos)) : 1);
|
|
297
307
|
if (i > 1 && result[i - 1] == pos)
|
|
298
308
|
result[i - 1] = to;
|
|
299
309
|
else {
|
|
@@ -374,22 +384,6 @@ function optionContent(config) {
|
|
|
374
384
|
});
|
|
375
385
|
return content.sort((a, b) => a.position - b.position).map(a => a.render);
|
|
376
386
|
}
|
|
377
|
-
function createInfoDialog(option, view$1) {
|
|
378
|
-
let dom = document.createElement("div");
|
|
379
|
-
dom.className = "cm-tooltip cm-completionInfo";
|
|
380
|
-
let { info } = option.completion;
|
|
381
|
-
if (typeof info == "string") {
|
|
382
|
-
dom.textContent = info;
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
let content = info(option.completion);
|
|
386
|
-
if (content.then)
|
|
387
|
-
content.then(node => dom.appendChild(node), e => view.logException(view$1.state, e, "completion info"));
|
|
388
|
-
else
|
|
389
|
-
dom.appendChild(content);
|
|
390
|
-
}
|
|
391
|
-
return dom;
|
|
392
|
-
}
|
|
393
387
|
function rangeAroundSelected(total, selected, max) {
|
|
394
388
|
if (total <= max)
|
|
395
389
|
return { from: 0, to: total };
|
|
@@ -458,13 +452,31 @@ class CompletionTooltip {
|
|
|
458
452
|
this.info.remove();
|
|
459
453
|
this.info = null;
|
|
460
454
|
}
|
|
461
|
-
let
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
455
|
+
let { completion } = open.options[open.selected];
|
|
456
|
+
let { info } = completion;
|
|
457
|
+
if (!info)
|
|
458
|
+
return;
|
|
459
|
+
let infoResult = typeof info === 'string' ? document.createTextNode(info) : info(completion);
|
|
460
|
+
if (!infoResult)
|
|
461
|
+
return;
|
|
462
|
+
if ('then' in infoResult) {
|
|
463
|
+
infoResult.then(node => {
|
|
464
|
+
if (node && this.view.state.field(this.stateField, false) == cState)
|
|
465
|
+
this.addInfoPane(node);
|
|
466
|
+
}).catch(e => view.logException(this.view.state, e, "completion info"));
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
this.addInfoPane(infoResult);
|
|
465
470
|
}
|
|
466
471
|
}
|
|
467
472
|
}
|
|
473
|
+
addInfoPane(content) {
|
|
474
|
+
let dom = this.info = document.createElement("div");
|
|
475
|
+
dom.className = "cm-tooltip cm-completionInfo";
|
|
476
|
+
dom.appendChild(content);
|
|
477
|
+
this.dom.appendChild(dom);
|
|
478
|
+
this.view.requestMeasure(this.placeInfo);
|
|
479
|
+
}
|
|
468
480
|
updateSelectedOption(selected) {
|
|
469
481
|
let set = null;
|
|
470
482
|
for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
|
|
@@ -514,6 +526,7 @@ class CompletionTooltip {
|
|
|
514
526
|
const ul = document.createElement("ul");
|
|
515
527
|
ul.id = id;
|
|
516
528
|
ul.setAttribute("role", "listbox");
|
|
529
|
+
ul.setAttribute("aria-expanded", "true");
|
|
517
530
|
for (let i = range.from; i < range.to; i++) {
|
|
518
531
|
let { completion, match } = options[i];
|
|
519
532
|
const li = ul.appendChild(document.createElement("li"));
|
|
@@ -549,7 +562,6 @@ function scrollIntoView(container, element) {
|
|
|
549
562
|
container.scrollTop += self.bottom - parent.bottom;
|
|
550
563
|
}
|
|
551
564
|
|
|
552
|
-
const MaxOptions = 300;
|
|
553
565
|
// Used to pick a preferred option when two options with the same
|
|
554
566
|
// label occur in the result.
|
|
555
567
|
function score(option) {
|
|
@@ -574,13 +586,11 @@ function sortOptions(active, state) {
|
|
|
574
586
|
}
|
|
575
587
|
}
|
|
576
588
|
}
|
|
577
|
-
options.sort(cmpOption);
|
|
578
589
|
let result = [], prev = null;
|
|
579
590
|
for (let opt of options.sort(cmpOption)) {
|
|
580
|
-
if (result.length == MaxOptions)
|
|
581
|
-
break;
|
|
582
591
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
583
|
-
prev.type != opt.completion.type
|
|
592
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
593
|
+
prev.apply != opt.completion.apply)
|
|
584
594
|
result.push(opt);
|
|
585
595
|
else if (score(opt.completion) > score(prev))
|
|
586
596
|
result[result.length - 1] = opt;
|
|
@@ -607,10 +617,11 @@ class CompletionDialog {
|
|
|
607
617
|
let selected = 0;
|
|
608
618
|
if (prev && prev.selected) {
|
|
609
619
|
let selectedValue = prev.options[prev.selected].completion;
|
|
610
|
-
for (let i = 0; i < options.length
|
|
611
|
-
if (options[i].completion == selectedValue)
|
|
620
|
+
for (let i = 0; i < options.length; i++)
|
|
621
|
+
if (options[i].completion == selectedValue) {
|
|
612
622
|
selected = i;
|
|
613
|
-
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
614
625
|
}
|
|
615
626
|
return new CompletionDialog(options, makeAttrs(id, selected), {
|
|
616
627
|
pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
|
|
@@ -671,13 +682,12 @@ function sameResults(a, b) {
|
|
|
671
682
|
}
|
|
672
683
|
}
|
|
673
684
|
const baseAttrs = {
|
|
674
|
-
"aria-autocomplete": "list"
|
|
675
|
-
"aria-expanded": "false"
|
|
685
|
+
"aria-autocomplete": "list"
|
|
676
686
|
};
|
|
677
687
|
function makeAttrs(id, selected) {
|
|
678
688
|
return {
|
|
679
689
|
"aria-autocomplete": "list",
|
|
680
|
-
"aria-
|
|
690
|
+
"aria-haspopup": "listbox",
|
|
681
691
|
"aria-activedescendant": id + "-" + selected,
|
|
682
692
|
"aria-controls": id
|
|
683
693
|
};
|
|
@@ -730,24 +740,27 @@ class ActiveSource {
|
|
|
730
740
|
}
|
|
731
741
|
}
|
|
732
742
|
class ActiveResult extends ActiveSource {
|
|
733
|
-
constructor(source, explicitPos, result, from, to
|
|
743
|
+
constructor(source, explicitPos, result, from, to) {
|
|
734
744
|
super(source, 2 /* Result */, explicitPos);
|
|
735
745
|
this.result = result;
|
|
736
746
|
this.from = from;
|
|
737
747
|
this.to = to;
|
|
738
|
-
this.span = span;
|
|
739
748
|
}
|
|
740
749
|
hasResult() { return true; }
|
|
741
750
|
handleUserEvent(tr, type, conf) {
|
|
751
|
+
var _a;
|
|
742
752
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
743
753
|
let pos = cur(tr.state);
|
|
744
754
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
745
755
|
pos > to ||
|
|
746
756
|
type == "delete" && cur(tr.startState) == this.from)
|
|
747
757
|
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
|
|
748
|
-
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
|
|
749
|
-
if (
|
|
750
|
-
return new ActiveResult(this.source, explicitPos, this.result, from, to
|
|
758
|
+
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
|
|
759
|
+
if (checkValid(this.result.validFor, tr.state, from, to))
|
|
760
|
+
return new ActiveResult(this.source, explicitPos, this.result, from, to);
|
|
761
|
+
if (this.result.update &&
|
|
762
|
+
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
|
|
763
|
+
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
|
|
751
764
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
752
765
|
}
|
|
753
766
|
handleChange(tr) {
|
|
@@ -755,9 +768,15 @@ class ActiveResult extends ActiveSource {
|
|
|
755
768
|
}
|
|
756
769
|
map(mapping) {
|
|
757
770
|
return mapping.empty ? this :
|
|
758
|
-
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1)
|
|
771
|
+
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
|
|
759
772
|
}
|
|
760
773
|
}
|
|
774
|
+
function checkValid(validFor, state, from, to) {
|
|
775
|
+
if (!validFor)
|
|
776
|
+
return false;
|
|
777
|
+
let text = state.sliceDoc(from, to);
|
|
778
|
+
return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
|
|
779
|
+
}
|
|
761
780
|
const startCompletionEffect = state.StateEffect.define();
|
|
762
781
|
const closeCompletionEffect = state.StateEffect.define();
|
|
763
782
|
const setActiveEffect = state.StateEffect.define({
|
|
@@ -768,7 +787,7 @@ const completionState = state.StateField.define({
|
|
|
768
787
|
create() { return CompletionState.start(); },
|
|
769
788
|
update(value, tr) { return value.update(tr); },
|
|
770
789
|
provide: f => [
|
|
771
|
-
|
|
790
|
+
view.showTooltip.from(f, val => val.tooltip),
|
|
772
791
|
view.EditorView.contentAttributes.from(f, state => state.attrs)
|
|
773
792
|
]
|
|
774
793
|
});
|
|
@@ -779,20 +798,20 @@ Returns a command that moves the completion selection forward or
|
|
|
779
798
|
backward by the given amount.
|
|
780
799
|
*/
|
|
781
800
|
function moveCompletionSelection(forward, by = "option") {
|
|
782
|
-
return (view) => {
|
|
783
|
-
let cState = view.state.field(completionState, false);
|
|
801
|
+
return (view$1) => {
|
|
802
|
+
let cState = view$1.state.field(completionState, false);
|
|
784
803
|
if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
|
|
785
804
|
return false;
|
|
786
|
-
let step = 1, tooltip
|
|
787
|
-
if (by == "page" && (tooltip
|
|
788
|
-
step = Math.max(2, Math.floor(tooltip
|
|
789
|
-
tooltip
|
|
805
|
+
let step = 1, tooltip;
|
|
806
|
+
if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
|
|
807
|
+
step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
|
|
808
|
+
tooltip.dom.querySelector("li").offsetHeight) - 1);
|
|
790
809
|
let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
|
|
791
810
|
if (selected < 0)
|
|
792
811
|
selected = by == "page" ? 0 : length - 1;
|
|
793
812
|
else if (selected >= length)
|
|
794
813
|
selected = by == "page" ? length - 1 : 0;
|
|
795
|
-
view.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
814
|
+
view$1.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
796
815
|
return true;
|
|
797
816
|
};
|
|
798
817
|
}
|
|
@@ -859,7 +878,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
859
878
|
for (let i = 0; i < this.running.length; i++) {
|
|
860
879
|
let query = this.running[i];
|
|
861
880
|
if (doesReset ||
|
|
862
|
-
query.updates.length + update.transactions.length > MaxUpdateCount &&
|
|
881
|
+
query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
|
|
863
882
|
for (let handler of query.context.abortListeners) {
|
|
864
883
|
try {
|
|
865
884
|
handler();
|
|
@@ -931,7 +950,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
931
950
|
continue;
|
|
932
951
|
this.running.splice(i--, 1);
|
|
933
952
|
if (query.done) {
|
|
934
|
-
let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state)
|
|
953
|
+
let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state));
|
|
935
954
|
// Replay the transactions that happened since the start of
|
|
936
955
|
// the request and see if that preserves the result
|
|
937
956
|
for (let tr of query.updates)
|
|
@@ -1204,8 +1223,9 @@ function fieldSelection(ranges, field) {
|
|
|
1204
1223
|
return state.EditorSelection.create(ranges.filter(r => r.field == field).map(r => state.EditorSelection.range(r.from, r.to)));
|
|
1205
1224
|
}
|
|
1206
1225
|
/**
|
|
1207
|
-
Convert a snippet template to a function that can
|
|
1208
|
-
Snippets are written
|
|
1226
|
+
Convert a snippet template to a function that can
|
|
1227
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
1228
|
+
using syntax like this:
|
|
1209
1229
|
|
|
1210
1230
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1211
1231
|
|
|
@@ -1388,9 +1408,239 @@ const completeAnyWord = context => {
|
|
|
1388
1408
|
return null;
|
|
1389
1409
|
let from = token ? token.from : context.pos;
|
|
1390
1410
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1391
|
-
return { from, options,
|
|
1411
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1392
1412
|
};
|
|
1393
1413
|
|
|
1414
|
+
const defaults = {
|
|
1415
|
+
brackets: ["(", "[", "{", "'", '"'],
|
|
1416
|
+
before: ")]}:;>"
|
|
1417
|
+
};
|
|
1418
|
+
const closeBracketEffect = state.StateEffect.define({
|
|
1419
|
+
map(value, mapping) {
|
|
1420
|
+
let mapped = mapping.mapPos(value, -1, state.MapMode.TrackAfter);
|
|
1421
|
+
return mapped == null ? undefined : mapped;
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
const skipBracketEffect = state.StateEffect.define({
|
|
1425
|
+
map(value, mapping) { return mapping.mapPos(value); }
|
|
1426
|
+
});
|
|
1427
|
+
const closedBracket = new class extends state.RangeValue {
|
|
1428
|
+
};
|
|
1429
|
+
closedBracket.startSide = 1;
|
|
1430
|
+
closedBracket.endSide = -1;
|
|
1431
|
+
const bracketState = state.StateField.define({
|
|
1432
|
+
create() { return state.RangeSet.empty; },
|
|
1433
|
+
update(value, tr) {
|
|
1434
|
+
if (tr.selection) {
|
|
1435
|
+
let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from;
|
|
1436
|
+
let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from;
|
|
1437
|
+
if (lineStart != tr.changes.mapPos(prevLineStart, -1))
|
|
1438
|
+
value = state.RangeSet.empty;
|
|
1439
|
+
}
|
|
1440
|
+
value = value.map(tr.changes);
|
|
1441
|
+
for (let effect of tr.effects) {
|
|
1442
|
+
if (effect.is(closeBracketEffect))
|
|
1443
|
+
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1444
|
+
else if (effect.is(skipBracketEffect))
|
|
1445
|
+
value = value.update({ filter: from => from != effect.value });
|
|
1446
|
+
}
|
|
1447
|
+
return value;
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
/**
|
|
1451
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
1452
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
1453
|
+
after the cursor. When closing a bracket directly in front of a
|
|
1454
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
1455
|
+
that bracket.
|
|
1456
|
+
*/
|
|
1457
|
+
function closeBrackets() {
|
|
1458
|
+
return [inputHandler, bracketState];
|
|
1459
|
+
}
|
|
1460
|
+
const definedClosing = "()[]{}<>";
|
|
1461
|
+
function closing(ch) {
|
|
1462
|
+
for (let i = 0; i < definedClosing.length; i += 2)
|
|
1463
|
+
if (definedClosing.charCodeAt(i) == ch)
|
|
1464
|
+
return definedClosing.charAt(i + 1);
|
|
1465
|
+
return state.fromCodePoint(ch < 128 ? ch : ch + 1);
|
|
1466
|
+
}
|
|
1467
|
+
function config(state, pos) {
|
|
1468
|
+
return state.languageDataAt("closeBrackets", pos)[0] || defaults;
|
|
1469
|
+
}
|
|
1470
|
+
const android = typeof navigator == "object" && /Android\b/.test(navigator.userAgent);
|
|
1471
|
+
const inputHandler = view.EditorView.inputHandler.of((view, from, to, insert) => {
|
|
1472
|
+
if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
|
|
1473
|
+
return false;
|
|
1474
|
+
let sel = view.state.selection.main;
|
|
1475
|
+
if (insert.length > 2 || insert.length == 2 && state.codePointSize(state.codePointAt(insert, 0)) == 1 ||
|
|
1476
|
+
from != sel.from || to != sel.to)
|
|
1477
|
+
return false;
|
|
1478
|
+
let tr = insertBracket(view.state, insert);
|
|
1479
|
+
if (!tr)
|
|
1480
|
+
return false;
|
|
1481
|
+
view.dispatch(tr);
|
|
1482
|
+
return true;
|
|
1483
|
+
});
|
|
1484
|
+
/**
|
|
1485
|
+
Command that implements deleting a pair of matching brackets when
|
|
1486
|
+
the cursor is between them.
|
|
1487
|
+
*/
|
|
1488
|
+
const deleteBracketPair = ({ state: state$1, dispatch }) => {
|
|
1489
|
+
if (state$1.readOnly)
|
|
1490
|
+
return false;
|
|
1491
|
+
let conf = config(state$1, state$1.selection.main.head);
|
|
1492
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1493
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1494
|
+
if (range.empty) {
|
|
1495
|
+
let before = prevChar(state$1.doc, range.head);
|
|
1496
|
+
for (let token of tokens) {
|
|
1497
|
+
if (token == before && nextChar(state$1.doc, range.head) == closing(state.codePointAt(token, 0)))
|
|
1498
|
+
return { changes: { from: range.head - token.length, to: range.head + token.length },
|
|
1499
|
+
range: state.EditorSelection.cursor(range.head - token.length),
|
|
1500
|
+
userEvent: "delete.backward" };
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return { range: dont = range };
|
|
1504
|
+
});
|
|
1505
|
+
if (!dont)
|
|
1506
|
+
dispatch(state$1.update(changes, { scrollIntoView: true }));
|
|
1507
|
+
return !dont;
|
|
1508
|
+
};
|
|
1509
|
+
/**
|
|
1510
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
1511
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
1512
|
+
*/
|
|
1513
|
+
const closeBracketsKeymap = [
|
|
1514
|
+
{ key: "Backspace", run: deleteBracketPair }
|
|
1515
|
+
];
|
|
1516
|
+
/**
|
|
1517
|
+
Implements the extension's behavior on text insertion. If the
|
|
1518
|
+
given string counts as a bracket in the language around the
|
|
1519
|
+
selection, and replacing the selection with it requires custom
|
|
1520
|
+
behavior (inserting a closing version or skipping past a
|
|
1521
|
+
previously-closed bracket), this function returns a transaction
|
|
1522
|
+
representing that custom behavior. (You only need this if you want
|
|
1523
|
+
to programmatically insert brackets—the
|
|
1524
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
1525
|
+
take care of running this for user input.)
|
|
1526
|
+
*/
|
|
1527
|
+
function insertBracket(state$1, bracket) {
|
|
1528
|
+
let conf = config(state$1, state$1.selection.main.head);
|
|
1529
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1530
|
+
for (let tok of tokens) {
|
|
1531
|
+
let closed = closing(state.codePointAt(tok, 0));
|
|
1532
|
+
if (bracket == tok)
|
|
1533
|
+
return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
|
|
1534
|
+
: handleOpen(state$1, tok, closed, conf.before || defaults.before);
|
|
1535
|
+
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
|
|
1536
|
+
return handleClose(state$1, tok, closed);
|
|
1537
|
+
}
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
function closedBracketAt(state, pos) {
|
|
1541
|
+
let found = false;
|
|
1542
|
+
state.field(bracketState).between(0, state.doc.length, from => {
|
|
1543
|
+
if (from == pos)
|
|
1544
|
+
found = true;
|
|
1545
|
+
});
|
|
1546
|
+
return found;
|
|
1547
|
+
}
|
|
1548
|
+
function nextChar(doc, pos) {
|
|
1549
|
+
let next = doc.sliceString(pos, pos + 2);
|
|
1550
|
+
return next.slice(0, state.codePointSize(state.codePointAt(next, 0)));
|
|
1551
|
+
}
|
|
1552
|
+
function prevChar(doc, pos) {
|
|
1553
|
+
let prev = doc.sliceString(pos - 2, pos);
|
|
1554
|
+
return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
|
1555
|
+
}
|
|
1556
|
+
function handleOpen(state$1, open, close, closeBefore) {
|
|
1557
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1558
|
+
if (!range.empty)
|
|
1559
|
+
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
|
1560
|
+
effects: closeBracketEffect.of(range.to + open.length),
|
|
1561
|
+
range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
|
1562
|
+
let next = nextChar(state$1.doc, range.head);
|
|
1563
|
+
if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
|
1564
|
+
return { changes: { insert: open + close, from: range.head },
|
|
1565
|
+
effects: closeBracketEffect.of(range.head + open.length),
|
|
1566
|
+
range: state.EditorSelection.cursor(range.head + open.length) };
|
|
1567
|
+
return { range: dont = range };
|
|
1568
|
+
});
|
|
1569
|
+
return dont ? null : state$1.update(changes, {
|
|
1570
|
+
scrollIntoView: true,
|
|
1571
|
+
userEvent: "input.type"
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
function handleClose(state$1, _open, close) {
|
|
1575
|
+
let dont = null, moved = state$1.selection.ranges.map(range => {
|
|
1576
|
+
if (range.empty && nextChar(state$1.doc, range.head) == close)
|
|
1577
|
+
return state.EditorSelection.cursor(range.head + close.length);
|
|
1578
|
+
return dont = range;
|
|
1579
|
+
});
|
|
1580
|
+
return dont ? null : state$1.update({
|
|
1581
|
+
selection: state.EditorSelection.create(moved, state$1.selection.mainIndex),
|
|
1582
|
+
scrollIntoView: true,
|
|
1583
|
+
effects: state$1.selection.ranges.map(({ from }) => skipBracketEffect.of(from))
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
// Handles cases where the open and close token are the same, and
|
|
1587
|
+
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
|
1588
|
+
function handleSame(state$1, token, allowTriple) {
|
|
1589
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1590
|
+
if (!range.empty)
|
|
1591
|
+
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
|
1592
|
+
effects: closeBracketEffect.of(range.to + token.length),
|
|
1593
|
+
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
|
1594
|
+
let pos = range.head, next = nextChar(state$1.doc, pos);
|
|
1595
|
+
if (next == token) {
|
|
1596
|
+
if (nodeStart(state$1, pos)) {
|
|
1597
|
+
return { changes: { insert: token + token, from: pos },
|
|
1598
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1599
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1600
|
+
}
|
|
1601
|
+
else if (closedBracketAt(state$1, pos)) {
|
|
1602
|
+
let isTriple = allowTriple && state$1.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1603
|
+
return { range: state.EditorSelection.cursor(pos + token.length * (isTriple ? 3 : 1)),
|
|
1604
|
+
effects: skipBracketEffect.of(pos) };
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
|
1608
|
+
nodeStart(state$1, pos - 2 * token.length)) {
|
|
1609
|
+
return { changes: { insert: token + token + token + token, from: pos },
|
|
1610
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1611
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1612
|
+
}
|
|
1613
|
+
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
|
|
1614
|
+
let prev = state$1.sliceDoc(pos - 1, pos);
|
|
1615
|
+
if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
|
|
1616
|
+
return { changes: { insert: token + token, from: pos },
|
|
1617
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1618
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1619
|
+
}
|
|
1620
|
+
return { range: dont = range };
|
|
1621
|
+
});
|
|
1622
|
+
return dont ? null : state$1.update(changes, {
|
|
1623
|
+
scrollIntoView: true,
|
|
1624
|
+
userEvent: "input.type"
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
function nodeStart(state, pos) {
|
|
1628
|
+
let tree = language.syntaxTree(state).resolveInner(pos + 1);
|
|
1629
|
+
return tree.parent && tree.from == pos;
|
|
1630
|
+
}
|
|
1631
|
+
function probablyInString(state, pos, quoteToken) {
|
|
1632
|
+
let node = language.syntaxTree(state).resolveInner(pos, -1);
|
|
1633
|
+
for (let i = 0; i < 5; i++) {
|
|
1634
|
+
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
|
|
1635
|
+
return true;
|
|
1636
|
+
let parent = node.to == pos && node.parent;
|
|
1637
|
+
if (!parent)
|
|
1638
|
+
break;
|
|
1639
|
+
node = parent;
|
|
1640
|
+
}
|
|
1641
|
+
return false;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1394
1644
|
/**
|
|
1395
1645
|
Returns an extension that enables autocompletion.
|
|
1396
1646
|
*/
|
|
@@ -1435,13 +1685,19 @@ function completionStatus(state) {
|
|
|
1435
1685
|
return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending"
|
|
1436
1686
|
: cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null;
|
|
1437
1687
|
}
|
|
1688
|
+
const completionArrayCache = new WeakMap;
|
|
1438
1689
|
/**
|
|
1439
1690
|
Returns the available completions as an array.
|
|
1440
1691
|
*/
|
|
1441
1692
|
function currentCompletions(state) {
|
|
1442
1693
|
var _a;
|
|
1443
1694
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1444
|
-
|
|
1695
|
+
if (!open)
|
|
1696
|
+
return [];
|
|
1697
|
+
let completions = completionArrayCache.get(open.options);
|
|
1698
|
+
if (!completions)
|
|
1699
|
+
completionArrayCache.set(open.options, completions = open.options.map(o => o.completion));
|
|
1700
|
+
return completions;
|
|
1445
1701
|
}
|
|
1446
1702
|
/**
|
|
1447
1703
|
Return the currently selected completion, if any.
|
|
@@ -1451,24 +1707,46 @@ function selectedCompletion(state) {
|
|
|
1451
1707
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1452
1708
|
return open ? open.options[open.selected].completion : null;
|
|
1453
1709
|
}
|
|
1710
|
+
/**
|
|
1711
|
+
Returns the currently selected position in the active completion
|
|
1712
|
+
list, or null if no completions are active.
|
|
1713
|
+
*/
|
|
1714
|
+
function selectedCompletionIndex(state) {
|
|
1715
|
+
var _a;
|
|
1716
|
+
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1717
|
+
return open ? open.selected : null;
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
Create an effect that can be attached to a transaction to change
|
|
1721
|
+
the currently selected completion.
|
|
1722
|
+
*/
|
|
1723
|
+
function setSelectedCompletion(index) {
|
|
1724
|
+
return setSelectedEffect.of(index);
|
|
1725
|
+
}
|
|
1454
1726
|
|
|
1455
1727
|
exports.CompletionContext = CompletionContext;
|
|
1456
1728
|
exports.acceptCompletion = acceptCompletion;
|
|
1457
1729
|
exports.autocompletion = autocompletion;
|
|
1458
1730
|
exports.clearSnippet = clearSnippet;
|
|
1731
|
+
exports.closeBrackets = closeBrackets;
|
|
1732
|
+
exports.closeBracketsKeymap = closeBracketsKeymap;
|
|
1459
1733
|
exports.closeCompletion = closeCompletion;
|
|
1460
1734
|
exports.completeAnyWord = completeAnyWord;
|
|
1461
1735
|
exports.completeFromList = completeFromList;
|
|
1462
1736
|
exports.completionKeymap = completionKeymap;
|
|
1463
1737
|
exports.completionStatus = completionStatus;
|
|
1464
1738
|
exports.currentCompletions = currentCompletions;
|
|
1739
|
+
exports.deleteBracketPair = deleteBracketPair;
|
|
1465
1740
|
exports.ifIn = ifIn;
|
|
1466
1741
|
exports.ifNotIn = ifNotIn;
|
|
1742
|
+
exports.insertBracket = insertBracket;
|
|
1467
1743
|
exports.moveCompletionSelection = moveCompletionSelection;
|
|
1468
1744
|
exports.nextSnippetField = nextSnippetField;
|
|
1469
1745
|
exports.pickedCompletion = pickedCompletion;
|
|
1470
1746
|
exports.prevSnippetField = prevSnippetField;
|
|
1471
1747
|
exports.selectedCompletion = selectedCompletion;
|
|
1748
|
+
exports.selectedCompletionIndex = selectedCompletionIndex;
|
|
1749
|
+
exports.setSelectedCompletion = setSelectedCompletion;
|
|
1472
1750
|
exports.snippet = snippet;
|
|
1473
1751
|
exports.snippetCompletion = snippetCompletion;
|
|
1474
1752
|
exports.snippetKeymap = snippetKeymap;
|