@codemirror/autocomplete 0.19.13 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/index.cjs +348 -70
- package/dist/index.d.ts +85 -16
- package/dist/index.js +326 -54
- package/package.json +5 -7
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Annotation, Facet, combineConfig, StateEffect, StateField, Prec,
|
|
2
|
-
import { Direction,
|
|
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
|
});
|
|
@@ -370,22 +380,6 @@ function optionContent(config) {
|
|
|
370
380
|
});
|
|
371
381
|
return content.sort((a, b) => a.position - b.position).map(a => a.render);
|
|
372
382
|
}
|
|
373
|
-
function createInfoDialog(option, view) {
|
|
374
|
-
let dom = document.createElement("div");
|
|
375
|
-
dom.className = "cm-tooltip cm-completionInfo";
|
|
376
|
-
let { info } = option.completion;
|
|
377
|
-
if (typeof info == "string") {
|
|
378
|
-
dom.textContent = info;
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
let content = info(option.completion);
|
|
382
|
-
if (content.then)
|
|
383
|
-
content.then(node => dom.appendChild(node), e => logException(view.state, e, "completion info"));
|
|
384
|
-
else
|
|
385
|
-
dom.appendChild(content);
|
|
386
|
-
}
|
|
387
|
-
return dom;
|
|
388
|
-
}
|
|
389
383
|
function rangeAroundSelected(total, selected, max) {
|
|
390
384
|
if (total <= max)
|
|
391
385
|
return { from: 0, to: total };
|
|
@@ -454,13 +448,31 @@ class CompletionTooltip {
|
|
|
454
448
|
this.info.remove();
|
|
455
449
|
this.info = null;
|
|
456
450
|
}
|
|
457
|
-
let
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
451
|
+
let { completion } = open.options[open.selected];
|
|
452
|
+
let { info } = completion;
|
|
453
|
+
if (!info)
|
|
454
|
+
return;
|
|
455
|
+
let infoResult = typeof info === 'string' ? document.createTextNode(info) : info(completion);
|
|
456
|
+
if (!infoResult)
|
|
457
|
+
return;
|
|
458
|
+
if ('then' in infoResult) {
|
|
459
|
+
infoResult.then(node => {
|
|
460
|
+
if (node && this.view.state.field(this.stateField, false) == cState)
|
|
461
|
+
this.addInfoPane(node);
|
|
462
|
+
}).catch(e => logException(this.view.state, e, "completion info"));
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
this.addInfoPane(infoResult);
|
|
461
466
|
}
|
|
462
467
|
}
|
|
463
468
|
}
|
|
469
|
+
addInfoPane(content) {
|
|
470
|
+
let dom = this.info = document.createElement("div");
|
|
471
|
+
dom.className = "cm-tooltip cm-completionInfo";
|
|
472
|
+
dom.appendChild(content);
|
|
473
|
+
this.dom.appendChild(dom);
|
|
474
|
+
this.view.requestMeasure(this.placeInfo);
|
|
475
|
+
}
|
|
464
476
|
updateSelectedOption(selected) {
|
|
465
477
|
let set = null;
|
|
466
478
|
for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
|
|
@@ -510,6 +522,7 @@ class CompletionTooltip {
|
|
|
510
522
|
const ul = document.createElement("ul");
|
|
511
523
|
ul.id = id;
|
|
512
524
|
ul.setAttribute("role", "listbox");
|
|
525
|
+
ul.setAttribute("aria-expanded", "true");
|
|
513
526
|
for (let i = range.from; i < range.to; i++) {
|
|
514
527
|
let { completion, match } = options[i];
|
|
515
528
|
const li = ul.appendChild(document.createElement("li"));
|
|
@@ -545,7 +558,6 @@ function scrollIntoView(container, element) {
|
|
|
545
558
|
container.scrollTop += self.bottom - parent.bottom;
|
|
546
559
|
}
|
|
547
560
|
|
|
548
|
-
const MaxOptions = 300;
|
|
549
561
|
// Used to pick a preferred option when two options with the same
|
|
550
562
|
// label occur in the result.
|
|
551
563
|
function score(option) {
|
|
@@ -570,13 +582,11 @@ function sortOptions(active, state) {
|
|
|
570
582
|
}
|
|
571
583
|
}
|
|
572
584
|
}
|
|
573
|
-
options.sort(cmpOption);
|
|
574
585
|
let result = [], prev = null;
|
|
575
586
|
for (let opt of options.sort(cmpOption)) {
|
|
576
|
-
if (result.length == MaxOptions)
|
|
577
|
-
break;
|
|
578
587
|
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
|
|
579
|
-
prev.type != opt.completion.type
|
|
588
|
+
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
|
|
589
|
+
prev.apply != opt.completion.apply)
|
|
580
590
|
result.push(opt);
|
|
581
591
|
else if (score(opt.completion) > score(prev))
|
|
582
592
|
result[result.length - 1] = opt;
|
|
@@ -603,10 +613,11 @@ class CompletionDialog {
|
|
|
603
613
|
let selected = 0;
|
|
604
614
|
if (prev && prev.selected) {
|
|
605
615
|
let selectedValue = prev.options[prev.selected].completion;
|
|
606
|
-
for (let i = 0; i < options.length
|
|
607
|
-
if (options[i].completion == selectedValue)
|
|
616
|
+
for (let i = 0; i < options.length; i++)
|
|
617
|
+
if (options[i].completion == selectedValue) {
|
|
608
618
|
selected = i;
|
|
609
|
-
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
610
621
|
}
|
|
611
622
|
return new CompletionDialog(options, makeAttrs(id, selected), {
|
|
612
623
|
pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
|
|
@@ -667,13 +678,12 @@ function sameResults(a, b) {
|
|
|
667
678
|
}
|
|
668
679
|
}
|
|
669
680
|
const baseAttrs = {
|
|
670
|
-
"aria-autocomplete": "list"
|
|
671
|
-
"aria-expanded": "false"
|
|
681
|
+
"aria-autocomplete": "list"
|
|
672
682
|
};
|
|
673
683
|
function makeAttrs(id, selected) {
|
|
674
684
|
return {
|
|
675
685
|
"aria-autocomplete": "list",
|
|
676
|
-
"aria-
|
|
686
|
+
"aria-haspopup": "listbox",
|
|
677
687
|
"aria-activedescendant": id + "-" + selected,
|
|
678
688
|
"aria-controls": id
|
|
679
689
|
};
|
|
@@ -726,24 +736,27 @@ class ActiveSource {
|
|
|
726
736
|
}
|
|
727
737
|
}
|
|
728
738
|
class ActiveResult extends ActiveSource {
|
|
729
|
-
constructor(source, explicitPos, result, from, to
|
|
739
|
+
constructor(source, explicitPos, result, from, to) {
|
|
730
740
|
super(source, 2 /* Result */, explicitPos);
|
|
731
741
|
this.result = result;
|
|
732
742
|
this.from = from;
|
|
733
743
|
this.to = to;
|
|
734
|
-
this.span = span;
|
|
735
744
|
}
|
|
736
745
|
hasResult() { return true; }
|
|
737
746
|
handleUserEvent(tr, type, conf) {
|
|
747
|
+
var _a;
|
|
738
748
|
let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
|
|
739
749
|
let pos = cur(tr.state);
|
|
740
750
|
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
|
|
741
751
|
pos > to ||
|
|
742
752
|
type == "delete" && cur(tr.startState) == this.from)
|
|
743
753
|
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
|
|
744
|
-
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
|
|
745
|
-
if (
|
|
746
|
-
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));
|
|
747
760
|
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
|
|
748
761
|
}
|
|
749
762
|
handleChange(tr) {
|
|
@@ -751,9 +764,15 @@ class ActiveResult extends ActiveSource {
|
|
|
751
764
|
}
|
|
752
765
|
map(mapping) {
|
|
753
766
|
return mapping.empty ? this :
|
|
754
|
-
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));
|
|
755
768
|
}
|
|
756
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
|
+
}
|
|
757
776
|
const startCompletionEffect = /*@__PURE__*/StateEffect.define();
|
|
758
777
|
const closeCompletionEffect = /*@__PURE__*/StateEffect.define();
|
|
759
778
|
const setActiveEffect = /*@__PURE__*/StateEffect.define({
|
|
@@ -855,7 +874,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
855
874
|
for (let i = 0; i < this.running.length; i++) {
|
|
856
875
|
let query = this.running[i];
|
|
857
876
|
if (doesReset ||
|
|
858
|
-
query.updates.length + update.transactions.length > MaxUpdateCount &&
|
|
877
|
+
query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
|
|
859
878
|
for (let handler of query.context.abortListeners) {
|
|
860
879
|
try {
|
|
861
880
|
handler();
|
|
@@ -927,7 +946,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
927
946
|
continue;
|
|
928
947
|
this.running.splice(i--, 1);
|
|
929
948
|
if (query.done) {
|
|
930
|
-
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));
|
|
931
950
|
// Replay the transactions that happened since the start of
|
|
932
951
|
// the request and see if that preserves the result
|
|
933
952
|
for (let tr of query.updates)
|
|
@@ -1200,8 +1219,9 @@ function fieldSelection(ranges, field) {
|
|
|
1200
1219
|
return EditorSelection.create(ranges.filter(r => r.field == field).map(r => EditorSelection.range(r.from, r.to)));
|
|
1201
1220
|
}
|
|
1202
1221
|
/**
|
|
1203
|
-
Convert a snippet template to a function that can
|
|
1204
|
-
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:
|
|
1205
1225
|
|
|
1206
1226
|
"for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
|
|
1207
1227
|
|
|
@@ -1384,9 +1404,239 @@ const completeAnyWord = context => {
|
|
|
1384
1404
|
return null;
|
|
1385
1405
|
let from = token ? token.from : context.pos;
|
|
1386
1406
|
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
|
|
1387
|
-
return { from, options,
|
|
1407
|
+
return { from, options, validFor: mapRE(re, s => "^" + s) };
|
|
1388
1408
|
};
|
|
1389
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 {
|
|
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
|
+
}
|
|
1639
|
+
|
|
1390
1640
|
/**
|
|
1391
1641
|
Returns an extension that enables autocompletion.
|
|
1392
1642
|
*/
|
|
@@ -1431,13 +1681,19 @@ function completionStatus(state) {
|
|
|
1431
1681
|
return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending"
|
|
1432
1682
|
: cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null;
|
|
1433
1683
|
}
|
|
1684
|
+
const completionArrayCache = /*@__PURE__*/new WeakMap;
|
|
1434
1685
|
/**
|
|
1435
1686
|
Returns the available completions as an array.
|
|
1436
1687
|
*/
|
|
1437
1688
|
function currentCompletions(state) {
|
|
1438
1689
|
var _a;
|
|
1439
1690
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1440
|
-
|
|
1691
|
+
if (!open)
|
|
1692
|
+
return [];
|
|
1693
|
+
let completions = completionArrayCache.get(open.options);
|
|
1694
|
+
if (!completions)
|
|
1695
|
+
completionArrayCache.set(open.options, completions = open.options.map(o => o.completion));
|
|
1696
|
+
return completions;
|
|
1441
1697
|
}
|
|
1442
1698
|
/**
|
|
1443
1699
|
Return the currently selected completion, if any.
|
|
@@ -1447,5 +1703,21 @@ function selectedCompletion(state) {
|
|
|
1447
1703
|
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1448
1704
|
return open ? open.options[open.selected].completion : null;
|
|
1449
1705
|
}
|
|
1706
|
+
/**
|
|
1707
|
+
Returns the currently selected position in the active completion
|
|
1708
|
+
list, or null if no completions are active.
|
|
1709
|
+
*/
|
|
1710
|
+
function selectedCompletionIndex(state) {
|
|
1711
|
+
var _a;
|
|
1712
|
+
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
|
|
1713
|
+
return open ? open.selected : null;
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
Create an effect that can be attached to a transaction to change
|
|
1717
|
+
the currently selected completion.
|
|
1718
|
+
*/
|
|
1719
|
+
function setSelectedCompletion(index) {
|
|
1720
|
+
return setSelectedEffect.of(index);
|
|
1721
|
+
}
|
|
1450
1722
|
|
|
1451
|
-
export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, 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"
|