@codemirror/autocomplete 6.2.0 → 6.3.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/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 6.3.1 (2022-11-14)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a regression where transactions for picking a completion (without custom `apply` method) no longer had the `pickedCompletion` annotation.
6
+
7
+ Reduce flickering for completion sources without `validFor` info by temporarily showing a disabled tooltip while the completion updates.
8
+
9
+ Make sure completion info tooltips are kept within the space provided by the `tooltipSpace` option.
10
+
11
+ ## 6.3.0 (2022-09-22)
12
+
13
+ ### New features
14
+
15
+ Close bracket configuration now supports a `stringPrefixes` property that can be used to allow autoclosing of prefixed strings.
16
+
1
17
  ## 6.2.0 (2022-09-13)
2
18
 
3
19
  ### New features
package/dist/index.cjs CHANGED
@@ -179,7 +179,7 @@ function applyCompletion(view, option) {
179
179
  const apply = option.completion.apply || option.completion.label;
180
180
  let result = option.source;
181
181
  if (typeof apply == "string")
182
- view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
182
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
183
183
  else
184
184
  apply(view, option.completion, result.from, result.to);
185
185
  }
@@ -414,6 +414,7 @@ class CompletionTooltip {
414
414
  write: (pos) => this.positionInfo(pos),
415
415
  key: this
416
416
  };
417
+ this.space = null;
417
418
  let cState = view.state.field(stateField);
418
419
  let { options, selected } = cState.open;
419
420
  let config = view.state.facet(completionConfig);
@@ -439,10 +440,17 @@ class CompletionTooltip {
439
440
  }
440
441
  mount() { this.updateSel(); }
441
442
  update(update) {
442
- if (update.state.field(this.stateField) != update.startState.field(this.stateField))
443
+ var _a, _b, _c;
444
+ let cState = update.state.field(this.stateField);
445
+ let prevState = update.startState.field(this.stateField);
446
+ if (cState != prevState) {
443
447
  this.updateSel();
448
+ if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
449
+ this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
450
+ }
444
451
  }
445
- positioned() {
452
+ positioned(space) {
453
+ this.space = space;
446
454
  if (this.info)
447
455
  this.view.requestMeasure(this.placeInfo);
448
456
  }
@@ -509,27 +517,32 @@ class CompletionTooltip {
509
517
  let sel = this.dom.querySelector("[aria-selected]");
510
518
  if (!sel || !this.info)
511
519
  return null;
512
- let win = this.dom.ownerDocument.defaultView;
513
520
  let listRect = this.dom.getBoundingClientRect();
514
521
  let infoRect = this.info.getBoundingClientRect();
515
522
  let selRect = sel.getBoundingClientRect();
516
- if (selRect.top > Math.min(win.innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
523
+ let space = this.space;
524
+ if (!space) {
525
+ let win = this.dom.ownerDocument.defaultView || window;
526
+ space = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight };
527
+ }
528
+ if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
529
+ selRect.bottom < Math.max(space.top, listRect.top) + 10)
517
530
  return null;
518
531
  let rtl = this.view.textDirection == view.Direction.RTL, left = rtl, narrow = false, maxWidth;
519
532
  let top = "", bottom = "";
520
- let spaceLeft = listRect.left, spaceRight = win.innerWidth - listRect.right;
533
+ let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
521
534
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
522
535
  left = false;
523
536
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
524
537
  left = true;
525
538
  if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
526
- top = (Math.max(0, Math.min(selRect.top, win.innerHeight - infoRect.height)) - listRect.top) + "px";
539
+ top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
527
540
  maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
528
541
  }
529
542
  else {
530
543
  narrow = true;
531
- maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : win.innerWidth - listRect.left) - 30 /* Info.Margin */) + "px";
532
- let spaceBelow = win.innerHeight - listRect.bottom;
544
+ maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
545
+ let spaceBelow = space.bottom - listRect.bottom;
533
546
  if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
534
547
  top = (selRect.bottom - listRect.top) + "px";
535
548
  else // Above it
