@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/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 {
@@ -374,22 +384,6 @@ function optionContent(config) {
374
384
  });
375
385
  return content.sort((a, b) => a.position - b.position).map(a => a.render);
376
386
  }
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
387
  function rangeAroundSelected(total, selected, max) {
394
388
  if (total <= max)
395
389
  return { from: 0, to: total };
@@ -458,13 +452,31 @@ class CompletionTooltip {
458
452
  this.info.remove();
459
453
  this.info = null;
460
454
  }
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);
455
+ let { completion } = open.options[open.selected];
456
+ let { info } = completion;
457
+ if (!info)
458
+ return;
459
+ let infoResult = typeof info === 'string' ? document.createTextNode(info) : info(completion);
460
+ if (!infoResult)
461
+ return;
462
+ if ('then' in infoResult) {
463
+ infoResult.then(node => {
464
+ if (node && this.view.state.field(this.stateField, false) == cState)
465
+ this.addInfoPane(node);
466
+ }).catch(e => view.logException(this.view.state, e, "completion info"));
467
+ }
468
+ else {
469
+ this.addInfoPane(infoResult);
465
470
  }
466
471
  }
467
472
  }
473
+ addInfoPane(content) {
474
+ let dom = this.info = document.createElement("div");
475
+ dom.className = "cm-tooltip cm-completionInfo";
476
+ dom.appendChild(content);
477
+ this.dom.appendChild(dom);
478
+ this.view.requestMeasure(this.placeInfo);
479
+ }
468
480
  updateSelectedOption(selected) {
469
481
  let set = null;
470
482
  for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
@@ -514,6 +526,7 @@ class CompletionTooltip {
514
526
  const ul = document.createElement("ul");
515
527
  ul.id = id;
516
528
  ul.setAttribute("role", "listbox");
529
+ ul.setAttribute("aria-expanded", "true");
517
530
  for (let i = range.from; i < range.to; i++) {
518
531
  let { completion, match } = options[i];
519
532
  const li = ul.appendChild(document.createElement("li"));
@@ -549,7 +562,6 @@ function scrollIntoView(container, element) {
549
562
  container.scrollTop += self.bottom - parent.bottom;
550
563
  }
551
564
 
552
- const MaxOptions = 300;
553
565
  // Used to pick a preferred option when two options with the same
554
566
  // label occur in the result.
555
567
  function score(option) {
@@ -574,13 +586,11 @@ function sortOptions(active, state) {
574
586
  }
575
587
  }
576
588
  }
577
- options.sort(cmpOption);
578
589
  let result = [], prev = null;
579
590
  for (let opt of options.sort(cmpOption)) {
580
- if (result.length == MaxOptions)
581
- break;
582
591
  if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
583
- prev.type != opt.completion.type || prev.apply != opt.completion.apply)
592
+ (prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
593
+ prev.apply != opt.completion.apply)
584
594
  result.push(opt);
585
595
  else if (score(opt.completion) > score(prev))
586
596
  result[result.length - 1] = opt;
@@ -607,10 +617,11 @@ class CompletionDialog {
607
617
  let selected = 0;
608
618
  if (prev && prev.selected) {
609
619
  let selectedValue = prev.options[prev.selected].completion;
610
- for (let i = 0; i < options.length && !selected; i++) {
611
- if (options[i].completion == selectedValue)
620
+ for (let i = 0; i < options.length; i++)
621
+ if (options[i].completion == selectedValue) {
612
622
  selected = i;
613
- }
623
+ break;
624
+ }
614
625
  }
615
626
  return new CompletionDialog(options, makeAttrs(id, selected), {
616
627
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
@@ -671,13 +682,12 @@ function sameResults(a, b) {
671
682
  }
672
683
  }
673
684
  const baseAttrs = {
674
- "aria-autocomplete": "list",
675
- "aria-expanded": "false"
685
+ "aria-autocomplete": "list"
676
686
  };
677
687
  function makeAttrs(id, selected) {
678
688
  return {
679
689
  "aria-autocomplete": "list",
680
- "aria-expanded": "true",
690
+ "aria-haspopup": "listbox",
681
691
  "aria-activedescendant": id + "-" + selected,
682
692
  "aria-controls": id
683
693
  };
@@ -730,24 +740,27 @@ class ActiveSource {
730
740
  }
731
741
  }
732
742
  class ActiveResult extends ActiveSource {
733
- constructor(source, explicitPos, result, from, to, span) {
743
+ constructor(source, explicitPos, result, from, to) {
734
744
  super(source, 2 /* Result */, explicitPos);
735
745
  this.result = result;
736
746
  this.from = from;
737
747
  this.to = to;
738
- this.span = span;
739
748
  }
740
749
  hasResult() { return true; }
741
750
  handleUserEvent(tr, type, conf) {
751
+ var _a;
742
752
  let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
743
753
  let pos = cur(tr.state);
744
754
  if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
745
755
  pos > to ||
746
756
  type == "delete" && cur(tr.startState) == this.from)
747
757
  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);
758
+ let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
759
+ if (checkValid(this.result.validFor, tr.state, from, to))
760
+ return new ActiveResult(this.source, explicitPos, this.result, from, to);
761
+ if (this.result.update &&
762
+ (updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
763
+ return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
751
764
  return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
752
765
  }
753
766
  handleChange(tr) {
@@ -755,9 +768,15 @@ class ActiveResult extends ActiveSource {
755
768
  }
756
769
  map(mapping) {
757
770
  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);
771
+ new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
759
772
  }
760
773
  }
774
+ function checkValid(validFor, state, from, to) {
775
+ if (!validFor)
776
+ return false;
777
+ let text = state.sliceDoc(from, to);
778
+ return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
779
+ }
761
780
  const startCompletionEffect = state.StateEffect.define();
762
781
  const closeCompletionEffect = state.StateEffect.define();
763
782
  const setActiveEffect = state.StateEffect.define({
@@ -768,7 +787,7 @@ const completionState = state.StateField.define({
768
787
  create() { return CompletionState.start(); },
769
788
  update(value, tr) { return value.update(tr); },
770
789
  provide: f => [
771
- tooltip.showTooltip.from(f, val => val.tooltip),
790
+ view.showTooltip.from(f, val => val.tooltip),
772
791
  view.EditorView.contentAttributes.from(f, state => state.attrs)
773
792
  ]
774
793
  });
@@ -779,20 +798,20 @@ Returns a command that moves the completion selection forward or
779
798
  backward by the given amount.
780
799
  */
781
800
  function moveCompletionSelection(forward, by = "option") {
782
- return (view) => {
783
- let cState = view.state.field(completionState, false);
801
+ return (view$1) => {
802
+ let cState = view$1.state.field(completionState, false);
784
803
  if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
785
804
  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);
805
+ let step = 1, tooltip;
806
+ if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
807
+ step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
808
+ tooltip.dom.querySelector("li").offsetHeight) - 1);
790
809
  let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
791
810
  if (selected < 0)
792
811
  selected = by == "page" ? 0 : length - 1;
793
812
  else if (selected >= length)
794
813
  selected = by == "page" ? length - 1 : 0;
795
- view.dispatch({ effects: setSelectedEffect.of(selected) });
814
+ view$1.dispatch({ effects: setSelectedEffect.of(selected) });
796
815
  return true;
797
816
  };
798
817
  }
@@ -859,7 +878,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
859
878
  for (let i = 0; i < this.running.length; i++) {
860
879
  let query = this.running[i];
861
880
  if (doesReset ||
862
- query.updates.length + update.transactions.length > MaxUpdateCount && query.time - Date.now() > MinAbortTime) {
881
+ query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
863
882
  for (let handler of query.context.abortListeners) {
864
883
  try {
865
884
  handler();
@@ -931,7 +950,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
931
950
  continue;
932
951
  this.running.splice(i--, 1);
933
952
  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);
953
+ let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state));
935
954
  // Replay the transactions that happened since the start of
936
955
  // the request and see if that preserves the result
937
956
  for (let tr of query.updates)
@@ -1204,8 +1223,9 @@ function fieldSelection(ranges, field) {
1204
1223
  return state.EditorSelection.create(ranges.filter(r => r.field == field).map(r => state.EditorSelection.range(r.from, r.to)));
1205
1224
  }
1206
1225
  /**
1207
- Convert a snippet template to a function that can apply it.
1208
- Snippets are written using syntax like this:
1226
+ Convert a snippet template to a function that can
1227
+ [apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
1228
+ using syntax like this:
1209
1229
 
1210
1230
  "for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
1211
1231
 
@@ -1388,9 +1408,239 @@ const completeAnyWord = context => {
1388
1408
  return null;
1389
1409
  let from = token ? token.from : context.pos;
1390
1410
  let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
1391
- return { from, options, span: mapRE(re, s => "^" + s) };
1411
+ return { from, options, validFor: mapRE(re, s => "^" + s) };
1392
1412
  };
1393
1413
 
1414
+ const defaults = {
1415
+ brackets: ["(", "[", "{", "'", '"'],
1416
+ before: ")]}:;>"
1417
+ };
1418
+ const closeBracketEffect = state.StateEffect.define({
1419
+ map(value, mapping) {
1420
+ let mapped = mapping.mapPos(value, -1, state.MapMode.TrackAfter);
1421
+ return mapped == null ? undefined : mapped;
1422
+ }
1423
+ });
1424
+ const skipBracketEffect = state.StateEffect.define({
1425
+ map(value, mapping) { return mapping.mapPos(value); }
1426
+ });
1427
+ const closedBracket = new class extends state.RangeValue {
1428
+ };
1429
+ closedBracket.startSide = 1;
1430
+ closedBracket.endSide = -1;
1431
+ const bracketState = state.StateField.define({
1432
+ create() { return state.RangeSet.empty; },
1433
+ update(value, tr) {
1434
+ if (tr.selection) {
1435
+ let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from;
1436
+ let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from;
1437
+ if (lineStart != tr.changes.mapPos(prevLineStart, -1))
1438
+ value = state.RangeSet.empty;
1439
+ }
1440
+ value = value.map(tr.changes);
1441
+ for (let effect of tr.effects) {
1442
+ if (effect.is(closeBracketEffect))
1443
+ value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
1444
+ else if (effect.is(skipBracketEffect))
1445
+ value = value.update({ filter: from => from != effect.value });
1446
+ }
1447
+ return value;
1448
+ }
1449
+ });
1450
+ /**
1451
+ Extension to enable bracket-closing behavior. When a closeable
1452
+ bracket is typed, its closing bracket is immediately inserted
1453
+ after the cursor. When closing a bracket directly in front of a
1454
+ closing bracket inserted by the extension, the cursor moves over
1455
+ that bracket.
1456
+ */
1457
+ function closeBrackets() {
1458
+ return [inputHandler, bracketState];
1459
+ }
1460
+ const definedClosing = "()[]{}<>";
1461
+ function closing(ch) {
1462
+ for (let i = 0; i < definedClosing.length; i += 2)
1463
+ if (definedClosing.charCodeAt(i) == ch)
1464
+ return definedClosing.charAt(i + 1);
1465
+ return state.fromCodePoint(ch < 128 ? ch : ch + 1);
1466
+ }
1467
+ function config(state, pos) {
1468
+ return state.languageDataAt("closeBrackets", pos)[0] || defaults;
1469
+ }
1470
+ const android = typeof navigator == "object" && /Android\b/.test(navigator.userAgent);
1471
+ const inputHandler = view.EditorView.inputHandler.of((view, from, to, insert) => {
1472
+ if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
1473
+ return false;
1474
+ let sel = view.state.selection.main;
1475
+ if (insert.length > 2 || insert.length == 2 && state.codePointSize(state.codePointAt(insert, 0)) == 1 ||
1476
+ from != sel.from || to != sel.to)
1477
+ return false;
1478
+ let tr = insertBracket(view.state, insert);
1479
+ if (!tr)
1480
+ return false;
1481
+ view.dispatch(tr);
1482
+ return true;
1483
+ });
1484
+ /**
1485
+ Command that implements deleting a pair of matching brackets when
1486
+ the cursor is between them.
1487
+ */
1488
+ const deleteBracketPair = ({ state: state$1, dispatch }) => {
1489
+ if (state$1.readOnly)
1490
+ return false;
1491
+ let conf = config(state$1, state$1.selection.main.head);
1492
+ let tokens = conf.brackets || defaults.brackets;
1493
+ let dont = null, changes = state$1.changeByRange(range => {
1494
+ if (range.empty) {
1495
+ let before = prevChar(state$1.doc, range.head);
1496
+ for (let token of tokens) {
1497
+ if (token == before && nextChar(state$1.doc, range.head) == closing(state.codePointAt(token, 0)))
1498
+ return { changes: { from: range.head - token.length, to: range.head + token.length },
1499
+ range: state.EditorSelection.cursor(range.head - token.length),
1500
+ userEvent: "delete.backward" };
1501
+ }
1502
+ }
1503
+ return { range: dont = range };
1504
+ });
1505
+ if (!dont)
1506
+ dispatch(state$1.update(changes, { scrollIntoView: true }));
1507
+ return !dont;
1508
+ };
1509
+ /**
1510
+ Close-brackets related key bindings. Binds Backspace to
1511
+ [`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
1512
+ */
1513
+ const closeBracketsKeymap = [
1514
+ { key: "Backspace", run: deleteBracketPair }
1515
+ ];
1516
+ /**
1517
+ Implements the extension's behavior on text insertion. If the
1518
+ given string counts as a bracket in the language around the
1519
+ selection, and replacing the selection with it requires custom
1520
+ behavior (inserting a closing version or skipping past a
1521
+ previously-closed bracket), this function returns a transaction
1522
+ representing that custom behavior. (You only need this if you want
1523
+ to programmatically insert brackets—the
1524
+ [`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
1525
+ take care of running this for user input.)
1526
+ */
1527
+ function insertBracket(state$1, bracket) {
1528
+ let conf = config(state$1, state$1.selection.main.head);
1529
+ let tokens = conf.brackets || defaults.brackets;
1530
+ for (let tok of tokens) {
1531
+ let closed = closing(state.codePointAt(tok, 0));
1532
+ if (bracket == tok)
1533
+ return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
1534
+ : handleOpen(state$1, tok, closed, conf.before || defaults.before);
1535
+ if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
1536
+ return handleClose(state$1, tok, closed);
1537
+ }
1538
+ return null;
1539
+ }
1540
+ function closedBracketAt(state, pos) {
1541
+ let found = false;
1542
+ state.field(bracketState).between(0, state.doc.length, from => {
1543
+ if (from == pos)
1544
+ found = true;
1545
+ });
1546
+ return found;
1547
+ }
1548
+ function nextChar(doc, pos) {
1549
+ let next = doc.sliceString(pos, pos + 2);
1550
+ return next.slice(0, state.codePointSize(state.codePointAt(next, 0)));
1551
+ }
1552
+ function prevChar(doc, pos) {
1553
+ let prev = doc.sliceString(pos - 2, pos);
1554
+ return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
1555
+ }
1556
+ function handleOpen(state$1, open, close, closeBefore) {
1557
+ let dont = null, changes = state$1.changeByRange(range => {
1558
+ if (!range.empty)
1559
+ return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
1560
+ effects: closeBracketEffect.of(range.to + open.length),
1561
+ range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
1562
+ let next = nextChar(state$1.doc, range.head);
1563
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
1564
+ return { changes: { insert: open + close, from: range.head },
1565
+ effects: closeBracketEffect.of(range.head + open.length),
1566
+ range: state.EditorSelection.cursor(range.head + open.length) };
1567
+ return { range: dont = range };
1568
+ });
1569
+ return dont ? null : state$1.update(changes, {
1570
+ scrollIntoView: true,
1571
+ userEvent: "input.type"
1572
+ });
1573
+ }
1574
+ function handleClose(state$1, _open, close) {
1575
+ let dont = null, moved = state$1.selection.ranges.map(range => {
1576
+ if (range.empty && nextChar(state$1.doc, range.head) == close)
1577
+ return state.EditorSelection.cursor(range.head + close.length);
1578
+ return dont = range;
1579
+ });
1580
+ return dont ? null : state$1.update({
1581
+ selection: state.EditorSelection.create(moved, state$1.selection.mainIndex),
1582
+ scrollIntoView: true,
1583
+ effects: state$1.selection.ranges.map(({ from }) => skipBracketEffect.of(from))
1584
+ });
1585
+ }
1586
+ // Handles cases where the open and close token are the same, and
1587
+ // possibly triple quotes (as in `"""abc"""`-style quoting).
1588
+ function handleSame(state$1, token, allowTriple) {
1589
+ let dont = null, changes = state$1.changeByRange(range => {
1590
+ if (!range.empty)
1591
+ return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
1592
+ effects: closeBracketEffect.of(range.to + token.length),
1593
+ range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
1594
+ let pos = range.head, next = nextChar(state$1.doc, pos);
1595
+ if (next == token) {
1596
+ if (nodeStart(state$1, pos)) {
1597
+ return { changes: { insert: token + token, from: pos },
1598
+ effects: closeBracketEffect.of(pos + token.length),
1599
+ range: state.EditorSelection.cursor(pos + token.length) };
1600
+ }
1601
+ else if (closedBracketAt(state$1, pos)) {
1602
+ let isTriple = allowTriple && state$1.sliceDoc(pos, pos + token.length * 3) == token + token + token;
1603
+ return { range: state.EditorSelection.cursor(pos + token.length * (isTriple ? 3 : 1)),
1604
+ effects: skipBracketEffect.of(pos) };
1605
+ }
1606
+ }
1607
+ else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
1608
+ nodeStart(state$1, pos - 2 * token.length)) {
1609
+ return { changes: { insert: token + token + token + token, from: pos },
1610
+ effects: closeBracketEffect.of(pos + token.length),
1611
+ range: state.EditorSelection.cursor(pos + token.length) };
1612
+ }
1613
+ else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
1614
+ let prev = state$1.sliceDoc(pos - 1, pos);
1615
+ if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
1616
+ return { changes: { insert: token + token, from: pos },
1617
+ effects: closeBracketEffect.of(pos + token.length),
1618
+ range: state.EditorSelection.cursor(pos + token.length) };
1619
+ }
1620
+ return { range: dont = range };
1621
+ });
1622
+ return dont ? null : state$1.update(changes, {
1623
+ scrollIntoView: true,
1624
+ userEvent: "input.type"
1625
+ });
1626
+ }
1627
+ function nodeStart(state, pos) {
1628
+ let tree = language.syntaxTree(state).resolveInner(pos + 1);
1629
+ return tree.parent && tree.from == pos;
1630
+ }
1631
+ function probablyInString(state, pos, quoteToken) {
1632
+ let node = language.syntaxTree(state).resolveInner(pos, -1);
1633
+ for (let i = 0; i < 5; i++) {
1634
+ if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
1635
+ return true;
1636
+ let parent = node.to == pos && node.parent;
1637
+ if (!parent)
1638
+ break;
1639
+ node = parent;
1640
+ }
1641
+ return false;
1642
+ }
1643
+
1394
1644
  /**
1395
1645
  Returns an extension that enables autocompletion.
1396
1646
  */
@@ -1435,13 +1685,19 @@ function completionStatus(state) {
1435
1685
  return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending"
1436
1686
  : cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null;
1437
1687
  }
1688
+ const completionArrayCache = new WeakMap;
1438
1689
  /**
1439
1690
  Returns the available completions as an array.
1440
1691
  */
1441
1692
  function currentCompletions(state) {
1442
1693
  var _a;
1443
1694
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1444
- return open ? open.options.map(o => o.completion) : [];
1695
+ if (!open)
1696
+ return [];
1697
+ let completions = completionArrayCache.get(open.options);
1698
+ if (!completions)
1699
+ completionArrayCache.set(open.options, completions = open.options.map(o => o.completion));
1700
+ return completions;
1445
1701
  }
1446
1702
  /**
1447
1703
  Return the currently selected completion, if any.
@@ -1451,24 +1707,46 @@ function selectedCompletion(state) {
1451
1707
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1452
1708
  return open ? open.options[open.selected].completion : null;
1453
1709
  }
1710
+ /**
1711
+ Returns the currently selected position in the active completion
1712
+ list, or null if no completions are active.
1713
+ */
1714
+ function selectedCompletionIndex(state) {
1715
+ var _a;
1716
+ let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1717
+ return open ? open.selected : null;
1718
+ }
1719
+ /**
1720
+ Create an effect that can be attached to a transaction to change
1721
+ the currently selected completion.
1722
+ */
1723
+ function setSelectedCompletion(index) {
1724
+ return setSelectedEffect.of(index);
1725
+ }
1454
1726
 
1455
1727
  exports.CompletionContext = CompletionContext;
1456
1728
  exports.acceptCompletion = acceptCompletion;
1457
1729
  exports.autocompletion = autocompletion;
1458
1730
  exports.clearSnippet = clearSnippet;
1731
+ exports.closeBrackets = closeBrackets;
1732
+ exports.closeBracketsKeymap = closeBracketsKeymap;
1459
1733
  exports.closeCompletion = closeCompletion;
1460
1734
  exports.completeAnyWord = completeAnyWord;
1461
1735
  exports.completeFromList = completeFromList;
1462
1736
  exports.completionKeymap = completionKeymap;
1463
1737
  exports.completionStatus = completionStatus;
1464
1738
  exports.currentCompletions = currentCompletions;
1739
+ exports.deleteBracketPair = deleteBracketPair;
1465
1740
  exports.ifIn = ifIn;
1466
1741
  exports.ifNotIn = ifNotIn;
1742
+ exports.insertBracket = insertBracket;
1467
1743
  exports.moveCompletionSelection = moveCompletionSelection;
1468
1744
  exports.nextSnippetField = nextSnippetField;
1469
1745
  exports.pickedCompletion = pickedCompletion;
1470
1746
  exports.prevSnippetField = prevSnippetField;
1471
1747
  exports.selectedCompletion = selectedCompletion;
1748
+ exports.selectedCompletionIndex = selectedCompletionIndex;
1749
+ exports.setSelectedCompletion = setSelectedCompletion;
1472
1750
  exports.snippet = snippet;
1473
1751
  exports.snippetCompletion = snippetCompletion;
1474
1752
  exports.snippetKeymap = snippetKeymap;