@codemirror/autocomplete 0.19.14 → 0.20.1

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