@@ -638,21 +651,24 @@ function sortOptions(active, state) {
638
651
  return result;
639
652
  }
640
653
  class CompletionDialog {
641
- constructor(options, attrs, tooltip, timestamp, selected) {
654
+ constructor(options, attrs, tooltip, timestamp, selected, disabled) {
642
655
  this.options = options;
643
656
  this.attrs = attrs;
644
657
  this.tooltip = tooltip;
645
658
  this.timestamp = timestamp;
646
659
  this.selected = selected;
660
+ this.disabled = disabled;
647
661
  }
648
662
  setSelected(selected, id) {
649
663
  return selected == this.selected || selected >= this.options.length ? this
650
- : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
664
+ : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
651
665
  }
652
666
  static build(active, state, id, prev, conf) {
653
667
  let options = sortOptions(active, state);
654
- if (!options.length)
655
- return null;
668
+ if (!options.length) {
669
+ return prev && active.some(a => a.state == 1 /* State.Pending */) ?
670
+ new CompletionDialog(prev.options, prev.attrs, prev.tooltip, prev.timestamp, prev.selected, true) : null;
671
+ }
656
672
  let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
657
673
  if (prev && prev.selected != selected && prev.selected != -1) {
658
674
  let selectedValue = prev.options[prev.selected].completion;
@@ -666,10 +682,10 @@ class CompletionDialog {
666
682
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
667
683
  create: completionTooltip(completionState),
668
684
  above: conf.aboveCursor,
669
- }, prev ? prev.timestamp : Date.now(), selected);
685
+ }, prev ? prev.timestamp : Date.now(), selected, false);
670
686
  }
671
687
  map(changes) {
672
- return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected);
688
+ return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
673
689
  }
674
690
  }
675
691
  class CompletionState {
@@ -692,9 +708,12 @@ class CompletionState {
692
708
  });
693
709
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
694
710
  active = this.active;
695
- let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
696
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
697
- : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
711
+ let open = this.open;
712
+ if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
713
+ !sameResults(active, this.active))
714
+ open = CompletionDialog.build(active, state, this.id, this.open, conf);
715
+ else if (open && tr.docChanged)
716
+ open = open.map(tr.changes);
698
717
  if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
699
718
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
700
719
  for (let effect of tr.effects)
