@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.js CHANGED
@@ -1,8 +1,6 @@
1
- import { Annotation, Facet, combineConfig, StateEffect, StateField, Prec, EditorSelection, Text, MapMode } from '@codemirror/state';
2
- import { Direction, logException, EditorView, ViewPlugin, Decoration, WidgetType, keymap } from '@codemirror/view';
3
- import { showTooltip, getTooltip } from '@codemirror/tooltip';
1
+ import { Annotation, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateEffect, StateField, Prec, Text, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
2
+ import { logException, Direction, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
4
3
  import { syntaxTree, indentUnit } from '@codemirror/language';
5
- import { codePointAt, codePointSize, fromCodePoint } from '@codemirror/text';
6
4
 
7
5
  /**
8
6
  An instance of this is passed to completion source functions.
@@ -98,10 +96,10 @@ completes them.
98
96
  */
99
97
  function completeFromList(list) {
100
98
  let options = list.map(o => typeof o == "string" ? { label: o } : o);
101
- let [span, match] = options.every(o => /^\w+$/.test(o.label)) ? [/\w*$/, /\w+$/] : prefixMatch(options);
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, span } : null;
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
- let apply = option.completion.apply || option.completion.label;
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
- changes: { from: result.from, to: result.to, insert: apply },
160
- selection: { anchor: result.from + apply.length },
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 option = open.options[open.selected];
458
- if (option.completion.info) {
459
- this.info = this.dom.appendChild(createInfoDialog(option, this.view));
460
- this.view.requestMeasure(this.placeInfo);
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 || prev.apply != opt.completion.apply)
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 && !selected; i++) {
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-expanded": "true",
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, span) {
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 (this.span && (from == to || this.span.test(tr.state.sliceDoc(from, to))))
746
- return new ActiveResult(this.source, explicitPos, this.result, from, to, this.span);
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), this.span);
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 && query.time - Date.now() > MinAbortTime) {
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), query.done.span && query.done.filter !== false ? ensureAnchor(query.done.span, true) : null);
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 apply it.
1204
- Snippets are written using syntax like this:
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, span: mapRE(re, s => "^" + s) };
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
- return open ? open.options.map(o => o.completion) : [];
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.19.13",
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.19.0",
30
- "@codemirror/state": "^0.19.4",
31
- "@codemirror/text": "^0.19.2",
32
- "@codemirror/tooltip": "^0.19.12",
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"