@codemirror/autocomplete 0.19.14 → 0.20.1
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 +36 -0
- package/dist/index.cjs +355 -64
- package/dist/index.d.ts +98 -16
- package/dist/index.js +333 -48
- 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 {
|
|
@@ -310,6 +320,7 @@ const completionConfig = state.Facet.define({
|
|
|
310
320
|
return state.combineConfig(configs, {
|
|
311
321
|
activateOnTyping: true,
|
|
312
322
|
override: null,
|
|
323
|
+
closeOnBlur: true,
|
|
313
324
|
maxRenderedOptions: 100,
|
|
314
325
|
defaultKeymap: true,
|
|
315
326
|
optionClass: () => "",
|
|
@@ -318,6 +329,7 @@ const completionConfig = state.Facet.define({
|
|
|
318
329
|
addToOptions: []
|
|
319
330
|
}, {
|
|
320
331
|
defaultKeymap: (a, b) => a && b,
|
|
332
|
+
closeOnBlur: (a, b) => a && b,
|
|
321
333
|
icons: (a, b) => a && b,
|
|
322
334
|
optionClass: (a, b) => c => joinClass(a(c), b(c)),
|
|
323
335
|
addToOptions: (a, b) => a.concat(b)
|
|
@@ -374,22 +386,6 @@ function optionContent(config) {
|
|
|
374
386
|
});
|
|
375
387
|
return content.sort((a, b) => a.position - b.position).map(a => a.render);
|
|
376
388
|
}
|
|
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
389
|
function rangeAroundSelected(total, selected, max) {
|
|
394
390
|
if (total <= max)
|
|
395
391
|
return { from: 0, to: total };
|
|
@@ -458,13 +454,31 @@ class CompletionTooltip {
|
|
|
458
454
|
this.info.remove();
|
|
459
455
|
this.info = null;
|
|
460
456
|
}
|
|
461
|
-
let
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
457
|
+
let { completion } = open.options[open.selected];
|
|
458
|
+
let { info } = completion;
|
|
459
|
+
if (!info)
|
|
460
|
+
return;
|
|
461
|
+
let infoResult = typeof info === 'string' ? document.createTextNode(info) : info(completion);
|
|
462
|
+
if (!infoResult)
|
|
463
|
+
return;
|
|
464
|
+
if ('then' in infoResult) {
|
|
465
|
+
infoResult.then(node => {
|
|
466
|
+
if (node && this.view.state.field(this.stateField, false) == cState)
|
|
467
|
+
this.addInfoPane(node);
|
|
468
|
+
}).catch(e => view.logException(this.view.state, e, "completion info"));
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
this.addInfoPane(infoResult);
|
|
465
472
|
}
|
|
466
473
|
}
|
|
467
474
|
}
|
|
475
|
+
addInfoPane(content) {
|
|
476
|
+
let dom = this.info = document.createElement("div");
|
|
477
|
+
dom.className = "cm-tooltip cm-completionInfo";
|
|
478
|
+
dom.appendChild(content);
|
|
479
|
+
this.dom.appendChild(dom);
|
|
480
|
+
this.view.requestMeasure(this.placeInfo);
|
|
481
|
+
}
|
|
468
482
|
updateSelectedOption(selected) {
|
|
469
483
|
let set = null;
|
|
470
484
|
for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
|
|
@@ -550,7 +564,6 @@ function scrollIntoView(container, element) {
|
|
|
550
564
|
container.scrollTop += self.bottom - parent.bottom;
|
|
551
565
|
}
|
|
552
566
|
|
|
553
|
-
const MaxOptions = 300;
|
|
554
567
|
// Used to pick a preferred option when two options with the same
|
|
555
568
|
// label occur in the result.
|
|
556
569
|
function score(option) {
|
|
@@ -562,8 +575,14 @@ function sortOptions(active, state) {
|
|
|
562
575
|
for (let a of active)
|
|
563
576
|
if (a.hasResult()) {
|
|
564
577
|
if (a.result.filter === false) {
|
|
565
|
-
|
|
566
|
-
|
|
578
|
+
let getMatch = a.result.getMatch;
|
|
579
|
+
for (let option of a.result.options) {
|
|
580
|
+
let match = [1e9 - i++];
|
|
581
|
+
if (getMatch)
|
|
582
|
+
for (let n of getMatch(option))
|
|
583
|
+
match.push(n);
|
|
584
|
+
options.push(new Option(option, a, match));
|
|
585
|
+
}
|
|
567
586
|
}
|
|
568
587
|
else {
|
|
569
588
|
let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
|
|
@@ -577,10 +596,9 @@ function sortOptions(active, state) {
|
|
|
577
596
|
}
|
|
578
597
|
let result = [], prev = null;
|
|
579
598
|
for (let opt of options.sort(cmpOption)) {
|
|
580
|
-
if (result.length == MaxOptions)
|
|
581
|
-
break;
|
|
582
599
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
583
|
-
prev.type != opt.completion.type
|
|
600
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
601
|
+
prev.apply != opt.completion.apply)
|
|
584
602
|
result.push(opt);
|
|
585
603
|
else if (score(opt.completion) > score(prev))
|
|
586
604
|
result[result.length - 1] = opt;
|
|
@@ -730,24 +748,27 @@ class ActiveSource {
|
|
|
730
748
|
}
|
|
731
749
|
}
|
|
732
750
|
class ActiveResult extends ActiveSource {
|
|
733
|
-
constructor(source, explicitPos, result, from, to
|
|
751
|
+
constructor(source, explicitPos, result, from, to) {
|
|
734
752
|
super(source, 2 /* Result */, explicitPos);
|
|
735
753
|
this.result = result;
|
|
736
754
|
this.from = from;
|
|
737
755
|
this.to = to;
|
|
738
|
-
this.span = span;
|
|
739
756
|
}
|
|
740
757
|
hasResult() { return true; }
|
|
741
758
|
handleUserEvent(tr, type, conf) {
|
|
759
|
+
var _a;
|
|
742
760
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
743
761
|
let pos = cur(tr.state);
|
|
744
762
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
745
763
|
pos > to ||
|
|
746
764
|
type == "delete" && cur(tr.startState) == this.from)
|
|
747
765
|
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
|
|
766
|
+
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
|
|
767
|
+
if (checkValid(this.result.validFor, tr.state, from, to))
|
|
768
|
+
return new ActiveResult(this.source, explicitPos, this.result, from, to);
|
|
769
|
+
if (this.result.update &&
|
|
770
|
+
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
|
|
771
|
+
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
|
|
751
772
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
752
773
|
}
|
|
753
774
|
handleChange(tr) {
|
|
@@ -755,9 +776,15 @@ class ActiveResult extends ActiveSource {
|
|
|
755
776
|
}
|
|
756
777
|
map(mapping) {
|
|
757
778
|
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)
|
|
779
|
+
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
|
|
759
780
|
}
|
|
760
781
|
}
|
|
782
|
+
function checkValid(validFor, state, from, to) {
|
|
783
|
+
if (!validFor)
|
|
784
|
+
return false;
|
|
785
|
+
let text = state.sliceDoc(from, to);
|
|
786
|
+
return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
|
|
787
|
+
}
|
|
761
788
|
const startCompletionEffect = state.StateEffect.define();
|
|
762
789
|
const closeCompletionEffect = state.StateEffect.define();
|
|
763
790
|
const setActiveEffect = state.StateEffect.define({
|
|
@@ -768,7 +795,7 @@ const completionState = state.StateField.define({
|
|
|
768
795
|
create() { return CompletionState.start(); },
|
|
769
796
|
update(value, tr) { return value.update(tr); },
|
|
770
797
|
provide: f => [
|
|
771
|
-
|
|
798
|
+
view.showTooltip.from(f, val => val.tooltip),
|
|
772
799
|
view.EditorView.contentAttributes.from(f, state => state.attrs)
|
|
773
800
|
]
|
|
774
801
|
});
|
|
@@ -779,20 +806,20 @@ Returns a command that moves the completion selection forward or
|
|
|
779
806
|
backward by the given amount.
|
|
780
807
|
*/
|
|
781
808
|
function moveCompletionSelection(forward, by = "option") {
|
|
782
|
-
return (view) => {
|
|
783
|
-
let cState = view.state.field(completionState, false);
|
|
809
|
+
return (view$1) => {
|
|
810
|
+
let cState = view$1.state.field(completionState, false);
|
|
784
811
|
if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
|
|
785
812
|
return false;
|
|
786
|
-
let step = 1, tooltip
|
|
787
|
-
if (by == "page" && (tooltip
|
|
788
|
-
step = Math.max(2, Math.floor(tooltip
|
|
789
|
-
tooltip
|
|
813
|
+
let step = 1, tooltip;
|
|
814
|
+
if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
|
|
815
|
+
step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
|
|
816
|
+
tooltip.dom.querySelector("li").offsetHeight) - 1);
|
|
790
817
|
let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
|
|
791
818
|
if (selected < 0)
|
|
792
819
|
selected = by == "page" ? 0 : length - 1;
|
|
793
820
|
else if (selected >= length)
|
|
794
821
|
selected = by == "page" ? length - 1 : 0;
|
|
795
|
-
view.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
822
|
+
view$1.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
796
823
|
return true;
|
|
797
824
|
};
|
|
798
825
|
}
|
|
@@ -931,7 +958,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
931
958
|
continue;
|
|
932
959
|
this.running.splice(i--, 1);
|
|
933
960
|
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)
|
|
961
|
+
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
962
|
// Replay the transactions that happened since the start of
|
|
936
963
|
// the request and see if that preserves the result
|
|
937
964
|
for (let tr of query.updates)
|
|
@@ -963,6 +990,11 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
963
990
|
}
|
|
964
991
|
}, {
|
|
965
992
|
eventHandlers: {
|
|
993
|
+
blur() {
|
|
994
|
+
let state = this.view.state.field(completionState, false);
|
|
995
|
+
if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur)
|
|
996
|
+
this.view.dispatch({ effects: closeCompletionEffect.of(null) });
|
|
997
|
+
},
|
|
966
998
|
compositionstart() {
|
|
967
999
|
this.composing = 1 /* Started */;
|
|
968
1000
|
},
|
|
@@ -1204,8 +1236,9 @@ function fieldSelection(ranges, field) {
|
|
|
1204
1236
|
return state.EditorSelection.create(ranges.filter(r => r.field == field).map(r => state.EditorSelection.range(r.from, r.to)));
|
|
1205
1237
|
}
|
|
1206
1238
|
/**
|
|
1207
|
-
Convert a snippet template to a function that can
|
|
1208
|
-
Snippets are written
|
|
1239
|
+
Convert a snippet template to a function that can
|
|
1240
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
1241
|
+
using syntax like this:
|
|
1209
1242
|
|
|
1210
1243
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1211
1244
|
|
|
@@ -1388,8 +1421,238 @@ const completeAnyWord = context => {
|
|
|
1388
1421
|
return null;
|
|
1389
1422
|
let from = token ? token.from : context.pos;
|
|
1390
1423
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1391
|
-
return { from, options,
|
|
1424
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
const defaults = {
|
|
1428
|
+
brackets: ["(", "[", "{", "'", '"'],
|
|
1429
|
+
before: ")]}:;>"
|
|
1430
|
+
};
|
|
1431
|
+
const closeBracketEffect = state.StateEffect.define({
|
|
1432
|
+
map(value, mapping) {
|
|
1433
|
+
let mapped = mapping.mapPos(value, -1, state.MapMode.TrackAfter);
|
|
1434
|
+
return mapped == null ? undefined : mapped;
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
const skipBracketEffect = state.StateEffect.define({
|
|
1438
|
+
map(value, mapping) { return mapping.mapPos(value); }
|
|
1439
|
+
});
|
|
1440
|
+
const closedBracket = new class extends state.RangeValue {
|
|
1441
|
+
};
|
|
1442
|
+
closedBracket.startSide = 1;
|
|
1443
|
+
closedBracket.endSide = -1;
|
|
1444
|
+
const bracketState = state.StateField.define({
|
|
1445
|
+
create() { return state.RangeSet.empty; },
|
|
1446
|
+
update(value, tr) {
|
|
1447
|
+
if (tr.selection) {
|
|
1448
|
+
let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from;
|
|
1449
|
+
let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from;
|
|
1450
|
+
if (lineStart != tr.changes.mapPos(prevLineStart, -1))
|
|
1451
|
+
value = state.RangeSet.empty;
|
|
1452
|
+
}
|
|
1453
|
+
value = value.map(tr.changes);
|
|
1454
|
+
for (let effect of tr.effects) {
|
|
1455
|
+
if (effect.is(closeBracketEffect))
|
|
1456
|
+
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1457
|
+
else if (effect.is(skipBracketEffect))
|
|
1458
|
+
value = value.update({ filter: from => from != effect.value });
|
|
1459
|
+
}
|
|
1460
|
+
return value;
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
/**
|
|
1464
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
1465
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
1466
|
+
after the cursor. When closing a bracket directly in front of a
|
|
1467
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
1468
|
+
that bracket.
|
|
1469
|
+
*/
|
|
1470
|
+
function closeBrackets() {
|
|
1471
|
+
return [inputHandler, bracketState];
|
|
1472
|
+
}
|
|
1473
|
+
const definedClosing = "()[]{}<>";
|
|
1474
|
+
function closing(ch) {
|
|
1475
|
+
for (let i = 0; i < definedClosing.length; i += 2)
|
|
1476
|
+
if (definedClosing.charCodeAt(i) == ch)
|
|
1477
|
+
return definedClosing.charAt(i + 1);
|
|
1478
|
+
return state.fromCodePoint(ch < 128 ? ch : ch + 1);
|
|
1479
|
+
}
|
|
1480
|
+
function config(state, pos) {
|
|
1481
|
+
return state.languageDataAt("closeBrackets", pos)[0] || defaults;
|
|
1482
|
+
}
|
|
1483
|
+
const android = typeof navigator == "object" && /Android\b/.test(navigator.userAgent);
|
|
1484
|
+
const inputHandler = view.EditorView.inputHandler.of((view, from, to, insert) => {
|
|
1485
|
+
if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
|
|
1486
|
+
return false;
|
|
1487
|
+
let sel = view.state.selection.main;
|
|
1488
|
+
if (insert.length > 2 || insert.length == 2 && state.codePointSize(state.codePointAt(insert, 0)) == 1 ||
|
|
1489
|
+
from != sel.from || to != sel.to)
|
|
1490
|
+
return false;
|
|
1491
|
+
let tr = insertBracket(view.state, insert);
|
|
1492
|
+
if (!tr)
|
|
1493
|
+
return false;
|
|
1494
|
+
view.dispatch(tr);
|
|
1495
|
+
return true;
|
|
1496
|
+
});
|
|
1497
|
+
/**
|
|
1498
|
+
Command that implements deleting a pair of matching brackets when
|
|
1499
|
+
the cursor is between them.
|
|
1500
|
+
*/
|
|
1501
|
+
const deleteBracketPair = ({ state: state$1, dispatch }) => {
|
|
1502
|
+
if (state$1.readOnly)
|
|
1503
|
+
return false;
|
|
1504
|
+
let conf = config(state$1, state$1.selection.main.head);
|
|
1505
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1506
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1507
|
+
if (range.empty) {
|
|
1508
|
+
let before = prevChar(state$1.doc, range.head);
|
|
1509
|
+
for (let token of tokens) {
|
|
1510
|
+
if (token == before && nextChar(state$1.doc, range.head) == closing(state.codePointAt(token, 0)))
|
|
1511
|
+
return { changes: { from: range.head - token.length, to: range.head + token.length },
|
|
1512
|
+
range: state.EditorSelection.cursor(range.head - token.length),
|
|
1513
|
+
userEvent: "delete.backward" };
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return { range: dont = range };
|
|
1517
|
+
});
|
|
1518
|
+
if (!dont)
|
|
1519
|
+
dispatch(state$1.update(changes, { scrollIntoView: true }));
|
|
1520
|
+
return !dont;
|
|
1392
1521
|
};
|
|
1522
|
+
/**
|
|
1523
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
1524
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
1525
|
+
*/
|
|
1526
|
+
const closeBracketsKeymap = [
|
|
1527
|
+
{ key: "Backspace", run: deleteBracketPair }
|
|
1528
|
+
];
|
|
1529
|
+
/**
|
|
1530
|
+
Implements the extension's behavior on text insertion. If the
|
|
1531
|
+
given string counts as a bracket in the language around the
|
|
1532
|
+
selection, and replacing the selection with it requires custom
|
|
1533
|
+
behavior (inserting a closing version or skipping past a
|
|
1534
|
+
previously-closed bracket), this function returns a transaction
|
|
1535
|
+
representing that custom behavior. (You only need this if you want
|
|
1536
|
+
to programmatically insert brackets—the
|
|
1537
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
1538
|
+
take care of running this for user input.)
|
|
1539
|
+
*/
|
|
1540
|
+
function insertBracket(state$1, bracket) {
|
|
1541
|
+
let conf = config(state$1, state$1.selection.main.head);
|
|
1542
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1543
|
+
for (let tok of tokens) {
|
|
1544
|
+
let closed = closing(state.codePointAt(tok, 0));
|
|
1545
|
+
if (bracket == tok)
|
|
1546
|
+
return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
|
|
1547
|
+
: handleOpen(state$1, tok, closed, conf.before || defaults.before);
|
|
1548
|
+
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
|
|
1549
|
+
return handleClose(state$1, tok, closed);
|
|
1550
|
+
}
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1553
|
+
function closedBracketAt(state, pos) {
|
|
1554
|
+
let found = false;
|
|
1555
|
+
state.field(bracketState).between(0, state.doc.length, from => {
|
|
1556
|
+
if (from == pos)
|
|
1557
|
+
found = true;
|
|
1558
|
+
});
|
|
1559
|
+
return found;
|
|
1560
|
+
}
|
|
1561
|
+
function nextChar(doc, pos) {
|
|
1562
|
+
let next = doc.sliceString(pos, pos + 2);
|
|
1563
|
+
return next.slice(0, state.codePointSize(state.codePointAt(next, 0)));
|
|
1564
|
+
}
|
|
1565
|
+
function prevChar(doc, pos) {
|
|
1566
|
+
let prev = doc.sliceString(pos - 2, pos);
|
|
1567
|
+
return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
|
1568
|
+
}
|
|
1569
|
+
function handleOpen(state$1, open, close, closeBefore) {
|
|
1570
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1571
|
+
if (!range.empty)
|
|
1572
|
+
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
|
1573
|
+
effects: closeBracketEffect.of(range.to + open.length),
|
|
1574
|
+
range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
|
1575
|
+
let next = nextChar(state$1.doc, range.head);
|
|
1576
|
+
if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
|
1577
|
+
return { changes: { insert: open + close, from: range.head },
|
|
1578
|
+
effects: closeBracketEffect.of(range.head + open.length),
|
|
1579
|
+
range: state.EditorSelection.cursor(range.head + open.length) };
|
|
1580
|
+
return { range: dont = range };
|
|
1581
|
+
});
|
|
1582
|
+
return dont ? null : state$1.update(changes, {
|
|
1583
|
+
scrollIntoView: true,
|
|
1584
|
+
userEvent: "input.type"
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
function handleClose(state$1, _open, close) {
|
|
1588
|
+
let dont = null, moved = state$1.selection.ranges.map(range => {
|
|
1589
|
+
if (range.empty && nextChar(state$1.doc, range.head) == close)
|
|
1590
|
+
return state.EditorSelection.cursor(range.head + close.length);
|
|
1591
|
+
return dont = range;
|
|
1592
|
+
});
|
|
1593
|
+
return dont ? null : state$1.update({
|
|
1594
|
+
selection: state.EditorSelection.create(moved, state$1.selection.mainIndex),
|
|
1595
|
+
scrollIntoView: true,
|
|
1596
|
+
effects: state$1.selection.ranges.map(({ from }) => skipBracketEffect.of(from))
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
// Handles cases where the open and close token are the same, and
|
|
1600
|
+
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
|
1601
|
+
function handleSame(state$1, token, allowTriple) {
|
|
1602
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1603
|
+
if (!range.empty)
|
|
1604
|
+
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
|
1605
|
+
effects: closeBracketEffect.of(range.to + token.length),
|
|
1606
|
+
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
|
1607
|
+
let pos = range.head, next = nextChar(state$1.doc, pos);
|
|
1608
|
+
if (next == token) {
|
|
1609
|
+
if (nodeStart(state$1, pos)) {
|
|
1610
|
+
return { changes: { insert: token + token, from: pos },
|
|
1611
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1612
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1613
|
+
}
|
|
1614
|
+
else if (closedBracketAt(state$1, pos)) {
|
|
1615
|
+
let isTriple = allowTriple && state$1.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1616
|
+
return { range: state.EditorSelection.cursor(pos + token.length * (isTriple ? 3 : 1)),
|
|
1617
|
+
effects: skipBracketEffect.of(pos) };
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
|
1621
|
+
nodeStart(state$1, pos - 2 * token.length)) {
|
|
1622
|
+
return { changes: { insert: token + token + token + token, from: pos },
|
|
1623
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1624
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1625
|
+
}
|
|
1626
|
+
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
|
|
1627
|
+
let prev = state$1.sliceDoc(pos - 1, pos);
|
|
1628
|
+
if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
|
|
1629
|
+
return { changes: { insert: token + token, from: pos },
|
|
1630
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1631
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1632
|
+
}
|
|
1633
|
+
return { range: dont = range };
|
|
1634
|
+
});
|
|
1635
|
+
return dont ? null : state$1.update(changes, {
|
|
1636
|
+
scrollIntoView: true,
|
|
1637
|
+
userEvent: "input.type"
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
function nodeStart(state, pos) {
|
|
1641
|
+
let tree = language.syntaxTree(state).resolveInner(pos + 1);
|
|
1642
|
+
return tree.parent && tree.from == pos;
|
|
1643
|
+
}
|
|
1644
|
+
function probablyInString(state, pos, quoteToken) {
|
|
1645
|
+
let node = language.syntaxTree(state).resolveInner(pos, -1);
|
|
1646
|
+
for (let i = 0; i < 5; i++) {
|
|
1647
|
+
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
|
|
1648
|
+
return true;
|
|
1649
|
+
let parent = node.to == pos && node.parent;
|
|
1650
|
+
if (!parent)
|
|
1651
|
+
break;
|
|
1652
|
+
node = parent;
|
|
1653
|
+
}
|
|
1654
|
+
return false;
|
|
1655
|
+
}
|
|
1393
1656
|
|
|
1394
1657
|
/**
|
|
1395
1658
|
Returns an extension that enables autocompletion.
|
|
@@ -1435,13 +1698,19 @@ function completionStatus(state) {
|
|
|
1435
1698
|
return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending"
|
|
1436
1699
|
: cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null;
|
|
1437
1700
|
}
|
|
1701
|
+
const completionArrayCache = new WeakMap;
|
|
1438
1702
|
/**
|
|
1439
1703
|
Returns the available completions as an array.
|
|
1440
1704
|
*/
|
|
1441
1705
|
function currentCompletions(state) {
|
|
1442
1706
|
var _a;
|
|
1443
1707
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1444
|
-
|
|
1708
|
+
if (!open)
|
|
1709
|
+
return [];
|
|
1710
|
+
let completions = completionArrayCache.get(open.options);
|
|
1711
|
+
if (!completions)
|
|
1712
|
+
completionArrayCache.set(open.options, completions = open.options.map(o => o.completion));
|
|
1713
|
+
return completions;
|
|
1445
1714
|
}
|
|
1446
1715
|
/**
|
|
1447
1716
|
Return the currently selected completion, if any.
|
|
@@ -1451,24 +1720,46 @@ function selectedCompletion(state) {
|
|
|
1451
1720
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1452
1721
|
return open ? open.options[open.selected].completion : null;
|
|
1453
1722
|
}
|
|
1723
|
+
/**
|
|
1724
|
+
Returns the currently selected position in the active completion
|
|
1725
|
+
list, or null if no completions are active.
|
|
1726
|
+
*/
|
|
1727
|
+
function selectedCompletionIndex(state) {
|
|
1728
|
+
var _a;
|
|
1729
|
+
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1730
|
+
return open ? open.selected : null;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
Create an effect that can be attached to a transaction to change
|
|
1734
|
+
the currently selected completion.
|
|
1735
|
+
*/
|
|
1736
|
+
function setSelectedCompletion(index) {
|
|
1737
|
+
return setSelectedEffect.of(index);
|
|
1738
|
+
}
|
|
1454
1739
|
|
|
1455
1740
|
exports.CompletionContext = CompletionContext;
|
|
1456
1741
|
exports.acceptCompletion = acceptCompletion;
|
|
1457
1742
|
exports.autocompletion = autocompletion;
|
|
1458
1743
|
exports.clearSnippet = clearSnippet;
|
|
1744
|
+
exports.closeBrackets = closeBrackets;
|
|
1745
|
+
exports.closeBracketsKeymap = closeBracketsKeymap;
|
|
1459
1746
|
exports.closeCompletion = closeCompletion;
|
|
1460
1747
|
exports.completeAnyWord = completeAnyWord;
|
|
1461
1748
|
exports.completeFromList = completeFromList;
|
|
1462
1749
|
exports.completionKeymap = completionKeymap;
|
|
1463
1750
|
exports.completionStatus = completionStatus;
|
|
1464
1751
|
exports.currentCompletions = currentCompletions;
|
|
1752
|
+
exports.deleteBracketPair = deleteBracketPair;
|
|
1465
1753
|
exports.ifIn = ifIn;
|
|
1466
1754
|
exports.ifNotIn = ifNotIn;
|
|
1755
|
+
exports.insertBracket = insertBracket;
|
|
1467
1756
|
exports.moveCompletionSelection = moveCompletionSelection;
|
|
1468
1757
|
exports.nextSnippetField = nextSnippetField;
|
|
1469
1758
|
exports.pickedCompletion = pickedCompletion;
|
|
1470
1759
|
exports.prevSnippetField = prevSnippetField;
|
|
1471
1760
|
exports.selectedCompletion = selectedCompletion;
|
|
1761
|
+
exports.selectedCompletionIndex = selectedCompletionIndex;
|
|
1762
|
+
exports.setSelectedCompletion = setSelectedCompletion;
|
|
1472
1763
|
exports.snippet = snippet;
|
|
1473
1764
|
exports.snippetCompletion = snippetCompletion;
|
|
1474
1765
|
exports.snippetKeymap = snippetKeymap;
|