@@ -834,7 +853,7 @@ backward by the given amount.
834
853
  function moveCompletionSelection(forward, by = "option") {
835
854
  return (view$1) => {
836
855
  let cState = view$1.state.field(completionState, false);
837
- if (!cState || !cState.open ||
856
+ if (!cState || !cState.open || cState.open.disabled ||
838
857
  Date.now() - cState.open.timestamp < view$1.state.facet(completionConfig).interactionDelay)
839
858
  return false;
840
859
  let step = 1, tooltip;
@@ -859,7 +878,8 @@ const acceptCompletion = (view) => {
859
878
  if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
860
879
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
861
880
  return false;
862
- applyCompletion(view, cState.open.options[cState.open.selected]);
881
+ if (!cState.open.disabled)
882
+ applyCompletion(view, cState.open.options[cState.open.selected]);
863
883
  return true;
864
884
  };
865
885
  /**
@@ -1064,10 +1084,16 @@ const baseTheme = view.EditorView.baseTheme({
1064
1084
  background: "#17c",
1065
1085
  color: "white",
1066
1086
  },
1087
+ "&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
1088
+ background: "#777",
1089
+ },
1067
1090
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
1068
1091
  background: "#347",
1069
1092
  color: "white",
1070
1093
  },
1094
+ "&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
1095
+ background: "#444",
1096
+ },
1071
1097
  ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
1072
1098
  content: '"···"',
1073
1099
  opacity: 0.5,
@@ -1091,6 +1117,7 @@ const baseTheme = view.EditorView.baseTheme({
1091
1117
  verticalAlign: "text-top",
1092
1118
  width: 0,
1093
1119
  height: "1.15em",
1120
+ display: "inline-block",
1094
1121
  margin: "0 -0.7px -.7em",
1095
1122
  borderLeft: "1.4px dotted #888"
1096
1123
  },
@@ -1474,7 +1501,8 @@ const completeAnyWord = context => {
1474
1501
 
1475
1502
  const defaults = {
1476
1503
  brackets: ["(", "[", "{", "'", '"'],
1477
- before: ")]}:;>"
1504
+ before: ")]}:;>",
1505
+ stringPrefixes: []
1478
1506
  };
1479
1507
  const closeBracketEffect = state.StateEffect.define({
1480
1508
  map(value, mapping) {
@@ -1590,7 +1618,7 @@ function insertBracket(state$1, bracket) {
1590
1618
  for (let tok of tokens) {
1591
1619
  let closed = closing(state.codePointAt(tok, 0));
1592
1620
  if (bracket == tok)
1593
- return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
1621
+ return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
1594
1622
  : handleOpen(state$1, tok, closed, conf.before || defaults.before);
1595
1623
  if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
1596
1624
  return handleClose(state$1, tok, closed);
@@ -1645,13 +1673,14 @@ function handleClose(state$1, _open, close) {
1645
1673
  }
1646
1674
  // Handles cases where the open and close token are the same, and
1647
1675
  // possibly triple quotes (as in `"""abc"""`-style quoting).
1648
- function handleSame(state$1, token, allowTriple) {
1676
+ function handleSame(state$1, token, allowTriple, config) {
1677
+ let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
1649
1678
  let dont = null, changes = state$1.changeByRange(range => {
1650
1679
  if (!range.empty)
1651
1680
  return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
1652
1681
  effects: closeBracketEffect.of(range.to + token.length),
1653
1682
  range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
1654
- let pos = range.head, next = nextChar(state$1.doc, pos);
1683
+ let pos = range.head, next = nextChar(state$1.doc, pos), start;
1655
1684
  if (next == token) {
1656
1685
  if (nodeStart(state$1, pos)) {
1657
1686
  return { changes: { insert: token + token, from: pos },
@@ -1665,14 +1694,14 @@ function handleSame(state$1, token, allowTriple) {
1665
1694
  }
1666
1695
  }
1667
1696
  else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
1668
- nodeStart(state$1, pos - 2 * token.length)) {
1697
+ (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
1698
+ nodeStart(state$1, start)) {
1669
1699
  return { changes: { insert: token + token + token + token, from: pos },
1670
1700
  effects: closeBracketEffect.of(pos + token.length),
1671
1701
  range: state.EditorSelection.cursor(pos + token.length) };
1672
1702
  }
1673
1703
  else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
1674
- let prev = state$1.sliceDoc(pos - 1, pos);
1675
- if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
1704
+ if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
1676
1705
  return { changes: { insert: token + token, from: pos },
1677
1706
  effects: closeBracketEffect.of(pos + token.length),
1678
1707
  range: state.EditorSelection.cursor(pos + token.length) };
@@ -1688,12 +1717,15 @@ function nodeStart(state, pos) {
1688
1717
  let tree = language.syntaxTree(state).resolveInner(pos + 1);
1689
1718
  return tree.parent && tree.from == pos;
1690
1719
  }
1691
- function probablyInString(state, pos, quoteToken) {
1720
+ function probablyInString(state, pos, quoteToken, prefixes) {
1692
1721
  let node = language.syntaxTree(state).resolveInner(pos, -1);
1722
+ let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
1693
1723
  for (let i = 0; i < 5; i++) {
1694
- if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken) {
1724
+ let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
1725
+ let quotePos = start.indexOf(quoteToken);
1726
+ if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
1695
1727
  let first = node.firstChild;
1696
- while (first && first.from == node.from && first.to - first.from > quoteToken.length) {
1728
+ while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
1697
1729
  if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
1698
1730
  return false;
1699
1731
  first = first.firstChild;
@@ -1707,6 +1739,17 @@ function probablyInString(state, pos, quoteToken) {
1707
1739
  }
1708
1740
  return false;
1709
1741
  }
1742
+ function canStartStringAt(state$1, pos, prefixes) {
1743
+ let charCat = state$1.charCategorizer(pos);
1744
+ if (charCat(state$1.sliceDoc(pos - 1, pos)) != state.CharCategory.Word)
1745
+ return pos;
1746
+ for (let prefix of prefixes) {
1747
+ let start = pos - prefix.length;
1748
+ if (state$1.sliceDoc(start, pos) == prefix && charCat(state$1.sliceDoc(start - 1, start)) != state.CharCategory.Word)
1749
+ return start;
1750
+ }
1751
+ return -1;
1752
+ }
1710
1753
 
1711
1754
  /**
1712
1755
  Returns an extension that enables autocompletion.
@@ -1759,7 +1802,7 @@ Returns the available completions as an array.
1759
1802
  function currentCompletions(state) {
1760
1803
  var _a;
1761
1804
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1762
- if (!open)
1805
+ if (!open || open.disabled)
1763
1806
  return [];
1764
1807
  let completions = completionArrayCache.get(open.options);
1765
1808
  if (!completions)
@@ -1772,7 +1815,7 @@ Return the currently selected completion, if any.
1772
1815
  function selectedCompletion(state) {
1773
1816
  var _a;
1774
1817
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1775
- return open && open.selected >= 0 ? open.options[open.selected].completion : null;
1818
+ return open && !open.disabled && open.selected >= 0 ? open.options[open.selected].completion : null;
1776
1819
  }
1777
1820
  /**
1778
1821
  Returns the currently selected position in the active completion
@@ -1781,7 +1824,7 @@ list, or null if no completions are active.
1781
1824
  function selectedCompletionIndex(state) {
1782
1825
  var _a;
1783
1826
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1784
- return open && open.selected >= 0 ? open.selected : null;
1827
+ return open && !open.disabled && open.selected >= 0 ? open.selected : null;
1785
1828
  }
1786
1829
  /**
1787
1830
  Create an effect that can be attached to a transaction to change
package/dist/index.d.ts CHANGED
@@ -66,7 +66,8 @@ interface CompletionConfig {
66
66
  completion, and should produce a DOM node to show. `position`
67
67
  determines where in the DOM the result appears, relative to
68
68
  other added widgets and the standard content. The default icons
69
- have position 20, the label position 50, and the detail position 70.
69
+ have position 20, the label position 50, and the detail position
70
+ 80.
70
71
  */
71
72
  addToOptions?: {
72
73
  render: (completion: Completion, state: EditorState) => Node | null;
@@ -404,6 +405,11 @@ interface CloseBracketConfig {
404
405
  whitespace. Defaults to `")]}:;>"`.
405
406
  */
406
407
  before?: string;
408
+ /**
409
+ When determining whether a given node may be a string, recognize
410
+ these prefixes before the opening quote.
411
+ */
412
+ stringPrefixes?: string[];
407
413
  }
408
414
  /**
409
415
  Extension to enable bracket-closing behavior. When a closeable
package/dist/index.js CHANGED
@@ -175,7 +175,7 @@ function applyCompletion(view, option) {
175
175
  const apply = option.completion.apply || option.completion.label;
176
176
  let result = option.source;
177
177
  if (typeof apply == "string")
178
- view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
178
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
179
179
  else
180
180
  apply(view, option.completion, result.from, result.to);
181
181
  }
@@ -410,6 +410,7 @@ class CompletionTooltip {
410
410
  write: (pos) => this.positionInfo(pos),
411
411
  key: this
412
412
  };
413
+ this.space = null;
413
414
  let cState = view.state.field(stateField);
414
415
  let { options, selected } = cState.open;
415
416
  let config = view.state.facet(completionConfig);
@@ -435,10 +436,17 @@ class CompletionTooltip {
435
436
  }
436
437
  mount() { this.updateSel(); }
437
438
  update(update) {
438
- if (update.state.field(this.stateField) != update.startState.field(this.stateField))
439
+ var _a, _b, _c;
440
+ let cState = update.state.field(this.stateField);
441
+ let prevState = update.startState.field(this.stateField);
442
+ if (cState != prevState) {
439
443
  this.updateSel();
444
+ if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
445
+ this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
446
+ }
440
447
  }
441
- positioned() {
448
+ positioned(space) {
449
+ this.space = space;
442
450
  if (this.info)
443
451
  this.view.requestMeasure(this.placeInfo);
444
452
  }
@@ -505,27 +513,32 @@ class CompletionTooltip {
505
513
  let sel = this.dom.querySelector("[aria-selected]");
506
514
  if (!sel || !this.info)
507
515
  return null;
508
- let win = this.dom.ownerDocument.defaultView;
509
516
  let listRect = this.dom.getBoundingClientRect();
510
517
  let infoRect = this.info.getBoundingClientRect();
511
518
  let selRect = sel.getBoundingClientRect();
512
- if (selRect.top > Math.min(win.innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
519
+ let space = this.space;
520
+ if (!space) {
521
+ let win = this.dom.ownerDocument.defaultView || window;
522
+ space = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight };
523
+ }
524
+ if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
525
+ selRect.bottom < Math.max(space.top, listRect.top) + 10)
513
526
  return null;
514
527
  let rtl = this.view.textDirection == Direction.RTL, left = rtl, narrow = false, maxWidth;
515
528
  let top = "", bottom = "";
516
- let spaceLeft = listRect.left, spaceRight = win.innerWidth - listRect.right;
529
+ let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
517
530
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
518
531
  left = false;
519
532
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
520
533
  left = true;
521
534
  if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
522
- top = (Math.max(0, Math.min(selRect.top, win.innerHeight - infoRect.height)) - listRect.top) + "px";
535
+ top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
523
536
  maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
524
537
  }
525
538
  else {
526
539
  narrow = true;
527
- maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : win.innerWidth - listRect.left) - 30 /* Info.Margin */) + "px";
528
- let spaceBelow = win.innerHeight - listRect.bottom;
540
+ maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
541
+ let spaceBelow = space.bottom - listRect.bottom;
529
542
  if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
530
543
  top = (selRect.bottom - listRect.top) + "px";
531
544
  else // Above it
@@ -634,21 +647,24 @@ function sortOptions(active, state) {
634
647
  return result;
635
648
  }
636
649
  class CompletionDialog {
637
- constructor(options, attrs, tooltip, timestamp, selected) {
650
+ constructor(options, attrs, tooltip, timestamp, selected, disabled) {
638
651
  this.options = options;
639
652
  this.attrs = attrs;
640
653
  this.tooltip = tooltip;
641
654
  this.timestamp = timestamp;
642
655
  this.selected = selected;
656
+ this.disabled = disabled;
643
657
  }
644
658
  setSelected(selected, id) {
645
659
  return selected == this.selected || selected >= this.options.length ? this
646
- : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
660
+ : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
647
661
  }
648
662
  static build(active, state, id, prev, conf) {
649
663
  let options = sortOptions(active, state);
650
- if (!options.length)
651
- return null;
664
+ if (!options.length) {
665
+ return prev && active.some(a => a.state == 1 /* State.Pending */) ?
666
+ new CompletionDialog(prev.options, prev.attrs, prev.tooltip, prev.timestamp, prev.selected, true) : null;
667
+ }
652
668
  let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
653
669
  if (prev && prev.selected != selected && prev.selected != -1) {
654
670
  let selectedValue = prev.options[prev.selected].completion;
@@ -662,10 +678,10 @@ class CompletionDialog {
662
678
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
663
679
  create: completionTooltip(completionState),
664
680
  above: conf.aboveCursor,
665
- }, prev ? prev.timestamp : Date.now(), selected);
681
+ }, prev ? prev.timestamp : Date.now(), selected, false);
666
682
  }
667
683
  map(changes) {
668
- return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected);
684
+ return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
669
685
  }
670
686
  }
671
687
  class CompletionState {
@@ -688,9 +704,12 @@ class CompletionState {
688
704
  });
689
705
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
690
706
  active = this.active;
691
- let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
692
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
693
- : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
707
+ let open = this.open;
708
+ if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
709
+ !sameResults(active, this.active))
710
+ open = CompletionDialog.build(active, state, this.id, this.open, conf);
711
+ else if (open && tr.docChanged)
712
+ open = open.map(tr.changes);
694
713
  if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
