@codemirror/autocomplete 0.19.15 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/index.cjs +319 -50
- package/dist/index.d.ts +93 -15
- package/dist/index.js +298 -34
- package/package.json +5 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
## 0.20.2 (2022-05-24)
|
|
2
|
+
|
|
3
|
+
### New features
|
|
4
|
+
|
|
5
|
+
The package now exports an `insertCompletionText` helper that implements the default behavior for applying a completion.
|
|
6
|
+
|
|
7
|
+
## 0.20.1 (2022-05-16)
|
|
8
|
+
|
|
9
|
+
### New features
|
|
10
|
+
|
|
11
|
+
The new `closeOnBlur` option determines whether the completion tooltip is closed when the editor loses focus.
|
|
12
|
+
|
|
13
|
+
`CompletionResult` objects with `filter: false` may now have a `getMatch` property that determines the matched range in the options.
|
|
14
|
+
|
|
15
|
+
## 0.20.0 (2022-04-20)
|
|
16
|
+
|
|
17
|
+
### Breaking changes
|
|
18
|
+
|
|
19
|
+
`CompletionResult.span` has been renamed to `validFor`, and may now hold a function as well as a regular expression.
|
|
20
|
+
|
|
21
|
+
### Bug fixes
|
|
22
|
+
|
|
23
|
+
Remove code that dropped any options beyond the 300th one when matching and sorting option lists.
|
|
24
|
+
|
|
25
|
+
Completion will now apply to all cursors when there are multiple cursors.
|
|
26
|
+
|
|
27
|
+
### New features
|
|
28
|
+
|
|
29
|
+
`CompletionResult.update` can now be used to implement quick autocompletion updates in a synchronous way.
|
|
30
|
+
|
|
31
|
+
The @codemirror/closebrackets package was merged into this one.
|
|
32
|
+
|
|
1
33
|
## 0.19.15 (2022-03-23)
|
|
2
34
|
|
|
3
35
|
### 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
|
/**
|
|
@@ -155,20 +153,35 @@ This annotation is added to transactions that are produced by
|
|
|
155
153
|
picking a completion.
|
|
156
154
|
*/
|
|
157
155
|
const pickedCompletion = state.Annotation.define();
|
|
156
|
+
/**
|
|
157
|
+
Helper function that returns a transaction spec which inserts a
|
|
158
|
+
completion's text in the main selection range, and any other
|
|
159
|
+
selection range that has the same text in front of it.
|
|
160
|
+
*/
|
|
161
|
+
function insertCompletionText(state$1, text, from, to) {
|
|
162
|
+
return state$1.changeByRange(range => {
|
|
163
|
+
if (range == state$1.selection.main)
|
|
164
|
+
return {
|
|
165
|
+
changes: { from: from, to: to, insert: text },
|
|
166
|
+
range: state.EditorSelection.cursor(from + text.length)
|
|
167
|
+
};
|
|
168
|
+
let len = to - from;
|
|
169
|
+
if (!range.empty ||
|
|
170
|
+
len && state$1.sliceDoc(range.from - len, range.from) != state$1.sliceDoc(from, to))
|
|
171
|
+
return { range };
|
|
172
|
+
return {
|
|
173
|
+
changes: { from: range.from - len, to: range.from, insert: text },
|
|
174
|
+
range: state.EditorSelection.cursor(range.from - len + text.length)
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}
|
|
158
178
|
function applyCompletion(view, option) {
|
|
159
|
-
|
|
179
|
+
const apply = option.completion.apply || option.completion.label;
|
|
160
180
|
let result = option.source;
|
|
161
|
-
if (typeof apply == "string")
|
|
162
|
-
view.dispatch(
|
|
163
|
-
|
|
164
|
-
selection: { anchor: result.from + apply.length },
|
|
165
|
-
userEvent: "input.complete",
|
|
166
|
-
annotations: pickedCompletion.of(option.completion)
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
181
|
+
if (typeof apply == "string")
|
|
182
|
+
view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
|
|
183
|
+
else
|
|
170
184
|
apply(view, option.completion, result.from, result.to);
|
|
171
|
-
}
|
|
172
185
|
}
|
|
173
186
|
const SourceCache = new WeakMap();
|
|
174
187
|
function asSource(source) {
|
|
@@ -194,10 +207,10 @@ class FuzzyMatcher {
|
|
|
194
207
|
this.precise = [];
|
|
195
208
|
this.byWord = [];
|
|
196
209
|
for (let p = 0; p < pattern.length;) {
|
|
197
|
-
let char =
|
|
210
|
+
let char = state.codePointAt(pattern, p), size = state.codePointSize(char);
|
|
198
211
|
this.chars.push(char);
|
|
199
212
|
let part = pattern.slice(p, p + size), upper = part.toUpperCase();
|
|
200
|
-
this.folded.push(
|
|
213
|
+
this.folded.push(state.codePointAt(upper == part ? part.toLowerCase() : upper, 0));
|
|
201
214
|
p += size;
|
|
202
215
|
}
|
|
203
216
|
this.astral = pattern.length != this.chars.length;
|
|
@@ -218,9 +231,9 @@ class FuzzyMatcher {
|
|
|
218
231
|
// For single-character queries, only match when they occur right
|
|
219
232
|
// at the start
|
|
220
233
|
if (chars.length == 1) {
|
|
221
|
-
let first =
|
|
222
|
-
return first == chars[0] ? [0, 0,
|
|
223
|
-
: first == folded[0] ? [-200 /* CaseFold */, 0,
|
|
234
|
+
let first = state.codePointAt(word, 0);
|
|
235
|
+
return first == chars[0] ? [0, 0, state.codePointSize(first)]
|
|
236
|
+
: first == folded[0] ? [-200 /* CaseFold */, 0, state.codePointSize(first)] : null;
|
|
224
237
|
}
|
|
225
238
|
let direct = word.indexOf(this.pattern);
|
|
226
239
|
if (direct == 0)
|
|
@@ -228,10 +241,10 @@ class FuzzyMatcher {
|
|
|
228
241
|
let len = chars.length, anyTo = 0;
|
|
229
242
|
if (direct < 0) {
|
|
230
243
|
for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
|
|
231
|
-
let next =
|
|
244
|
+
let next = state.codePointAt(word, i);
|
|
232
245
|
if (next == chars[anyTo] || next == folded[anyTo])
|
|
233
246
|
any[anyTo++] = i;
|
|
234
|
-
i +=
|
|
247
|
+
i += state.codePointSize(next);
|
|
235
248
|
}
|
|
236
249
|
// No match, exit immediately
|
|
237
250
|
if (anyTo < len)
|
|
@@ -249,7 +262,7 @@ class FuzzyMatcher {
|
|
|
249
262
|
let hasLower = /[a-z]/.test(word), wordAdjacent = true;
|
|
250
263
|
// Go over the option's text, scanning for the various kinds of matches
|
|
251
264
|
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* NonWord */; i < e && byWordTo < len;) {
|
|
252
|
-
let next =
|
|
265
|
+
let next = state.codePointAt(word, i);
|
|
253
266
|
if (direct < 0) {
|
|
254
267
|
if (preciseTo < len && next == chars[preciseTo])
|
|
255
268
|
precise[preciseTo++] = i;
|
|
@@ -267,7 +280,7 @@ class FuzzyMatcher {
|
|
|
267
280
|
}
|
|
268
281
|
let ch, type = next < 0xff
|
|
269
282
|
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Lower */ : next >= 65 && next <= 90 ? 1 /* Upper */ : 0 /* NonWord */)
|
|
270
|
-
: ((ch =
|
|
283
|
+
: ((ch = state.fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Upper */ : ch != ch.toUpperCase() ? 2 /* Lower */ : 0 /* NonWord */);
|
|
271
284
|
if (!i || type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) {
|
|
272
285
|
if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
|
|
273
286
|
byWord[byWordTo++] = i;
|
|
@@ -275,7 +288,7 @@ class FuzzyMatcher {
|
|
|
275
288
|
wordAdjacent = false;
|
|
276
289
|
}
|
|
277
290
|
prevType = type;
|
|
278
|
-
i +=
|
|
291
|
+
i += state.codePointSize(next);
|
|
279
292
|
}
|
|
280
293
|
if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
|
|
281
294
|
return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
|
|
@@ -293,7 +306,7 @@ class FuzzyMatcher {
|
|
|
293
306
|
result(score, positions, word) {
|
|
294
307
|
let result = [score - word.length], i = 1;
|
|
295
308
|
for (let pos of positions) {
|
|
296
|
-
let to = pos + (this.astral ?
|
|
309
|
+
let to = pos + (this.astral ? state.codePointSize(state.codePointAt(word, pos)) : 1);
|
|
297
310
|
if (i > 1 && result[i - 1] == pos)
|
|
298
311
|
result[i - 1] = to;
|
|
299
312
|
else {
|
|
@@ -310,6 +323,7 @@ const completionConfig = state.Facet.define({
|
|
|
310
323
|
return state.combineConfig(configs, {
|
|
311
324
|
activateOnTyping: true,
|
|
312
325
|
override: null,
|
|
326
|
+
closeOnBlur: true,
|
|
313
327
|
maxRenderedOptions: 100,
|
|
314
328
|
defaultKeymap: true,
|
|
315
329
|
optionClass: () => "",
|
|
@@ -318,6 +332,7 @@ const completionConfig = state.Facet.define({
|
|
|
318
332
|
addToOptions: []
|
|
319
333
|
}, {
|
|
320
334
|
defaultKeymap: (a, b) => a && b,
|
|
335
|
+
closeOnBlur: (a, b) => a && b,
|
|
321
336
|
icons: (a, b) => a && b,
|
|
322
337
|
optionClass: (a, b) => c => joinClass(a(c), b(c)),
|
|
323
338
|
addToOptions: (a, b) => a.concat(b)
|
|
@@ -552,7 +567,6 @@ function scrollIntoView(container, element) {
|
|
|
552
567
|
container.scrollTop += self.bottom - parent.bottom;
|
|
553
568
|
}
|
|
554
569
|
|
|
555
|
-
const MaxOptions = 300;
|
|
556
570
|
// Used to pick a preferred option when two options with the same
|
|
557
571
|
// label occur in the result.
|
|
558
572
|
function score(option) {
|
|
@@ -564,8 +578,14 @@ function sortOptions(active, state) {
|
|
|
564
578
|
for (let a of active)
|
|
565
579
|
if (a.hasResult()) {
|
|
566
580
|
if (a.result.filter === false) {
|
|
567
|
-
|
|
568
|
-
|
|
581
|
+
let getMatch = a.result.getMatch;
|
|
582
|
+
for (let option of a.result.options) {
|
|
583
|
+
let match = [1e9 - i++];
|
|
584
|
+
if (getMatch)
|
|
585
|
+
for (let n of getMatch(option))
|
|
586
|
+
match.push(n);
|
|
587
|
+
options.push(new Option(option, a, match));
|
|
588
|
+
}
|
|
569
589
|
}
|
|
570
590
|
else {
|
|
571
591
|
let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
|
|
@@ -579,10 +599,9 @@ function sortOptions(active, state) {
|
|
|
579
599
|
}
|
|
580
600
|
let result = [], prev = null;
|
|
581
601
|
for (let opt of options.sort(cmpOption)) {
|
|
582
|
-
if (result.length == MaxOptions)
|
|
583
|
-
break;
|
|
584
602
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
585
|
-
prev.type != opt.completion.type
|
|
603
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
604
|
+
prev.apply != opt.completion.apply)
|
|
586
605
|
result.push(opt);
|
|
587
606
|
else if (score(opt.completion) > score(prev))
|
|
588
607
|
result[result.length - 1] = opt;
|
|
@@ -732,24 +751,27 @@ class ActiveSource {
|
|
|
732
751
|
}
|
|
733
752
|
}
|
|
734
753
|
class ActiveResult extends ActiveSource {
|
|
735
|
-
constructor(source, explicitPos, result, from, to
|
|
754
|
+
constructor(source, explicitPos, result, from, to) {
|
|
736
755
|
super(source, 2 /* Result */, explicitPos);
|
|
737
756
|
this.result = result;
|
|
738
757
|
this.from = from;
|
|
739
758
|
this.to = to;
|
|
740
|
-
this.span = span;
|
|
741
759
|
}
|
|
742
760
|
hasResult() { return true; }
|
|
743
761
|
handleUserEvent(tr, type, conf) {
|
|
762
|
+
var _a;
|
|
744
763
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
745
764
|
let pos = cur(tr.state);
|
|
746
765
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
747
766
|
pos > to ||
|
|
748
767
|
type == "delete" && cur(tr.startState) == this.from)
|
|
749
768
|
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
|
|
769
|
+
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
|
|
770
|
+
if (checkValid(this.result.validFor, tr.state, from, to))
|
|
771
|
+
return new ActiveResult(this.source, explicitPos, this.result, from, to);
|
|
772
|
+
if (this.result.update &&
|
|
773
|
+
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
|
|
774
|
+
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
|
|
753
775
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
754
776
|
}
|
|
755
777
|
handleChange(tr) {
|
|
@@ -757,9 +779,15 @@ class ActiveResult extends ActiveSource {
|
|
|
757
779
|
}
|
|
758
780
|
map(mapping) {
|
|
759
781
|
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)
|
|
782
|
+
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
|
|
761
783
|
}
|
|
762
784
|
}
|
|
785
|
+
function checkValid(validFor, state, from, to) {
|
|
786
|
+
if (!validFor)
|
|
787
|
+
return false;
|
|
788
|
+
let text = state.sliceDoc(from, to);
|
|
789
|
+
return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
|
|
790
|
+
}
|
|
763
791
|
const startCompletionEffect = state.StateEffect.define();
|
|
764
792
|
const closeCompletionEffect = state.StateEffect.define();
|
|
765
793
|
const setActiveEffect = state.StateEffect.define({
|
|
@@ -770,7 +798,7 @@ const completionState = state.StateField.define({
|
|
|
770
798
|
create() { return CompletionState.start(); },
|
|
771
799
|
update(value, tr) { return value.update(tr); },
|
|
772
800
|
provide: f => [
|
|
773
|
-
|
|
801
|
+
view.showTooltip.from(f, val => val.tooltip),
|
|
774
802
|
view.EditorView.contentAttributes.from(f, state => state.attrs)
|
|
775
803
|
]
|
|
776
804
|
});
|
|
@@ -781,20 +809,20 @@ Returns a command that moves the completion selection forward or
|
|
|
781
809
|
backward by the given amount.
|
|
782
810
|
*/
|
|
783
811
|
function moveCompletionSelection(forward, by = "option") {
|
|
784
|
-
return (view) => {
|
|
785
|
-
let cState = view.state.field(completionState, false);
|
|
812
|
+
return (view$1) => {
|
|
813
|
+
let cState = view$1.state.field(completionState, false);
|
|
786
814
|
if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
|
|
787
815
|
return false;
|
|
788
|
-
let step = 1, tooltip
|
|
789
|
-
if (by == "page" && (tooltip
|
|
790
|
-
step = Math.max(2, Math.floor(tooltip
|
|
791
|
-
tooltip
|
|
816
|
+
let step = 1, tooltip;
|
|
817
|
+
if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
|
|
818
|
+
step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
|
|
819
|
+
tooltip.dom.querySelector("li").offsetHeight) - 1);
|
|
792
820
|
let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
|
|
793
821
|
if (selected < 0)
|
|
794
822
|
selected = by == "page" ? 0 : length - 1;
|
|
795
823
|
else if (selected >= length)
|
|
796
824
|
selected = by == "page" ? length - 1 : 0;
|
|
797
|
-
view.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
825
|
+
view$1.dispatch({ effects: setSelectedEffect.of(selected) });
|
|
798
826
|
return true;
|
|
799
827
|
};
|
|
800
828
|
}
|
|
@@ -933,7 +961,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
933
961
|
continue;
|
|
934
962
|
this.running.splice(i--, 1);
|
|
935
963
|
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)
|
|
964
|
+
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
965
|
// Replay the transactions that happened since the start of
|
|
938
966
|
// the request and see if that preserves the result
|
|
939
967
|
for (let tr of query.updates)
|
|
@@ -965,6 +993,11 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
|
|
|
965
993
|
}
|
|
966
994
|
}, {
|
|
967
995
|
eventHandlers: {
|
|
996
|
+
blur() {
|
|
997
|
+
let state = this.view.state.field(completionState, false);
|
|
998
|
+
if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur)
|
|
999
|
+
this.view.dispatch({ effects: closeCompletionEffect.of(null) });
|
|
1000
|
+
},
|
|
968
1001
|
compositionstart() {
|
|
969
1002
|
this.composing = 1 /* Started */;
|
|
970
1003
|
},
|
|
@@ -1206,8 +1239,9 @@ function fieldSelection(ranges, field) {
|
|
|
1206
1239
|
return state.EditorSelection.create(ranges.filter(r => r.field == field).map(r => state.EditorSelection.range(r.from, r.to)));
|
|
1207
1240
|
}
|
|
1208
1241
|
/**
|
|
1209
|
-
Convert a snippet template to a function that can
|
|
1210
|
-
Snippets are written
|
|
1242
|
+
Convert a snippet template to a function that can
|
|
1243
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
1244
|
+
using syntax like this:
|
|
1211
1245
|
|
|
1212
1246
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1213
1247
|
|
|
@@ -1390,8 +1424,238 @@ const completeAnyWord = context => {
|
|
|
1390
1424
|
return null;
|
|
1391
1425
|
let from = token ? token.from : context.pos;
|
|
1392
1426
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1393
|
-
return { from, options,
|
|
1427
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
const defaults = {
|
|
1431
|
+
brackets: ["(", "[", "{", "'", '"'],
|
|
1432
|
+
before: ")]}:;>"
|
|
1394
1433
|
};
|
|
1434
|
+
const closeBracketEffect = state.StateEffect.define({
|
|
1435
|
+
map(value, mapping) {
|
|
1436
|
+
let mapped = mapping.mapPos(value, -1, state.MapMode.TrackAfter);
|
|
1437
|
+
return mapped == null ? undefined : mapped;
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
const skipBracketEffect = state.StateEffect.define({
|
|
1441
|
+
map(value, mapping) { return mapping.mapPos(value); }
|
|
1442
|
+
});
|
|
1443
|
+
const closedBracket = new class extends state.RangeValue {
|
|
1444
|
+
};
|
|
1445
|
+
closedBracket.startSide = 1;
|
|
1446
|
+
closedBracket.endSide = -1;
|
|
1447
|
+
const bracketState = state.StateField.define({
|
|
1448
|
+
create() { return state.RangeSet.empty; },
|
|
1449
|
+
update(value, tr) {
|
|
1450
|
+
if (tr.selection) {
|
|
1451
|
+
let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from;
|
|
1452
|
+
let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from;
|
|
1453
|
+
if (lineStart != tr.changes.mapPos(prevLineStart, -1))
|
|
1454
|
+
value = state.RangeSet.empty;
|
|
1455
|
+
}
|
|
1456
|
+
value = value.map(tr.changes);
|
|
1457
|
+
for (let effect of tr.effects) {
|
|
1458
|
+
if (effect.is(closeBracketEffect))
|
|
1459
|
+
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1460
|
+
else if (effect.is(skipBracketEffect))
|
|
1461
|
+
value = value.update({ filter: from => from != effect.value });
|
|
1462
|
+
}
|
|
1463
|
+
return value;
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
/**
|
|
1467
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
1468
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
1469
|
+
after the cursor. When closing a bracket directly in front of a
|
|
1470
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
1471
|
+
that bracket.
|
|
1472
|
+
*/
|
|
1473
|
+
function closeBrackets() {
|
|
1474
|
+
return [inputHandler, bracketState];
|
|
1475
|
+
}
|
|
1476
|
+
const definedClosing = "()[]{}<>";
|
|
1477
|
+
function closing(ch) {
|
|
1478
|
+
for (let i = 0; i < definedClosing.length; i += 2)
|
|
1479
|
+
if (definedClosing.charCodeAt(i) == ch)
|
|
1480
|
+
return definedClosing.charAt(i + 1);
|
|
1481
|
+
return state.fromCodePoint(ch < 128 ? ch : ch + 1);
|
|
1482
|
+
}
|
|
1483
|
+
function config(state, pos) {
|
|
1484
|
+
return state.languageDataAt("closeBrackets", pos)[0] || defaults;
|
|
1485
|
+
}
|
|
1486
|
+
const android = typeof navigator == "object" && /Android\b/.test(navigator.userAgent);
|
|
1487
|
+
const inputHandler = view.EditorView.inputHandler.of((view, from, to, insert) => {
|
|
1488
|
+
if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
|
|
1489
|
+
return false;
|
|
1490
|
+
let sel = view.state.selection.main;
|
|
1491
|
+
if (insert.length > 2 || insert.length == 2 && state.codePointSize(state.codePointAt(insert, 0)) == 1 ||
|
|
1492
|
+
from != sel.from || to != sel.to)
|
|
1493
|
+
return false;
|
|
1494
|
+
let tr = insertBracket(view.state, insert);
|
|
1495
|
+
if (!tr)
|
|
1496
|
+
return false;
|
|
1497
|
+
view.dispatch(tr);
|
|
1498
|
+
return true;
|
|
1499
|
+
});
|
|
1500
|
+
/**
|
|
1501
|
+
Command that implements deleting a pair of matching brackets when
|
|
1502
|
+
the cursor is between them.
|
|
1503
|
+
*/
|
|
1504
|
+
const deleteBracketPair = ({ state: state$1, dispatch }) => {
|
|
1505
|
+
if (state$1.readOnly)
|
|
1506
|
+
return false;
|
|
1507
|
+
let conf = config(state$1, state$1.selection.main.head);
|
|
1508
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1509
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1510
|
+
if (range.empty) {
|
|
1511
|
+
let before = prevChar(state$1.doc, range.head);
|
|
1512
|
+
for (let token of tokens) {
|
|
1513
|
+
if (token == before && nextChar(state$1.doc, range.head) == closing(state.codePointAt(token, 0)))
|
|
1514
|
+
return { changes: { from: range.head - token.length, to: range.head + token.length },
|
|
1515
|
+
range: state.EditorSelection.cursor(range.head - token.length),
|
|
1516
|
+
userEvent: "delete.backward" };
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return { range: dont = range };
|
|
1520
|
+
});
|
|
1521
|
+
if (!dont)
|
|
1522
|
+
dispatch(state$1.update(changes, { scrollIntoView: true }));
|
|
1523
|
+
return !dont;
|
|
1524
|
+
};
|
|
1525
|
+
/**
|
|
1526
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
1527
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
1528
|
+
*/
|
|
1529
|
+
const closeBracketsKeymap = [
|
|
1530
|
+
{ key: "Backspace", run: deleteBracketPair }
|
|
1531
|
+
];
|
|
1532
|
+
/**
|
|
1533
|
+
Implements the extension's behavior on text insertion. If the
|
|
1534
|
+
given string counts as a bracket in the language around the
|
|
1535
|
+
selection, and replacing the selection with it requires custom
|
|
1536
|
+
behavior (inserting a closing version or skipping past a
|
|
1537
|
+
previously-closed bracket), this function returns a transaction
|
|
1538
|
+
representing that custom behavior. (You only need this if you want
|
|
1539
|
+
to programmatically insert brackets—the
|
|
1540
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
1541
|
+
take care of running this for user input.)
|
|
1542
|
+
*/
|
|
1543
|
+
function insertBracket(state$1, bracket) {
|
|
1544
|
+
let conf = config(state$1, state$1.selection.main.head);
|
|
1545
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1546
|
+
for (let tok of tokens) {
|
|
1547
|
+
let closed = closing(state.codePointAt(tok, 0));
|
|
1548
|
+
if (bracket == tok)
|
|
1549
|
+
return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
|
|
1550
|
+
: handleOpen(state$1, tok, closed, conf.before || defaults.before);
|
|
1551
|
+
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
|
|
1552
|
+
return handleClose(state$1, tok, closed);
|
|
1553
|
+
}
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
function closedBracketAt(state, pos) {
|
|
1557
|
+
let found = false;
|
|
1558
|
+
state.field(bracketState).between(0, state.doc.length, from => {
|
|
1559
|
+
if (from == pos)
|
|
1560
|
+
found = true;
|
|
1561
|
+
});
|
|
1562
|
+
return found;
|
|
1563
|
+
}
|
|
1564
|
+
function nextChar(doc, pos) {
|
|
1565
|
+
let next = doc.sliceString(pos, pos + 2);
|
|
1566
|
+
return next.slice(0, state.codePointSize(state.codePointAt(next, 0)));
|
|
1567
|
+
}
|
|
1568
|
+
function prevChar(doc, pos) {
|
|
1569
|
+
let prev = doc.sliceString(pos - 2, pos);
|
|
1570
|
+
return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
|
1571
|
+
}
|
|
1572
|
+
function handleOpen(state$1, open, close, closeBefore) {
|
|
1573
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1574
|
+
if (!range.empty)
|
|
1575
|
+
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
|
1576
|
+
effects: closeBracketEffect.of(range.to + open.length),
|
|
1577
|
+
range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
|
1578
|
+
let next = nextChar(state$1.doc, range.head);
|
|
1579
|
+
if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
|
1580
|
+
return { changes: { insert: open + close, from: range.head },
|
|
1581
|
+
effects: closeBracketEffect.of(range.head + open.length),
|
|
1582
|
+
range: state.EditorSelection.cursor(range.head + open.length) };
|
|
1583
|
+
return { range: dont = range };
|
|
1584
|
+
});
|
|
1585
|
+
return dont ? null : state$1.update(changes, {
|
|
1586
|
+
scrollIntoView: true,
|
|
1587
|
+
userEvent: "input.type"
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
function handleClose(state$1, _open, close) {
|
|
1591
|
+
let dont = null, moved = state$1.selection.ranges.map(range => {
|
|
1592
|
+
if (range.empty && nextChar(state$1.doc, range.head) == close)
|
|
1593
|
+
return state.EditorSelection.cursor(range.head + close.length);
|
|
1594
|
+
return dont = range;
|
|
1595
|
+
});
|
|
1596
|
+
return dont ? null : state$1.update({
|
|
1597
|
+
selection: state.EditorSelection.create(moved, state$1.selection.mainIndex),
|
|
1598
|
+
scrollIntoView: true,
|
|
1599
|
+
effects: state$1.selection.ranges.map(({ from }) => skipBracketEffect.of(from))
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
// Handles cases where the open and close token are the same, and
|
|
1603
|
+
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
|
1604
|
+
function handleSame(state$1, token, allowTriple) {
|
|
1605
|
+
let dont = null, changes = state$1.changeByRange(range => {
|
|
1606
|
+
if (!range.empty)
|
|
1607
|
+
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
|
1608
|
+
effects: closeBracketEffect.of(range.to + token.length),
|
|
1609
|
+
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
|
1610
|
+
let pos = range.head, next = nextChar(state$1.doc, pos);
|
|
1611
|
+
if (next == token) {
|
|
1612
|
+
if (nodeStart(state$1, pos)) {
|
|
1613
|
+
return { changes: { insert: token + token, from: pos },
|
|
1614
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1615
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1616
|
+
}
|
|
1617
|
+
else if (closedBracketAt(state$1, pos)) {
|
|
1618
|
+
let isTriple = allowTriple && state$1.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1619
|
+
return { range: state.EditorSelection.cursor(pos + token.length * (isTriple ? 3 : 1)),
|
|
1620
|
+
effects: skipBracketEffect.of(pos) };
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
|
1624
|
+
nodeStart(state$1, pos - 2 * token.length)) {
|
|
1625
|
+
return { changes: { insert: token + token + token + token, from: pos },
|
|
1626
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1627
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1628
|
+
}
|
|
1629
|
+
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
|
|
1630
|
+
let prev = state$1.sliceDoc(pos - 1, pos);
|
|
1631
|
+
if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
|
|
1632
|
+
return { changes: { insert: token + token, from: pos },
|
|
1633
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1634
|
+
range: state.EditorSelection.cursor(pos + token.length) };
|
|
1635
|
+
}
|
|
1636
|
+
return { range: dont = range };
|
|
1637
|
+
});
|
|
1638
|
+
return dont ? null : state$1.update(changes, {
|
|
1639
|
+
scrollIntoView: true,
|
|
1640
|
+
userEvent: "input.type"
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
function nodeStart(state, pos) {
|
|
1644
|
+
let tree = language.syntaxTree(state).resolveInner(pos + 1);
|
|
1645
|
+
return tree.parent && tree.from == pos;
|
|
1646
|
+
}
|
|
1647
|
+
function probablyInString(state, pos, quoteToken) {
|
|
1648
|
+
let node = language.syntaxTree(state).resolveInner(pos, -1);
|
|
1649
|
+
for (let i = 0; i < 5; i++) {
|
|
1650
|
+
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
|
|
1651
|
+
return true;
|
|
1652
|
+
let parent = node.to == pos && node.parent;
|
|
1653
|
+
if (!parent)
|
|
1654
|
+
break;
|
|
1655
|
+
node = parent;
|
|
1656
|
+
}
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1395
1659
|
|
|
1396
1660
|
/**
|
|
1397
1661
|
Returns an extension that enables autocompletion.
|
|
@@ -1480,14 +1744,19 @@ exports.CompletionContext = CompletionContext;
|
|
|
1480
1744
|
exports.acceptCompletion = acceptCompletion;
|
|
1481
1745
|
exports.autocompletion = autocompletion;
|
|
1482
1746
|
exports.clearSnippet = clearSnippet;
|
|
1747
|
+
exports.closeBrackets = closeBrackets;
|
|
1748
|
+
exports.closeBracketsKeymap = closeBracketsKeymap;
|
|
1483
1749
|
exports.closeCompletion = closeCompletion;
|
|
1484
1750
|
exports.completeAnyWord = completeAnyWord;
|
|
1485
1751
|
exports.completeFromList = completeFromList;
|
|
1486
1752
|
exports.completionKeymap = completionKeymap;
|
|
1487
1753
|
exports.completionStatus = completionStatus;
|
|
1488
1754
|
exports.currentCompletions = currentCompletions;
|
|
1755
|
+
exports.deleteBracketPair = deleteBracketPair;
|
|
1489
1756
|
exports.ifIn = ifIn;
|
|
1490
1757
|
exports.ifNotIn = ifNotIn;
|
|
1758
|
+
exports.insertBracket = insertBracket;
|
|
1759
|
+
exports.insertCompletionText = insertCompletionText;
|
|
1491
1760
|
exports.moveCompletionSelection = moveCompletionSelection;
|
|
1492
1761
|
exports.nextSnippetField = nextSnippetField;
|
|
1493
1762
|
exports.pickedCompletion = pickedCompletion;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _codemirror_state from '@codemirror/state';
|
|
2
|
-
import { EditorState, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
|
|
2
|
+
import { EditorState, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
|
|
3
3
|
import { EditorView, KeyBinding, Command } from '@codemirror/view';
|
|
4
4
|
import * as _lezer_common from '@lezer/common';
|
|
5
5
|
|
|
@@ -18,6 +18,11 @@ interface CompletionConfig {
|
|
|
18
18
|
*/
|
|
19
19
|
override?: readonly CompletionSource[] | null;
|
|
20
20
|
/**
|
|
21
|
+
Determines whether the completion tooltip is closed when the
|
|
22
|
+
editor loses focus. Defaults to true.
|
|
23
|
+
*/
|
|
24
|
+
closeOnBlur?: boolean;
|
|
25
|
+
/**
|
|
21
26
|
The maximum number of options to render to the DOM.
|
|
22
27
|
*/
|
|
23
28
|
maxRenderedOptions?: number;
|
|
@@ -52,8 +57,7 @@ interface CompletionConfig {
|
|
|
52
57
|
completion, and should produce a DOM node to show. `position`
|
|
53
58
|
determines where in the DOM the result appears, relative to
|
|
54
59
|
other added widgets and the standard content. The default icons
|
|
55
|
-
have position 20, the label position 50, and the detail position
|
|
56
|
-
70.
|
|
60
|
+
have position 20, the label position 50, and the detail position 70.
|
|
57
61
|
*/
|
|
58
62
|
addToOptions?: {
|
|
59
63
|
render: (completion: Completion, state: EditorState) => Node | null;
|
|
@@ -226,33 +230,57 @@ interface CompletionResult {
|
|
|
226
230
|
*/
|
|
227
231
|
options: readonly Completion[];
|
|
228
232
|
/**
|
|
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.
|
|
233
|
+
When given, further typing or deletion that causes the part of
|
|
234
|
+
the document between ([mapped](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) `from`
|
|
235
|
+
and `to` to match this regular expression or predicate function
|
|
236
|
+
will not query the completion source again, but continue with
|
|
237
|
+
this list of options. This can help a lot with responsiveness,
|
|
238
|
+
since it allows the completion list to be updated synchronously.
|
|
235
239
|
*/
|
|
236
|
-
|
|
240
|
+
validFor?: RegExp | ((text: string, from: number, to: number, state: EditorState) => boolean);
|
|
237
241
|
/**
|
|
238
242
|
By default, the library filters and scores completions. Set
|
|
239
243
|
`filter` to `false` to disable this, and cause your completions
|
|
240
244
|
to all be included, in the order they were given. When there are
|
|
241
245
|
other sources, unfiltered completions appear at the top of the
|
|
242
|
-
list of completions. `
|
|
243
|
-
`false`, because it only works when filtering.
|
|
246
|
+
list of completions. `validFor` must not be given when `filter`
|
|
247
|
+
is `false`, because it only works when filtering.
|
|
244
248
|
*/
|
|
245
249
|
filter?: boolean;
|
|
250
|
+
/**
|
|
251
|
+
When [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) is set to
|
|
252
|
+
`false`, this may be provided to compute the ranges on the label
|
|
253
|
+
that match the input. Should return an array of numbers where
|
|
254
|
+
each pair of adjacent numbers provide the start and end of a
|
|
255
|
+
range.
|
|
256
|
+
*/
|
|
257
|
+
getMatch?: (completion: Completion) => readonly number[];
|
|
258
|
+
/**
|
|
259
|
+
Synchronously update the completion result after typing or
|
|
260
|
+
deletion. If given, this should not do any expensive work, since
|
|
261
|
+
it will be called during editor state updates. The function
|
|
262
|
+
should make sure (similar to
|
|
263
|
+
[`validFor`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.validFor)) that the
|
|
264
|
+
completion still applies in the new state.
|
|
265
|
+
*/
|
|
266
|
+
update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null;
|
|
246
267
|
}
|
|
247
268
|
/**
|
|
248
269
|
This annotation is added to transactions that are produced by
|
|
249
270
|
picking a completion.
|
|
250
271
|
*/
|
|
251
272
|
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
|
|
273
|
+
/**
|
|
274
|
+
Helper function that returns a transaction spec which inserts a
|
|
275
|
+
completion's text in the main selection range, and any other
|
|
276
|
+
selection range that has the same text in front of it.
|
|
277
|
+
*/
|
|
278
|
+
declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
|
|
252
279
|
|
|
253
280
|
/**
|
|
254
|
-
Convert a snippet template to a function that can
|
|
255
|
-
Snippets are written
|
|
281
|
+
Convert a snippet template to a function that can
|
|
282
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
283
|
+
using syntax like this:
|
|
256
284
|
|
|
257
285
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
258
286
|
|
|
@@ -331,6 +359,56 @@ return those as completions.
|
|
|
331
359
|
*/
|
|
332
360
|
declare const completeAnyWord: CompletionSource;
|
|
333
361
|
|
|
362
|
+
/**
|
|
363
|
+
Configures bracket closing behavior for a syntax (via
|
|
364
|
+
[language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt)) using the `"closeBrackets"`
|
|
365
|
+
identifier.
|
|
366
|
+
*/
|
|
367
|
+
interface CloseBracketConfig {
|
|
368
|
+
/**
|
|
369
|
+
The opening brackets to close. Defaults to `["(", "[", "{", "'",
|
|
370
|
+
'"']`. Brackets may be single characters or a triple of quotes
|
|
371
|
+
(as in `"''''"`).
|
|
372
|
+
*/
|
|
373
|
+
brackets?: string[];
|
|
374
|
+
/**
|
|
375
|
+
Characters in front of which newly opened brackets are
|
|
376
|
+
automatically closed. Closing always happens in front of
|
|
377
|
+
whitespace. Defaults to `")]}:;>"`.
|
|
378
|
+
*/
|
|
379
|
+
before?: string;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
383
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
384
|
+
after the cursor. When closing a bracket directly in front of a
|
|
385
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
386
|
+
that bracket.
|
|
387
|
+
*/
|
|
388
|
+
declare function closeBrackets(): Extension;
|
|
389
|
+
/**
|
|
390
|
+
Command that implements deleting a pair of matching brackets when
|
|
391
|
+
the cursor is between them.
|
|
392
|
+
*/
|
|
393
|
+
declare const deleteBracketPair: StateCommand;
|
|
394
|
+
/**
|
|
395
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
396
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
397
|
+
*/
|
|
398
|
+
declare const closeBracketsKeymap: readonly KeyBinding[];
|
|
399
|
+
/**
|
|
400
|
+
Implements the extension's behavior on text insertion. If the
|
|
401
|
+
given string counts as a bracket in the language around the
|
|
402
|
+
selection, and replacing the selection with it requires custom
|
|
403
|
+
behavior (inserting a closing version or skipping past a
|
|
404
|
+
previously-closed bracket), this function returns a transaction
|
|
405
|
+
representing that custom behavior. (You only need this if you want
|
|
406
|
+
to programmatically insert brackets—the
|
|
407
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
408
|
+
take care of running this for user input.)
|
|
409
|
+
*/
|
|
410
|
+
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
|
|
411
|
+
|
|
334
412
|
/**
|
|
335
413
|
Returns an extension that enables autocompletion.
|
|
336
414
|
*/
|
|
@@ -373,4 +451,4 @@ the currently selected completion.
|
|
|
373
451
|
*/
|
|
374
452
|
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
|
|
375
453
|
|
|
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 };
|
|
454
|
+
export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
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
|
/**
|
|
@@ -151,20 +149,35 @@ This annotation is added to transactions that are produced by
|
|
|
151
149
|
picking a completion.
|
|
152
150
|
*/
|
|
153
151
|
const pickedCompletion = /*@__PURE__*/Annotation.define();
|
|
152
|
+
/**
|
|
153
|
+
Helper function that returns a transaction spec which inserts a
|
|
154
|
+
completion's text in the main selection range, and any other
|
|
155
|
+
selection range that has the same text in front of it.
|
|
156
|
+
*/
|
|
157
|
+
function insertCompletionText(state, text, from, to) {
|
|
158
|
+
return state.changeByRange(range => {
|
|
159
|
+
if (range == state.selection.main)
|
|
160
|
+
return {
|
|
161
|
+
changes: { from: from, to: to, insert: text },
|
|
162
|
+
range: EditorSelection.cursor(from + text.length)
|
|
163
|
+
};
|
|
164
|
+
let len = to - from;
|
|
165
|
+
if (!range.empty ||
|
|
166
|
+
len && state.sliceDoc(range.from - len, range.from) != state.sliceDoc(from, to))
|
|
167
|
+
return { range };
|
|
168
|
+
return {
|
|
169
|
+
changes: { from: range.from - len, to: range.from, insert: text },
|
|
170
|
+
range: EditorSelection.cursor(range.from - len + text.length)
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
154
174
|
function applyCompletion(view, option) {
|
|
155
|
-
|
|
175
|
+
const apply = option.completion.apply || option.completion.label;
|
|
156
176
|
let result = option.source;
|
|
157
|
-
if (typeof apply == "string")
|
|
158
|
-
view.dispatch(
|
|
159
|
-
|
|
160
|
-
selection: { anchor: result.from + apply.length },
|
|
161
|
-
userEvent: "input.complete",
|
|
162
|
-
annotations: pickedCompletion.of(option.completion)
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
177
|
+
if (typeof apply == "string")
|
|
178
|
+
view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
|
|
179
|
+
else
|
|
166
180
|
apply(view, option.completion, result.from, result.to);
|
|
167
|
-
}
|
|
168
181
|
}
|
|
169
182
|
const SourceCache = /*@__PURE__*/new WeakMap();
|
|
170
183
|
function asSource(source) {
|
|
@@ -306,6 +319,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
|
|
|
306
319
|
return combineConfig(configs, {
|
|
307
320
|
activateOnTyping: true,
|
|
308
321
|
override: null,
|
|
322
|
+
closeOnBlur: true,
|
|
309
323
|
maxRenderedOptions: 100,
|
|
310
324
|
defaultKeymap: true,
|
|
311
325
|
optionClass: () => "",
|
|
@@ -314,6 +328,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
|
|
|
314
328
|
addToOptions: []
|
|
315
329
|
}, {
|
|
316
330
|
defaultKeymap: (a, b) => a && b,
|
|
331
|
+
closeOnBlur: (a, b) => a && b,
|
|
317
332
|
icons: (a, b) => a && b,
|
|
318
333
|
optionClass: (a, b) => c => joinClass(a(c), b(c)),
|
|
319
334
|
addToOptions: (a, b) => a.concat(b)
|
|
@@ -548,7 +563,6 @@ function scrollIntoView(container, element) {
|
|
|
548
563
|
container.scrollTop += self.bottom - parent.bottom;
|
|
549
564
|
}
|
|
550
565
|
|
|
551
|
-
const MaxOptions = 300;
|
|
552
566
|
// Used to pick a preferred option when two options with the same
|
|
553
567
|
// label occur in the result.
|
|
554
568
|
function score(option) {
|
|
@@ -560,8 +574,14 @@ function sortOptions(active, state) {
|
|
|
560
574
|
for (let a of active)
|
|
561
575
|
if (a.hasResult()) {
|
|
562
576
|
if (a.result.filter === false) {
|
|
563
|
-
|
|
564
|
-
|
|
577
|
+
let getMatch = a.result.getMatch;
|
|
578
|
+
for (let option of a.result.options) {
|
|
579
|
+
let match = [1e9 - i++];
|
|
580
|
+
if (getMatch)
|
|
581
|
+
for (let n of getMatch(option))
|
|
582
|
+
match.push(n);
|
|
583
|
+
options.push(new Option(option, a, match));
|
|
584
|
+
}
|
|
565
585
|
}
|
|
566
586
|
else {
|
|
567
587
|
let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
|
|
@@ -575,10 +595,9 @@ function sortOptions(active, state) {
|
|
|
575
595
|
}
|
|
576
596
|
let result = [], prev = null;
|
|
577
597
|
for (let opt of options.sort(cmpOption)) {
|
|
578
|
-
if (result.length == MaxOptions)
|
|
579
|
-
break;
|
|
580
598
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
581
|
-
prev.type != opt.completion.type
|
|
599
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
600
|
+
prev.apply != opt.completion.apply)
|
|
582
601
|
result.push(opt);
|
|
583
602
|
else if (score(opt.completion) > score(prev))
|
|
584
603
|
result[result.length - 1] = opt;
|
|
@@ -728,24 +747,27 @@ class ActiveSource {
|
|
|
728
747
|
}
|
|
729
748
|
}
|
|
730
749
|
class ActiveResult extends ActiveSource {
|
|
731
|
-
constructor(source, explicitPos, result, from, to
|
|
750
|
+
constructor(source, explicitPos, result, from, to) {
|
|
732
751
|
super(source, 2 /* Result */, explicitPos);
|
|
733
752
|
this.result = result;
|
|
734
753
|
this.from = from;
|
|
735
754
|
this.to = to;
|
|
736
|
-
this.span = span;
|
|
737
755
|
}
|
|
738
756
|
hasResult() { return true; }
|
|
739
757
|
handleUserEvent(tr, type, conf) {
|
|
758
|
+
var _a;
|
|
740
759
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
741
760
|
let pos = cur(tr.state);
|
|
742
761
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
743
762
|
pos > to ||
|
|
744
763
|
type == "delete" && cur(tr.startState) == this.from)
|
|
745
764
|
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
|
|
765
|
+
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
|
|
766
|
+
if (checkValid(this.result.validFor, tr.state, from, to))
|
|
767
|
+
return new ActiveResult(this.source, explicitPos, this.result, from, to);
|
|
768
|
+
if (this.result.update &&
|
|
769
|
+
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
|
|
770
|
+
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
|
|
749
771
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
750
772
|
}
|
|
751
773
|
handleChange(tr) {
|
|
@@ -753,9 +775,15 @@ class ActiveResult extends ActiveSource {
|
|
|
753
775
|
}
|
|
754
776
|
map(mapping) {
|
|
755
777
|
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)
|
|
778
|
+
new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
|
|
757
779
|
}
|
|
758
780
|
}
|
|
781
|
+
function checkValid(validFor, state, from, to) {
|
|
782
|
+
if (!validFor)
|
|
783
|
+
return false;
|
|
784
|
+
let text = state.sliceDoc(from, to);
|
|
785
|
+
return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
|
|
786
|
+
}
|
|
759
787
|
const startCompletionEffect = /*@__PURE__*/StateEffect.define();
|
|
760
788
|
const closeCompletionEffect = /*@__PURE__*/StateEffect.define();
|
|
761
789
|
const setActiveEffect = /*@__PURE__*/StateEffect.define({
|
|
@@ -929,7 +957,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
929
957
|
continue;
|
|
930
958
|
this.running.splice(i--, 1);
|
|
931
959
|
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)
|
|
960
|
+
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
961
|
// Replay the transactions that happened since the start of
|
|
934
962
|
// the request and see if that preserves the result
|
|
935
963
|
for (let tr of query.updates)
|
|
@@ -961,6 +989,11 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
961
989
|
}
|
|
962
990
|
}, {
|
|
963
991
|
eventHandlers: {
|
|
992
|
+
blur() {
|
|
993
|
+
let state = this.view.state.field(completionState, false);
|
|
994
|
+
if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur)
|
|
995
|
+
this.view.dispatch({ effects: closeCompletionEffect.of(null) });
|
|
996
|
+
},
|
|
964
997
|
compositionstart() {
|
|
965
998
|
this.composing = 1 /* Started */;
|
|
966
999
|
},
|
|
@@ -1202,8 +1235,9 @@ function fieldSelection(ranges, field) {
|
|
|
1202
1235
|
return EditorSelection.create(ranges.filter(r => r.field == field).map(r => EditorSelection.range(r.from, r.to)));
|
|
1203
1236
|
}
|
|
1204
1237
|
/**
|
|
1205
|
-
Convert a snippet template to a function that can
|
|
1206
|
-
Snippets are written
|
|
1238
|
+
Convert a snippet template to a function that can
|
|
1239
|
+
[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
|
|
1240
|
+
using syntax like this:
|
|
1207
1241
|
|
|
1208
1242
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1209
1243
|
|
|
@@ -1386,8 +1420,238 @@ const completeAnyWord = context => {
|
|
|
1386
1420
|
return null;
|
|
1387
1421
|
let from = token ? token.from : context.pos;
|
|
1388
1422
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1389
|
-
return { from, options,
|
|
1423
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
const defaults = {
|
|
1427
|
+
brackets: ["(", "[", "{", "'", '"'],
|
|
1428
|
+
before: ")]}:;>"
|
|
1390
1429
|
};
|
|
1430
|
+
const closeBracketEffect = /*@__PURE__*/StateEffect.define({
|
|
1431
|
+
map(value, mapping) {
|
|
1432
|
+
let mapped = mapping.mapPos(value, -1, MapMode.TrackAfter);
|
|
1433
|
+
return mapped == null ? undefined : mapped;
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
const skipBracketEffect = /*@__PURE__*/StateEffect.define({
|
|
1437
|
+
map(value, mapping) { return mapping.mapPos(value); }
|
|
1438
|
+
});
|
|
1439
|
+
const closedBracket = /*@__PURE__*/new class extends RangeValue {
|
|
1440
|
+
};
|
|
1441
|
+
closedBracket.startSide = 1;
|
|
1442
|
+
closedBracket.endSide = -1;
|
|
1443
|
+
const bracketState = /*@__PURE__*/StateField.define({
|
|
1444
|
+
create() { return RangeSet.empty; },
|
|
1445
|
+
update(value, tr) {
|
|
1446
|
+
if (tr.selection) {
|
|
1447
|
+
let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from;
|
|
1448
|
+
let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from;
|
|
1449
|
+
if (lineStart != tr.changes.mapPos(prevLineStart, -1))
|
|
1450
|
+
value = RangeSet.empty;
|
|
1451
|
+
}
|
|
1452
|
+
value = value.map(tr.changes);
|
|
1453
|
+
for (let effect of tr.effects) {
|
|
1454
|
+
if (effect.is(closeBracketEffect))
|
|
1455
|
+
value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
|
|
1456
|
+
else if (effect.is(skipBracketEffect))
|
|
1457
|
+
value = value.update({ filter: from => from != effect.value });
|
|
1458
|
+
}
|
|
1459
|
+
return value;
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
/**
|
|
1463
|
+
Extension to enable bracket-closing behavior. When a closeable
|
|
1464
|
+
bracket is typed, its closing bracket is immediately inserted
|
|
1465
|
+
after the cursor. When closing a bracket directly in front of a
|
|
1466
|
+
closing bracket inserted by the extension, the cursor moves over
|
|
1467
|
+
that bracket.
|
|
1468
|
+
*/
|
|
1469
|
+
function closeBrackets() {
|
|
1470
|
+
return [inputHandler, bracketState];
|
|
1471
|
+
}
|
|
1472
|
+
const definedClosing = "()[]{}<>";
|
|
1473
|
+
function closing(ch) {
|
|
1474
|
+
for (let i = 0; i < definedClosing.length; i += 2)
|
|
1475
|
+
if (definedClosing.charCodeAt(i) == ch)
|
|
1476
|
+
return definedClosing.charAt(i + 1);
|
|
1477
|
+
return fromCodePoint(ch < 128 ? ch : ch + 1);
|
|
1478
|
+
}
|
|
1479
|
+
function config(state, pos) {
|
|
1480
|
+
return state.languageDataAt("closeBrackets", pos)[0] || defaults;
|
|
1481
|
+
}
|
|
1482
|
+
const android = typeof navigator == "object" && /*@__PURE__*//Android\b/.test(navigator.userAgent);
|
|
1483
|
+
const inputHandler = /*@__PURE__*/EditorView.inputHandler.of((view, from, to, insert) => {
|
|
1484
|
+
if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
|
|
1485
|
+
return false;
|
|
1486
|
+
let sel = view.state.selection.main;
|
|
1487
|
+
if (insert.length > 2 || insert.length == 2 && codePointSize(codePointAt(insert, 0)) == 1 ||
|
|
1488
|
+
from != sel.from || to != sel.to)
|
|
1489
|
+
return false;
|
|
1490
|
+
let tr = insertBracket(view.state, insert);
|
|
1491
|
+
if (!tr)
|
|
1492
|
+
return false;
|
|
1493
|
+
view.dispatch(tr);
|
|
1494
|
+
return true;
|
|
1495
|
+
});
|
|
1496
|
+
/**
|
|
1497
|
+
Command that implements deleting a pair of matching brackets when
|
|
1498
|
+
the cursor is between them.
|
|
1499
|
+
*/
|
|
1500
|
+
const deleteBracketPair = ({ state, dispatch }) => {
|
|
1501
|
+
if (state.readOnly)
|
|
1502
|
+
return false;
|
|
1503
|
+
let conf = config(state, state.selection.main.head);
|
|
1504
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1505
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1506
|
+
if (range.empty) {
|
|
1507
|
+
let before = prevChar(state.doc, range.head);
|
|
1508
|
+
for (let token of tokens) {
|
|
1509
|
+
if (token == before && nextChar(state.doc, range.head) == closing(codePointAt(token, 0)))
|
|
1510
|
+
return { changes: { from: range.head - token.length, to: range.head + token.length },
|
|
1511
|
+
range: EditorSelection.cursor(range.head - token.length),
|
|
1512
|
+
userEvent: "delete.backward" };
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return { range: dont = range };
|
|
1516
|
+
});
|
|
1517
|
+
if (!dont)
|
|
1518
|
+
dispatch(state.update(changes, { scrollIntoView: true }));
|
|
1519
|
+
return !dont;
|
|
1520
|
+
};
|
|
1521
|
+
/**
|
|
1522
|
+
Close-brackets related key bindings. Binds Backspace to
|
|
1523
|
+
[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
|
|
1524
|
+
*/
|
|
1525
|
+
const closeBracketsKeymap = [
|
|
1526
|
+
{ key: "Backspace", run: deleteBracketPair }
|
|
1527
|
+
];
|
|
1528
|
+
/**
|
|
1529
|
+
Implements the extension's behavior on text insertion. If the
|
|
1530
|
+
given string counts as a bracket in the language around the
|
|
1531
|
+
selection, and replacing the selection with it requires custom
|
|
1532
|
+
behavior (inserting a closing version or skipping past a
|
|
1533
|
+
previously-closed bracket), this function returns a transaction
|
|
1534
|
+
representing that custom behavior. (You only need this if you want
|
|
1535
|
+
to programmatically insert brackets—the
|
|
1536
|
+
[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
|
|
1537
|
+
take care of running this for user input.)
|
|
1538
|
+
*/
|
|
1539
|
+
function insertBracket(state, bracket) {
|
|
1540
|
+
let conf = config(state, state.selection.main.head);
|
|
1541
|
+
let tokens = conf.brackets || defaults.brackets;
|
|
1542
|
+
for (let tok of tokens) {
|
|
1543
|
+
let closed = closing(codePointAt(tok, 0));
|
|
1544
|
+
if (bracket == tok)
|
|
1545
|
+
return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1)
|
|
1546
|
+
: handleOpen(state, tok, closed, conf.before || defaults.before);
|
|
1547
|
+
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
|
|
1548
|
+
return handleClose(state, tok, closed);
|
|
1549
|
+
}
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
function closedBracketAt(state, pos) {
|
|
1553
|
+
let found = false;
|
|
1554
|
+
state.field(bracketState).between(0, state.doc.length, from => {
|
|
1555
|
+
if (from == pos)
|
|
1556
|
+
found = true;
|
|
1557
|
+
});
|
|
1558
|
+
return found;
|
|
1559
|
+
}
|
|
1560
|
+
function nextChar(doc, pos) {
|
|
1561
|
+
let next = doc.sliceString(pos, pos + 2);
|
|
1562
|
+
return next.slice(0, codePointSize(codePointAt(next, 0)));
|
|
1563
|
+
}
|
|
1564
|
+
function prevChar(doc, pos) {
|
|
1565
|
+
let prev = doc.sliceString(pos - 2, pos);
|
|
1566
|
+
return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
|
|
1567
|
+
}
|
|
1568
|
+
function handleOpen(state, open, close, closeBefore) {
|
|
1569
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1570
|
+
if (!range.empty)
|
|
1571
|
+
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
|
|
1572
|
+
effects: closeBracketEffect.of(range.to + open.length),
|
|
1573
|
+
range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
|
|
1574
|
+
let next = nextChar(state.doc, range.head);
|
|
1575
|
+
if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
|
|
1576
|
+
return { changes: { insert: open + close, from: range.head },
|
|
1577
|
+
effects: closeBracketEffect.of(range.head + open.length),
|
|
1578
|
+
range: EditorSelection.cursor(range.head + open.length) };
|
|
1579
|
+
return { range: dont = range };
|
|
1580
|
+
});
|
|
1581
|
+
return dont ? null : state.update(changes, {
|
|
1582
|
+
scrollIntoView: true,
|
|
1583
|
+
userEvent: "input.type"
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
function handleClose(state, _open, close) {
|
|
1587
|
+
let dont = null, moved = state.selection.ranges.map(range => {
|
|
1588
|
+
if (range.empty && nextChar(state.doc, range.head) == close)
|
|
1589
|
+
return EditorSelection.cursor(range.head + close.length);
|
|
1590
|
+
return dont = range;
|
|
1591
|
+
});
|
|
1592
|
+
return dont ? null : state.update({
|
|
1593
|
+
selection: EditorSelection.create(moved, state.selection.mainIndex),
|
|
1594
|
+
scrollIntoView: true,
|
|
1595
|
+
effects: state.selection.ranges.map(({ from }) => skipBracketEffect.of(from))
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
// Handles cases where the open and close token are the same, and
|
|
1599
|
+
// possibly triple quotes (as in `"""abc"""`-style quoting).
|
|
1600
|
+
function handleSame(state, token, allowTriple) {
|
|
1601
|
+
let dont = null, changes = state.changeByRange(range => {
|
|
1602
|
+
if (!range.empty)
|
|
1603
|
+
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
|
|
1604
|
+
effects: closeBracketEffect.of(range.to + token.length),
|
|
1605
|
+
range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
|
|
1606
|
+
let pos = range.head, next = nextChar(state.doc, pos);
|
|
1607
|
+
if (next == token) {
|
|
1608
|
+
if (nodeStart(state, pos)) {
|
|
1609
|
+
return { changes: { insert: token + token, from: pos },
|
|
1610
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1611
|
+
range: EditorSelection.cursor(pos + token.length) };
|
|
1612
|
+
}
|
|
1613
|
+
else if (closedBracketAt(state, pos)) {
|
|
1614
|
+
let isTriple = allowTriple && state.sliceDoc(pos, pos + token.length * 3) == token + token + token;
|
|
1615
|
+
return { range: EditorSelection.cursor(pos + token.length * (isTriple ? 3 : 1)),
|
|
1616
|
+
effects: skipBracketEffect.of(pos) };
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
|
|
1620
|
+
nodeStart(state, pos - 2 * token.length)) {
|
|
1621
|
+
return { changes: { insert: token + token + token + token, from: pos },
|
|
1622
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1623
|
+
range: EditorSelection.cursor(pos + token.length) };
|
|
1624
|
+
}
|
|
1625
|
+
else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
|
|
1626
|
+
let prev = state.sliceDoc(pos - 1, pos);
|
|
1627
|
+
if (prev != token && state.charCategorizer(pos)(prev) != CharCategory.Word && !probablyInString(state, pos, token))
|
|
1628
|
+
return { changes: { insert: token + token, from: pos },
|
|
1629
|
+
effects: closeBracketEffect.of(pos + token.length),
|
|
1630
|
+
range: EditorSelection.cursor(pos + token.length) };
|
|
1631
|
+
}
|
|
1632
|
+
return { range: dont = range };
|
|
1633
|
+
});
|
|
1634
|
+
return dont ? null : state.update(changes, {
|
|
1635
|
+
scrollIntoView: true,
|
|
1636
|
+
userEvent: "input.type"
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
function nodeStart(state, pos) {
|
|
1640
|
+
let tree = syntaxTree(state).resolveInner(pos + 1);
|
|
1641
|
+
return tree.parent && tree.from == pos;
|
|
1642
|
+
}
|
|
1643
|
+
function probablyInString(state, pos, quoteToken) {
|
|
1644
|
+
let node = syntaxTree(state).resolveInner(pos, -1);
|
|
1645
|
+
for (let i = 0; i < 5; i++) {
|
|
1646
|
+
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
|
|
1647
|
+
return true;
|
|
1648
|
+
let parent = node.to == pos && node.parent;
|
|
1649
|
+
if (!parent)
|
|
1650
|
+
break;
|
|
1651
|
+
node = parent;
|
|
1652
|
+
}
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1391
1655
|
|
|
1392
1656
|
/**
|
|
1393
1657
|
Returns an extension that enables autocompletion.
|
|
@@ -1472,4 +1736,4 @@ function setSelectedCompletion(index) {
|
|
|
1472
1736
|
return setSelectedEffect.of(index);
|
|
1473
1737
|
}
|
|
1474
1738
|
|
|
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 };
|
|
1739
|
+
export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemirror/autocomplete",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.2",
|
|
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"
|