@codemirror/autocomplete 0.19.15 → 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 +18 -0
- package/dist/index.cjs +293 -41
- package/dist/index.d.ts +73 -14
- package/dist/index.js +273 -25
- package/package.json +5 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 0.20.0 (2022-04-20)
|
|
2
|
+
|
|
3
|
+
### Breaking changes
|
|
4
|
+
|
|
5
|
+
`CompletionResult.span` has been renamed to `validFor`, and may now hold a function as well as a regular expression.
|
|
6
|
+
|
|
7
|
+
### Bug fixes
|
|
8
|
+
|
|
9
|
+
Remove code that dropped any options beyond the 300th one when matching and sorting option lists.
|
|
10
|
+
|
|
11
|
+
Completion will now apply to all cursors when there are multiple cursors.
|
|
12
|
+
|
|
13
|
+
### New features
|
|
14
|
+
|
|
15
|
+
`CompletionResult.update` can now be used to implement quick autocompletion updates in a synchronous way.
|
|
16
|
+
|
|
17
|
+
The @codemirror/closebrackets package was merged into this one.
|
|
18
|
+
|
|
1
19
|
## 0.19.15 (2022-03-23)
|
|
2
20
|
|
|
3
21
|
### New features
|
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 {
|
|
@@ -552,7 +562,6 @@ function scrollIntoView(container, element) {
|
|
|
552
562
|
container.scrollTop += self.bottom - parent.bottom;
|
|
553
563
|
}
|
|
554
564
|
|
|
555
|
-
const MaxOptions = 300;
|
|
556
565
|
// Used to pick a preferred option when two options with the same
|
|
557
566
|
// label occur in the result.
|
|
558
567
|
function score(option) {
|
|
@@ -579,10 +588,9 @@ function sortOptions(active, state) {
|
|
|
579
588
|
}
|
|
580
589
|
let result = [], prev = null;
|
|
581
590
|
for (let opt of options.sort(cmpOption)) {
|
|
582
|
-
if (result.length == MaxOptions)
|
|
583
|
-
break;
|
|
584
591
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
585
|
-
prev.type != opt.completion.type
|
|
592
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
593
|
+
prev.apply != opt.completion.apply)
|
|
586
594
|
result.push(opt);
|
|
587
595
|
else if (score(opt.completion) > score(prev))
|
|
588
596
|
result[result.length - 1] = opt;
|
|
@@ -732,24 +740,27 @@ class ActiveSource {
|
|
|
732
740
|
}
|
|
733
741
|
}
|
|
734
742
|
class ActiveResult extends ActiveSource {
|
|
735
|
-
constructor(source, explicitPos, result, from, to
|
|
743
|
+
constructor(source, explicitPos, result, from, to) {
|
|
736
744
|
super(source, 2 /* Result */, explicitPos);
|
|
737
745
|
this.result = result;
|
|
738
746
|
this.from = from;
|
|
739
747
|
this.to = to;
|
|
740
|
-
this.span = span;
|
|
741
748
|
}
|
|
742
749
|
hasResult() { return true; }
|
|
743
750
|
handleUserEvent(tr, type, conf) {
|
|
751
|
+
var _a;
|
|
744
752
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
745
753
|
let pos = cur(tr.state);
|
|
746
754
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
747
755
|
pos > to ||
|
|
748
756
|
type == "delete" && cur(tr.startState) == this.from)
|
|
749
757
|
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
|
|
750
|
-
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
|
|
751
|
-
if (
|
|
752
|
-
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));
|
|
753
764
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
754
765
|
}
|
|
755
766
|
handleChange(tr) {
|
|
@@ -757,9 +768,15 @@ class ActiveResult extends ActiveSource {
|
|
|
757
768
|
}
|
|
758
769
|
map(mapping) {
|
|
759
770
|
return mapping.empty ? this :
|
|
760
|
-
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));
|
|
761
772
|
}
|
|
762
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
|
+
}
|
|
763
780
|
const startCompletionEffect = state.StateEffect.define();
|
|
764
781
|
const closeCompletionEffect = state.StateEffect.define();
|
|
765
782
|
const setActiveEffect = state.StateEffect.define({
|
|
@@ -770,7 +787,7 @@ const completionState = state.StateField.define({
|
|
|
770
787
|
create() { return CompletionState.start(); },
|
|
771
788
|
update(value, tr) { return value.update(tr); },
|
|
772
789
|
provide: f => [
|
|
773
|
-
|
|
790
|
+
view.showTooltip.from(f, val => val.tooltip),
|
|
774
791
|
view.EditorView.contentAttributes.from(f, state => state.attrs)
|
|
775
792
|
]
|
|
776
793
|
});
|
|
@@ -781,20 +798,20 @@ Returns a command that moves the completion selection forward or
|
|
|
781
798
|
backward by the given amount.
|
|
782
799
|
*/
|
|
783
800
|
function moveCompletionSelection(forward, by = "option") {
|
|
784
|
-
return (view) => {
|
|
785
|
-
let cState = view.state.field(completionState, false);
|
|
801
|
+
return (view$1) => {
|
|
802
|
+
let cState = view$1.state.field(completionState, false);
|
|
786
803
|
if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
|
|
787
804
|
return false;
|
|
788
|
-
let step = 1, tooltip
|
|
789
|
-
if (by == "page" && (tooltip
|
|
790
|
-
step = Math.max(2, Math.floor(tooltip
|
|
791
|
-
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);
|
|
792
809
|
let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
|
|
793
810
|
if (selected < 0)
|
|
794
811
|
selected = by == "page" ? 0 : length - 1;
|
|
795
812
|
else if (selected >= length)
|
|
796
813
|
selected = by == "page" ? length - 1 : 0;
|
|
797
|
-
view.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
814
|
+
view$1.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
798
815
|
return true;
|
|
799
816
|
};
|
|
800
817
|
}
|
|
@@ -933,7 +950,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
933
950
|
continue;
|
|
934
951
|
this.running.splice(i--, 1);
|
|
935
952
|
if (query.done) {
|
|
936
|
-
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));
|
|
937
954
|
// Replay the transactions that happened since the start of
|
|
938
955
|
// the request and see if that preserves the result
|
|
939
956
|
for (let tr of query.updates)
|
|
@@ -1206,8 +1223,9 @@ function fieldSelection(ranges, field) {
|
|
|
1206
1223
|
return state.EditorSelection.create(ranges.filter(r => r.field == field).map(r => state.EditorSelection.range(r.from, r.to)));
|
|
1207
1224
|
}
|
|
1208
1225
|
/**
|
|
1209
|
-
Convert a snippet template to a function that can
|
|
1210
|
-
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:
|
|
1211
1229
|
|
|
1212
1230
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1213
1231
|
|
|
@@ -1390,8 +1408,238 @@ const completeAnyWord = context => {
|
|
|
1390
1408
|
return null;
|
|
1391
1409
|
let from = token ? token.from : context.pos;
|
|
1392
1410
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1393
|
-
return { from, options,
|
|
1411
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1412
|
+
};
|
|
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 {
|
|
1394
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
|
+
}
|
|
1395
1643
|
|
|
1396
1644
|
/**
|
|
1397
1645
|
Returns an extension that enables autocompletion.
|
|
@@ -1480,14 +1728,18 @@ exports.CompletionContext = CompletionContext;
|
|
|
1480
1728
|
exports.acceptCompletion = acceptCompletion;
|
|
1481
1729
|
exports.autocompletion = autocompletion;
|
|
1482
1730
|
exports.clearSnippet = clearSnippet;
|
|
1731
|
+
exports.closeBrackets = closeBrackets;
|
|
1732
|
+
exports.closeBracketsKeymap = closeBracketsKeymap;
|
|
1483
1733
|
exports.closeCompletion = closeCompletion;
|
|
1484
1734
|
exports.completeAnyWord = completeAnyWord;
|
|
1485
1735
|
exports.completeFromList = completeFromList;
|
|
1486
1736
|
exports.completionKeymap = completionKeymap;
|
|
1487
1737
|
exports.completionStatus = completionStatus;
|
|
1488
1738
|
exports.currentCompletions = currentCompletions;
|
|
1739
|
+
exports.deleteBracketPair = deleteBracketPair;
|
|
1489
1740
|
exports.ifIn = ifIn;
|
|
1490
1741
|
exports.ifNotIn = ifNotIn;
|
|
1742
|
+
exports.insertBracket = insertBracket;
|
|
1491
1743
|
exports.moveCompletionSelection = moveCompletionSelection;
|
|
1492
1744
|
exports.nextSnippetField = nextSnippetField;
|
|
1493
1745
|
exports.pickedCompletion = pickedCompletion;
|
package/dist/index.d.ts
CHANGED
|
@@ -52,8 +52,7 @@ interface CompletionConfig {
|
|
|
52
52
|
completion, and should produce a DOM node to show. `position`
|
|
53
53
|
determines where in the DOM the result appears, relative to
|
|
54
54
|
other added widgets and the standard content. The default icons
|
|
55
|
-
have position 20, the label position 50, and the detail position
|
|
56
|
-
70.
|
|
55
|
+
have position 20, the label position 50, and the detail position 70.
|
|
57
56
|
*/
|
|
58
57
|
addToOptions?: {
|
|
59
58
|
render: (completion: Completion, state: EditorState) => Node | null;
|
|
@@ -226,23 +225,32 @@ interface CompletionResult {
|
|
|
226
225
|
*/
|
|
227
226
|
options: readonly Completion[];
|
|
228
227
|
/**
|
|
229
|
-
When given, further
|
|
230
|
-
between ([mapped](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) `from`
|
|
231
|
-
match this regular expression
|
|
232
|
-
source again, but continue with
|
|
233
|
-
help a lot with responsiveness,
|
|
234
|
-
list to be updated synchronously.
|
|
228
|
+
When given, further typing or deletion that causes the part of
|
|
229
|
+
the document between ([mapped](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) `from`
|
|
230
|
+
and `to` to match this regular expression or predicate function
|
|
231
|
+
will not query the completion source again, but continue with
|
|
232
|
+
this list of options. This can help a lot with responsiveness,
|
|
233
|
+
since it allows the completion list to be updated synchronously.
|
|
235
234
|
*/
|
|
236
|
-
|
|
235
|
+
validFor?: RegExp | ((text: string, from: number, to: number, state: EditorState) => boolean);
|
|
237
236
|
/**
|
|
238
237
|
By default, the library filters and scores completions. Set
|
|
239
238
|
`filter` to `false` to disable this, and cause your completions
|
|
240
239
|
to all be included, in the order they were given. When there are
|
|
241
240
|
other sources, unfiltered completions appear at the top of the
|
|
242
|
-
list of completions. `
|
|
243
|
-
`false`, because it only works when filtering.
|
|
241
|
+
list of completions. `validFor` must not be given when `filter`
|
|
242
|
+
is `false`, because it only works when filtering.
|
|
244
243
|
*/
|
|
245
244
|
filter?: boolean;
|
|
245
|
+
/**
|
|
246
|
+
Synchronously update the completion result after typing or
|
|
247
|
+
deletion. If given, this should not do any expensive work, since
|
|
248
|
+
it will be called during editor state updates. The function
|
|
249
|
+
should make sure (similar to
|
|
250
|
+
[`validFor`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.validFor)) that the
|
|
251
|
+
completion still applies in the new state.
|
|
252
|
+
*/
|
|
253
|
+
update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null;
|
|
246
254
|
}
|
|
247
255
|
/**
|
|
248
256
|
This annotation is added to transactions that are produced by
|
|
@@ -251,8 +259,9 @@ picking a completion.
|
|
|
251
259
|
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
|
|
252
260
|
|
|
253
261
|
/**
|
|
254
|
-
Convert a snippet template to a function that can
|
|
255
|
-
Snippets are written
|
|
262
|
+
Convert a snippet template to a function that can
|
|
263
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
264
|
+
using syntax like this:
|
|
256
265
|
|
|
257
266
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
258
267
|
|
|
@@ -331,6 +340,56 @@ return those as completions.
|
|
|
331
340
|
*/
|
|
332
341
|
declare const completeAnyWord: CompletionSource;
|
|
333
342
|
|
|
343
|
+
/**
|
|
344
|
+
Configures bracket closing behavior for a syntax (via
|
|
345
|
+
[language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt)) using the `"closeBrackets"`
|
|
346
|
+
identifier.
|
|
347
|
+
*/
|
|
348
|
+
interface CloseBracketConfig {
|
|
349
|
+
/**
|
|
350
|
+
The opening brackets to close. Defaults to `["(", "[", "{", "'",
|
|
351
|
+
'"']`. Brackets may be single characters or a triple of quotes
|
|
352
|
+
(as in `"''''"`).
|
|
353
|
+
*/
|
|
354
|
+
brackets?: string[];
|
|
355
|
+
/**
|
|
356
|
+
Characters in front of which newly opened brackets are
|
|
357
|
+
automatically closed. Closing always happens in front of
|
|
358
|
+
whitespace. Defaults to `")]}:;>"`.
|
|
359
|
+
*/
|
|
360
|
+
before?: string;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
364
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
365
|
+
after the cursor. When closing a bracket directly in front of a
|
|
366
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
367
|
+
that bracket.
|
|
368
|
+
*/
|
|
369
|
+
declare function closeBrackets(): Extension;
|
|
370
|
+
/**
|
|
371
|
+
Command that implements deleting a pair of matching brackets when
|
|
372
|
+
the cursor is between them.
|
|
373
|
+
*/
|
|
374
|
+
declare const deleteBracketPair: StateCommand;
|
|
375
|
+
/**
|
|
376
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
377
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
378
|
+
*/
|
|
379
|
+
declare const closeBracketsKeymap: readonly KeyBinding[];
|
|
380
|
+
/**
|
|
381
|
+
Implements the extension's behavior on text insertion. If the
|
|
382
|
+
given string counts as a bracket in the language around the
|
|
383
|
+
selection, and replacing the selection with it requires custom
|
|
384
|
+
behavior (inserting a closing version or skipping past a
|
|
385
|
+
previously-closed bracket), this function returns a transaction
|
|
386
|
+
representing that custom behavior. (You only need this if you want
|
|
387
|
+
to programmatically insert brackets—the
|
|
388
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
389
|
+
take care of running this for user input.)
|
|
390
|
+
*/
|
|
391
|
+
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
|
|
392
|
+
|
|
334
393
|
/**
|
|
335
394
|
Returns an extension that enables autocompletion.
|
|
336
395
|
*/
|
|
@@ -373,4 +432,4 @@ the currently selected completion.
|
|
|
373
432
|
*/
|
|
374
433
|
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
|
|
375
434
|
|
|
376
|
-
export { Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
|
435
|
+
export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Annotation, Facet, combineConfig, StateEffect, StateField, Prec,
|
|
2
|
-
import { logException, Direction, EditorView, ViewPlugin, Decoration, WidgetType, keymap } from '@codemirror/view';
|
|
3
|
-
import { showTooltip, getTooltip } from '@codemirror/tooltip';
|
|
1
|
+
import { Annotation, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateEffect, StateField, Prec, Text, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
|
|
2
|
+
import { logException, Direction, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
|
|
4
3
|
import { syntaxTree, indentUnit } from '@codemirror/language';
|
|
5
|
-
import { codePointAt, codePointSize, fromCodePoint } from '@codemirror/text';
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
An instance of this is passed to completion source functions.
|
|
@@ -98,10 +96,10 @@ completes them.
|
|
|
98
96
|
*/
|
|
99
97
|
function completeFromList(list) {
|
|
100
98
|
let options = list.map(o => typeof o == "string" ? { label: o } : o);
|
|
101
|
-
let [
|
|
99
|
+
let [validFor, match] = options.every(o => /^\w+$/.test(o.label)) ? [/\w*$/, /\w+$/] : prefixMatch(options);
|
|
102
100
|
return (context) => {
|
|
103
101
|
let token = context.matchBefore(match);
|
|
104
|
-
return token || context.explicit ? { from: token ? token.from : context.pos, options,
|
|
102
|
+
return token || context.explicit ? { from: token ? token.from : context.pos, options, validFor } : null;
|
|
105
103
|
};
|
|
106
104
|
}
|
|
107
105
|
/**
|
|
@@ -152,12 +150,24 @@ picking a completion.
|
|
|
152
150
|
*/
|
|
153
151
|
const pickedCompletion = /*@__PURE__*/Annotation.define();
|
|
154
152
|
function applyCompletion(view, option) {
|
|
155
|
-
|
|
153
|
+
const apply = option.completion.apply || option.completion.label;
|
|
156
154
|
let result = option.source;
|
|
157
155
|
if (typeof apply == "string") {
|
|
158
|
-
view.dispatch({
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
view.dispatch(view.state.changeByRange(range => {
|
|
157
|
+
if (range == view.state.selection.main)
|
|
158
|
+
return {
|
|
159
|
+
changes: { from: result.from, to: result.to, insert: apply },
|
|
160
|
+
range: EditorSelection.cursor(result.from + apply.length)
|
|
161
|
+
};
|
|
162
|
+
let len = result.to - result.from;
|
|
163
|
+
if (!range.empty ||
|
|
164
|
+
len && view.state.sliceDoc(range.from - len, range.from) != view.state.sliceDoc(result.from, result.to))
|
|
165
|
+
return { range };
|
|
166
|
+
return {
|
|
167
|
+
changes: { from: range.from - len, to: range.from, insert: apply },
|
|
168
|
+
range: EditorSelection.cursor(range.from - len + apply.length)
|
|
169
|
+
};
|
|
170
|
+
}), {
|
|
161
171
|
userEvent: "input.complete",
|
|
162
172
|
annotations: pickedCompletion.of(option.completion)
|
|
163
173
|
});
|
|
@@ -548,7 +558,6 @@ function scrollIntoView(container, element) {
|
|
|
548
558
|
container.scrollTop += self.bottom - parent.bottom;
|
|
549
559
|
}
|
|
550
560
|
|
|
551
|
-
const MaxOptions = 300;
|
|
552
561
|
// Used to pick a preferred option when two options with the same
|
|
553
562
|
// label occur in the result.
|
|
554
563
|
function score(option) {
|
|
@@ -575,10 +584,9 @@ function sortOptions(active, state) {
|
|
|
575
584
|
}
|
|
576
585
|
let result = [], prev = null;
|
|
577
586
|
for (let opt of options.sort(cmpOption)) {
|
|
578
|
-
if (result.length == MaxOptions)
|
|
579
|
-
break;
|
|
580
587
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
581
|
-
prev.type != opt.completion.type
|
|
588
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
589
|
+
prev.apply != opt.completion.apply)
|
|
582
590
|
result.push(opt);
|
|
583
591
|
else if (score(opt.completion) > score(prev))
|
|
584
592
|
result[result.length - 1] = opt;
|
|
@@ -728,24 +736,27 @@ class ActiveSource {
|
|
|
728
736
|
}
|
|
729
737
|
}
|
|
730
738
|
class ActiveResult extends ActiveSource {
|
|
731
|
-
constructor(source, explicitPos, result, from, to
|
|
739
|
+
constructor(source, explicitPos, result, from, to) {
|
|
732
740
|
super(source, 2 /* Result */, explicitPos);
|
|
733
741
|
this.result = result;
|
|
734
742
|
this.from = from;
|
|
735
743
|
this.to = to;
|
|
736
|
-
this.span = span;
|
|
737
744
|
}
|
|
738
745
|
hasResult() { return true; }
|
|
739
746
|
handleUserEvent(tr, type, conf) {
|
|
747
|
+
var _a;
|
|
740
748
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
741
749
|
let pos = cur(tr.state);
|
|
742
750
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
743
751
|
pos > to ||
|
|
744
752
|
type == "delete" && cur(tr.startState) == this.from)
|
|
745
753
|
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
|
|
746
|
-
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
|
|
747
|
-
if (
|
|
748
|
-
return new ActiveResult(this.source, explicitPos, this.result, from, to
|
|
754
|
+
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
|
|
755
|
+
if (checkValid(this.result.validFor, tr.state, from, to))
|
|
756
|
+
return new ActiveResult(this.source, explicitPos, this.result, from, to);
|
|
757
|
+
if (this.result.update &&
|
|
758
|
+
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
|
|
759
|
+
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
|
|
749
760
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
750
761
|
}
|
|
751
762
|
handleChange(tr) {
|
|
@@ -753,9 +764,15 @@ class ActiveResult extends ActiveSource {
|
|
|
753
764
|
}
|
|
754
765
|
map(mapping) {
|
|
755
766
|
return mapping.empty ? this :
|
|
756
|
-
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1)
|
|
767
|
+
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
|
|
757
768
|
}
|
|
758
769
|
}
|
|
770
|
+
function checkValid(validFor, state, from, to) {
|
|
771
|
+
if (!validFor)
|
|
772
|
+
return false;
|
|
773
|
+
let text = state.sliceDoc(from, to);
|
|
774
|
+
return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
|
|
775
|
+
}
|
|
759
776
|
const startCompletionEffect = /*@__PURE__*/StateEffect.define();
|
|
760
777
|
const closeCompletionEffect = /*@__PURE__*/StateEffect.define();
|
|
761
778
|
const setActiveEffect = /*@__PURE__*/StateEffect.define({
|
|
@@ -929,7 +946,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
929
946
|
continue;
|
|
930
947
|
this.running.splice(i--, 1);
|
|
931
948
|
if (query.done) {
|
|
932
|
-
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)
|
|
949
|
+
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));
|
|
933
950
|
// Replay the transactions that happened since the start of
|
|
934
951
|
// the request and see if that preserves the result
|
|
935
952
|
for (let tr of query.updates)
|
|
@@ -1202,8 +1219,9 @@ function fieldSelection(ranges, field) {
|
|
|
1202
1219
|
return EditorSelection.create(ranges.filter(r => r.field == field).map(r => EditorSelection.range(r.from, r.to)));
|
|
1203
1220
|
}
|
|
1204
1221
|
/**
|
|
1205
|
-
Convert a snippet template to a function that can
|
|
1206
|
-
Snippets are written
|
|
1222
|
+
Convert a snippet template to a function that can
|
|
1223
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
1224
|
+
using syntax like this:
|
|
1207
1225
|
|
|
1208
1226
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1209
1227
|
|
|
@@ -1386,8 +1404,238 @@ const completeAnyWord = context => {
|
|
|
1386
1404
|
return null;
|
|
1387
1405
|
let from = token ? token.from : context.pos;
|
|
1388
1406
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1389
|
-
return { from, options,
|
|
1407
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
const defaults = {
|
|
1411
|
+
brackets: ["(", "[", "{", "'", '"'],
|
|
1412
|
+
before: ")]}:;>"
|
|
1413
|
+
};
|
|
1414
|
+
const closeBracketEffect = /*@__PURE__*/StateEffect.define({
|
|
1415
|
+
map(value, mapping) {
|
|
1416
|
+
let mapped = mapping.mapPos(value, -1, MapMode.TrackAfter);
|
|
1417
|
+
return mapped == null ? undefined : mapped;
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
const skipBracketEffect = /*@__PURE__*/StateEffect.define({
|
|
1421
|
+
map(value, mapping) { return mapping.mapPos(value); }
|
|
1422
|
+
});
|
|
1423
|
+
const closedBracket = /*@__PURE__*/new class extends RangeValue {
|
|
1390
1424
|
};
|
|
1425
|
+
closedBracket.startSide = 1;
|
|
1426
|
+
closedBracket.endSide = -1;
|
|
1427
|
+
const bracketState = /*@__PURE__*/StateField.define({
|
|
1428
|
+
create() { return RangeSet.empty; },
|
|
1429
|
+
update(value, tr) {
|
|
1430
|
+
if (tr.selection) {
|
|
1431
|
+
let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from;
|
|
1432
|
+
let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from;
|
|
1433
|
+
if (lineStart != tr.changes.mapPos(prevLineStart, -1))
|
|
1434
|
+
value = RangeSet.empty;
|
|
1435
|
+
}
|
|
1436
|
+
value = value.map(tr.changes);
|
|
1437
|
+
for (let effect of tr.effects) {
|
|
1438
|
+
if (effect.is(closeBracketEffect))
|
|
1439
|
+
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1440
|
+
else if (effect.is(skipBracketEffect))
|
|
1441
|
+
value = value.update({ filter: from => from != effect.value });
|
|
1442
|
+
}
|
|
1443
|
+
return value;
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
/**
|
|
1447
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
1448
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
1449
|
+
after the cursor. When closing a bracket directly in front of a
|
|
1450
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
1451
|
+
that bracket.
|
|
1452
|
+
*/
|
|
1453
|
+
function closeBrackets() {
|
|
1454
|
+
return [inputHandler, bracketState];
|
|
1455
|
+
}
|
|
1456
|
+
const definedClosing = "()[]{}<>";
|
|
1457
|
+
function closing(ch) {
|
|
1458
|
+
for (let i = 0; i < definedClosing.length; i += 2)
|
|
1459
|
+
if (definedClosing.charCodeAt(i) == ch)
|
|
1460
|
+
return definedClosing.charAt(i + 1);
|
|
1461
|
+
return fromCodePoint(ch < 128 ? ch : ch + 1);
|
|
1462
|
+
}
|
|
1463
|
+
function config(state, pos) {
|
|
1464
|
+
return state.languageDataAt("closeBrackets", pos)[0] || defaults;
|
|
1465
|
+
}
|
|
1466
|
+
const android = typeof navigator == "object" && /*@__PURE__*//Android\b/.test(navigator.userAgent);
|
|
1467
|
+
const inputHandler = /*@__PURE__*/EditorView.inputHandler.of((view, from, to, insert) => {
|
|
1468
|
+
if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
|
|
1469
|
+
return false;
|
|
1470
|
+
let sel = view.state.selection.main;
|
|
1471
|
+
if (insert.length > 2 || insert.length == 2 && codePointSize(codePointAt(insert, 0)) == 1 ||
|
|
1472
|
+
from != sel.from || to != sel.to)
|
|
1473
|
+
return false;
|
|
1474
|
+
let tr = insertBracket(view.state, insert);
|
|
1475
|
+
if (!tr)
|
|
1476
|
+
return false;
|
|
1477
|
+
view.dispatch(tr);
|
|
1478
|
+
return true;
|
|
1479
|
+
});
|
|
1480
|
+
/**
|
|
1481
|
+
Command that implements deleting a pair of matching brackets when
|
|
1482
|
+
the cursor is between them.
|
|
1483
|
+
*/
|
|
1484
|
+
const deleteBracketPair = ({ state, dispatch }) => {
|
|
1485
|
+
if (state.readOnly)
|
|
1486
|
+
return false;
|
|
1487
|
+
let conf = config(state, state.selection.main.head);
|
|
1488
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1489
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1490
|
+
if (range.empty) {
|
|
1491
|
+
let before = prevChar(state.doc, range.head);
|
|
1492
|
+
for (let token of tokens) {
|
|
1493
|
+
if (token == before && nextChar(state.doc, range.head) == closing(codePointAt(token, 0)))
|
|
1494
|
+
return { changes: { from: range.head - token.length, to: range.head + token.length },
|
|
1495
|
+
range: EditorSelection.cursor(range.head - token.length),
|
|
1496
|
+
userEvent: "delete.backward" };
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return { range: dont = range };
|
|
1500
|
+
});
|
|
1501
|
+
if (!dont)
|
|
1502
|
+
dispatch(state.update(changes, { scrollIntoView: true }));
|
|
1503
|
+
return !dont;
|
|
1504
|
+
};
|
|
1505
|
+
/**
|
|
1506
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
1507
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
1508
|
+
*/
|
|
1509
|
+
const closeBracketsKeymap = [
|
|
1510
|
+
{ key: "Backspace", run: deleteBracketPair }
|
|
1511
|
+
];
|
|
1512
|
+
/**
|
|
1513
|
+
Implements the extension's behavior on text insertion. If the
|
|
1514
|
+
given string counts as a bracket in the language around the
|
|
1515
|
+
selection, and replacing the selection with it requires custom
|
|
1516
|
+
behavior (inserting a closing version or skipping past a
|
|
1517
|
+
previously-closed bracket), this function returns a transaction
|
|
1518
|
+
representing that custom behavior. (You only need this if you want
|
|
1519
|
+
to programmatically insert brackets—the
|
|
1520
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
1521
|
+
take care of running this for user input.)
|
|
1522
|
+
*/
|
|
1523
|
+
function insertBracket(state, bracket) {
|
|
1524
|
+
let conf = config(state, state.selection.main.head);
|
|
1525
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1526
|
+
for (let tok of tokens) {
|
|
1527
|
+
let closed = closing(codePointAt(tok, 0));
|
|
1528
|
+
if (bracket == tok)
|
|
1529
|
+
return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1)
|
|
1530
|
+
: handleOpen(state, tok, closed, conf.before || defaults.before);
|
|
1531
|
+
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
|
|
1532
|
+
return handleClose(state, tok, closed);
|
|
1533
|
+
}
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
function closedBracketAt(state, pos) {
|
|
1537
|
+
let found = false;
|
|
1538
|
+
state.field(bracketState).between(0, state.doc.length, from => {
|
|
1539
|
+
if (from == pos)
|
|
1540
|
+
found = true;
|
|
1541
|
+
});
|
|
1542
|
+
return found;
|
|
1543
|
+
}
|
|
1544
|
+
function nextChar(doc, pos) {
|
|
1545
|
+
let next = doc.sliceString(pos, pos + 2);
|
|
1546
|
+
return next.slice(0, codePointSize(codePointAt(next, 0)));
|
|
1547
|
+
}
|
|
1548
|
+
function prevChar(doc, pos) {
|
|
1549
|
+
let prev = doc.sliceString(pos - 2, pos);
|
|
1550
|
+
return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
|
1551
|
+
}
|
|
1552
|
+
function handleOpen(state, open, close, closeBefore) {
|
|
1553
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1554
|
+
if (!range.empty)
|
|
1555
|
+
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
|
1556
|
+
effects: closeBracketEffect.of(range.to + open.length),
|
|
1557
|
+
range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
|
1558
|
+
let next = nextChar(state.doc, range.head);
|
|
1559
|
+
if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
|
1560
|
+
return { changes: { insert: open + close, from: range.head },
|
|
1561
|
+
effects: closeBracketEffect.of(range.head + open.length),
|
|
1562
|
+
range: EditorSelection.cursor(range.head + open.length) };
|
|
1563
|
+
return { range: dont = range };
|
|
1564
|
+
});
|
|
1565
|
+
return dont ? null : state.update(changes, {
|
|
1566
|
+
scrollIntoView: true,
|
|
1567
|
+
userEvent: "input.type"
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
function handleClose(state, _open, close) {
|
|
1571
|
+
let dont = null, moved = state.selection.ranges.map(range => {
|
|
1572
|
+
if (range.empty && nextChar(state.doc, range.head) == close)
|
|
1573
|
+
return EditorSelection.cursor(range.head + close.length);
|
|
1574
|
+
return dont = range;
|
|
1575
|
+
});
|
|
1576
|
+
return dont ? null : state.update({
|
|
1577
|
+
selection: EditorSelection.create(moved, state.selection.mainIndex),
|
|
1578
|
+
scrollIntoView: true,
|
|
1579
|
+
effects: state.selection.ranges.map(({ from }) => skipBracketEffect.of(from))
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
// Handles cases where the open and close token are the same, and
|
|
1583
|
+
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
|
1584
|
+
function handleSame(state, token, allowTriple) {
|
|
1585
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1586
|
+
if (!range.empty)
|
|
1587
|
+
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
|
1588
|
+
effects: closeBracketEffect.of(range.to + token.length),
|
|
1589
|
+
range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
|
1590
|
+
let pos = range.head, next = nextChar(state.doc, pos);
|
|
1591
|
+
if (next == token) {
|
|
1592
|
+
if (nodeStart(state, pos)) {
|
|
1593
|
+
return { changes: { insert: token + token, from: pos },
|
|
1594
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1595
|
+
range: EditorSelection.cursor(pos + token.length) };
|
|
1596
|
+
}
|
|
1597
|
+
else if (closedBracketAt(state, pos)) {
|
|
1598
|
+
let isTriple = allowTriple && state.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1599
|
+
return { range: EditorSelection.cursor(pos + token.length * (isTriple ? 3 : 1)),
|
|
1600
|
+
effects: skipBracketEffect.of(pos) };
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
|
1604
|
+
nodeStart(state, pos - 2 * token.length)) {
|
|
1605
|
+
return { changes: { insert: token + token + token + token, from: pos },
|
|
1606
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1607
|
+
range: EditorSelection.cursor(pos + token.length) };
|
|
1608
|
+
}
|
|
1609
|
+
else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
|
|
1610
|
+
let prev = state.sliceDoc(pos - 1, pos);
|
|
1611
|
+
if (prev != token && state.charCategorizer(pos)(prev) != CharCategory.Word && !probablyInString(state, pos, token))
|
|
1612
|
+
return { changes: { insert: token + token, from: pos },
|
|
1613
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1614
|
+
range: EditorSelection.cursor(pos + token.length) };
|
|
1615
|
+
}
|
|
1616
|
+
return { range: dont = range };
|
|
1617
|
+
});
|
|
1618
|
+
return dont ? null : state.update(changes, {
|
|
1619
|
+
scrollIntoView: true,
|
|
1620
|
+
userEvent: "input.type"
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
function nodeStart(state, pos) {
|
|
1624
|
+
let tree = syntaxTree(state).resolveInner(pos + 1);
|
|
1625
|
+
return tree.parent && tree.from == pos;
|
|
1626
|
+
}
|
|
1627
|
+
function probablyInString(state, pos, quoteToken) {
|
|
1628
|
+
let node = syntaxTree(state).resolveInner(pos, -1);
|
|
1629
|
+
for (let i = 0; i < 5; i++) {
|
|
1630
|
+
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
|
|
1631
|
+
return true;
|
|
1632
|
+
let parent = node.to == pos && node.parent;
|
|
1633
|
+
if (!parent)
|
|
1634
|
+
break;
|
|
1635
|
+
node = parent;
|
|
1636
|
+
}
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1391
1639
|
|
|
1392
1640
|
/**
|
|
1393
1641
|
Returns an extension that enables autocompletion.
|
|
@@ -1472,4 +1720,4 @@ function setSelectedCompletion(index) {
|
|
|
1472
1720
|
return setSelectedEffect.of(index);
|
|
1473
1721
|
}
|
|
1474
1722
|
|
|
1475
|
-
export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
|
1723
|
+
export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemirror/autocomplete",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Autocompletion for the CodeMirror code editor",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "cm-runtests",
|
|
@@ -26,12 +26,10 @@
|
|
|
26
26
|
"sideEffects": false,
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@codemirror/language": "^0.
|
|
30
|
-
"@codemirror/state": "^0.
|
|
31
|
-
"@codemirror/
|
|
32
|
-
"@
|
|
33
|
-
"@codemirror/view": "^0.19.0",
|
|
34
|
-
"@lezer/common": "^0.15.0"
|
|
29
|
+
"@codemirror/language": "^0.20.0",
|
|
30
|
+
"@codemirror/state": "^0.20.0",
|
|
31
|
+
"@codemirror/view": "^0.20.0",
|
|
32
|
+
"@lezer/common": "^0.16.0"
|
|
35
33
|
},
|
|
36
34
|
"devDependencies": {
|
|
37
35
|
"@codemirror/buildhelper": "^0.1.5"
|