695
714
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
696
715
  for (let effect of tr.effects)
@@ -830,7 +849,7 @@ backward by the given amount.
830
849
  function moveCompletionSelection(forward, by = "option") {
831
850
  return (view) => {
832
851
  let cState = view.state.field(completionState, false);
833
- if (!cState || !cState.open ||
852
+ if (!cState || !cState.open || cState.open.disabled ||
834
853
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
835
854
  return false;
836
855
  let step = 1, tooltip;
@@ -855,7 +874,8 @@ const acceptCompletion = (view) => {
855
874
  if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
856
875
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
857
876
  return false;
858
- applyCompletion(view, cState.open.options[cState.open.selected]);
877
+ if (!cState.open.disabled)
878
+ applyCompletion(view, cState.open.options[cState.open.selected]);
859
879
  return true;
860
880
  };
861
881
  /**
@@ -1060,10 +1080,16 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
1060
1080
  background: "#17c",
1061
1081
  color: "white",
1062
1082
  },
1083
+ "&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
1084
+ background: "#777",
1085
+ },
1063
1086
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
1064
1087
  background: "#347",
1065
1088
  color: "white",
1066
1089
  },
1090
+ "&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
1091
+ background: "#444",
1092
+ },
1067
1093
  ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
1068
1094
  content: '"···"',
1069
1095
  opacity: 0.5,
@@ -1087,6 +1113,7 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
1087
1113
  verticalAlign: "text-top",
1088
1114
  width: 0,
1089
1115
  height: "1.15em",
1116
+ display: "inline-block",
1090
1117
  margin: "0 -0.7px -.7em",
1091
1118
  borderLeft: "1.4px dotted #888"
1092
1119
  },
@@ -1470,7 +1497,8 @@ const completeAnyWord = context => {
1470
1497
 
1471
1498
  const defaults = {
1472
1499
  brackets: ["(", "[", "{", "'", '"'],
1473
- before: ")]}:;>"
1500
+ before: ")]}:;>",
1501
+ stringPrefixes: []
1474
1502
  };
1475
1503
  const closeBracketEffect = /*@__PURE__*/StateEffect.define({
1476
1504
  map(value, mapping) {
@@ -1586,7 +1614,7 @@ function insertBracket(state, bracket) {
1586
1614
  for (let tok of tokens) {
1587
1615
  let closed = closing(codePointAt(tok, 0));
1588
1616
  if (bracket == tok)
1589
- return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1)
1617
+ return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
1590
1618
  : handleOpen(state, tok, closed, conf.before || defaults.before);
1591
1619
  if (bracket == closed && closedBracketAt(state, state.selection.main.from))
1592
1620
  return handleClose(state, tok, closed);
@@ -1641,13 +1669,14 @@ function handleClose(state, _open, close) {
1641
1669
  }
1642
1670
  // Handles cases where the open and close token are the same, and
1643
1671
  // possibly triple quotes (as in `"""abc"""`-style quoting).
1644
- function handleSame(state, token, allowTriple) {
1672
+ function handleSame(state, token, allowTriple, config) {
1673
+ let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
1645
1674
  let dont = null, changes = state.changeByRange(range => {
1646
1675
  if (!range.empty)
1647
1676
  return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
1648
1677
  effects: closeBracketEffect.of(range.to + token.length),
1649
1678
  range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
1650
- let pos = range.head, next = nextChar(state.doc, pos);
1679
+ let pos = range.head, next = nextChar(state.doc, pos), start;
1651
1680
  if (next == token) {
1652
1681
  if (nodeStart(state, pos)) {
1653
1682
  return { changes: { insert: token + token, from: pos },
@@ -1661,14 +1690,14 @@ function handleSame(state, token, allowTriple) {
1661
1690
  }
1662
1691
  }
1663
1692
  else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
1664
- nodeStart(state, pos - 2 * token.length)) {
1693
+ (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
1694
+ nodeStart(state, start)) {
1665
1695
  return { changes: { insert: token + token + token + token, from: pos },
1666
1696
  effects: closeBracketEffect.of(pos + token.length),
1667
1697
  range: EditorSelection.cursor(pos + token.length) };
1668
1698
  }
1669
1699
  else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
1670
- let prev = state.sliceDoc(pos - 1, pos);
1671
- if (prev != token && state.charCategorizer(pos)(prev) != CharCategory.Word && !probablyInString(state, pos, token))
1700
+ if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
1672
1701
  return { changes: { insert: token + token, from: pos },
1673
1702
  effects: closeBracketEffect.of(pos + token.length),
1674
1703
  range: EditorSelection.cursor(pos + token.length) };
@@ -1684,12 +1713,15 @@ function nodeStart(state, pos) {
1684
1713
  let tree = syntaxTree(state).resolveInner(pos + 1);
1685
1714
  return tree.parent && tree.from == pos;
1686
1715
  }
1687
- function probablyInString(state, pos, quoteToken) {
1716
+ function probablyInString(state, pos, quoteToken, prefixes) {
1688
1717
  let node = syntaxTree(state).resolveInner(pos, -1);
1718
+ let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
1689
1719
  for (let i = 0; i < 5; i++) {
1690
- if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken) {
1720
+ let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
1721
+ let quotePos = start.indexOf(quoteToken);
1722
+ if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
1691
1723
  let first = node.firstChild;
1692
- while (first && first.from == node.from && first.to - first.from > quoteToken.length) {
1724
+ while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
1693
1725
  if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
1694
1726
  return false;
1695
1727
  first = first.firstChild;
@@ -1703,6 +1735,17 @@ function probablyInString(state, pos, quoteToken) {
1703
1735
  }
1704
1736
  return false;
1705
1737
  }
1738
+ function canStartStringAt(state, pos, prefixes) {
1739
+ let charCat = state.charCategorizer(pos);
1740
+ if (charCat(state.sliceDoc(pos - 1, pos)) != CharCategory.Word)
1741
+ return pos;
1742
+ for (let prefix of prefixes) {
1743
+ let start = pos - prefix.length;
1744
+ if (state.sliceDoc(start, pos) == prefix && charCat(state.sliceDoc(start - 1, start)) != CharCategory.Word)
1745
+ return start;
1746
+ }
1747
+ return -1;
1748
+ }
1706
1749
 
1707
1750
  /**
1708
1751
  Returns an extension that enables autocompletion.
@@ -1755,7 +1798,7 @@ Returns the available completions as an array.
1755
1798
  function currentCompletions(state) {
1756
1799
  var _a;
1757
1800
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1758
- if (!open)
1801
+ if (!open || open.disabled)
1759
1802
  return [];
1760
1803
  let completions = completionArrayCache.get(open.options);
1761
1804
  if (!completions)
@@ -1768,7 +1811,7 @@ Return the currently selected completion, if any.
1768
1811
  function selectedCompletion(state) {
1769
1812
  var _a;
1770
1813
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1771
- return open && open.selected >= 0 ? open.options[open.selected].completion : null;
1814
+ return open && !open.disabled && open.selected >= 0 ? open.options[open.selected].completion : null;
1772
1815
  }
1773
1816
  /**
1774
1817
  Returns the currently selected position in the active completion
@@ -1777,7 +1820,7 @@ list, or null if no completions are active.
1777
1820
  function selectedCompletionIndex(state) {
1778
1821
  var _a;
1779
1822
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1780
- return open && open.selected >= 0 ? open.selected : null;
1823
+ return open && !open.disabled && open.selected >= 0 ? open.selected : null;
1781
1824
  }
1782
1825
  /**
1783
1826
  Create an effect that can be attached to a transaction to change
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.2.0",
3
+ "version": "6.3.1",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@codemirror/language": "^6.0.0",
30
30
  "@codemirror/state": "^6.0.0",
31
- "@codemirror/view": "^6.0.0",
31
+ "@codemirror/view": "^6.5.0",
32
32
  "@lezer/common": "^1.0.0"
33
33
  },
34
34
  "peerDependencies": {