@codemirror/autocomplete 6.1.1 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 6.3.0 (2022-09-22)
2
+
3
+ ### New features
4
+
5
+ Close bracket configuration now supports a `stringPrefixes` property that can be used to allow autoclosing of prefixed strings.
6
+
7
+ ## 6.2.0 (2022-09-13)
8
+
9
+ ### New features
10
+
11
+ Autocompletion now takes an `interactionDelay` option that can be used to control the delay between the time where completion opens and the time where commands like `acceptCompletion` affect it.
12
+
1
13
  ## 6.1.1 (2022-09-08)
2
14
 
3
15
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -331,7 +331,8 @@ const completionConfig = state.Facet.define({
331
331
  aboveCursor: false,
332
332
  icons: true,
333
333
  addToOptions: [],
334
- compareCompletions: (a, b) => a.label.localeCompare(b.label)
334
+ compareCompletions: (a, b) => a.label.localeCompare(b.label),
335
+ interactionDelay: 75
335
336
  }, {
336
337
  defaultKeymap: (a, b) => a && b,
337
338
  closeOnBlur: (a, b) => a && b,
@@ -508,7 +509,7 @@ class CompletionTooltip {
508
509
  let sel = this.dom.querySelector("[aria-selected]");
509
510
  if (!sel || !this.info)
510
511
  return null;
511
- let win = this.dom.ownerDocument.defaultView;
512
+ let win = this.dom.ownerDocument.defaultView || window;
512
513
  let listRect = this.dom.getBoundingClientRect();
513
514
  let infoRect = this.info.getBoundingClientRect();
514
515
  let selRect = sel.getBoundingClientRect();
@@ -826,7 +827,6 @@ const completionState = state.StateField.define({
826
827
  ]
827
828
  });
828
829
 
829
- const CompletionInteractMargin = 75;
830
830
  /**
831
831
  Returns a command that moves the completion selection forward or
832
832
  backward by the given amount.
@@ -834,7 +834,8 @@ backward by the given amount.
834
834
  function moveCompletionSelection(forward, by = "option") {
835
835
  return (view$1) => {
836
836
  let cState = view$1.state.field(completionState, false);
837
- if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
837
+ if (!cState || !cState.open ||
838
+ Date.now() - cState.open.timestamp < view$1.state.facet(completionConfig).interactionDelay)
838
839
  return false;
839
840
  let step = 1, tooltip;
840
841
  if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
@@ -855,8 +856,8 @@ Accept the current completion.
855
856
  */
856
857
  const acceptCompletion = (view) => {
857
858
  let cState = view.state.field(completionState, false);
858
- if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin ||
859
- cState.open.selected < 0)
859
+ if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
860
+ Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
860
861
  return false;
861
862
  applyCompletion(view, cState.open.options[cState.open.selected]);
862
863
  return true;
@@ -1090,6 +1091,7 @@ const baseTheme = view.EditorView.baseTheme({
1090
1091
  verticalAlign: "text-top",
1091
1092
  width: 0,
1092
1093
  height: "1.15em",
1094
+ display: "inline-block",
1093
1095
  margin: "0 -0.7px -.7em",
1094
1096
  borderLeft: "1.4px dotted #888"
1095
1097
  },
@@ -1473,7 +1475,8 @@ const completeAnyWord = context => {
1473
1475
 
1474
1476
  const defaults = {
1475
1477
  brackets: ["(", "[", "{", "'", '"'],
1476
- before: ")]}:;>"
1478
+ before: ")]}:;>",
1479
+ stringPrefixes: []
1477
1480
  };
1478
1481
  const closeBracketEffect = state.StateEffect.define({
1479
1482
  map(value, mapping) {
@@ -1589,7 +1592,7 @@ function insertBracket(state$1, bracket) {
1589
1592
  for (let tok of tokens) {
1590
1593
  let closed = closing(state.codePointAt(tok, 0));
1591
1594
  if (bracket == tok)
1592
- return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
1595
+ return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
1593
1596
  : handleOpen(state$1, tok, closed, conf.before || defaults.before);
1594
1597
  if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
1595
1598
  return handleClose(state$1, tok, closed);
@@ -1644,13 +1647,14 @@ function handleClose(state$1, _open, close) {
1644
1647
  }
1645
1648
  // Handles cases where the open and close token are the same, and
1646
1649
  // possibly triple quotes (as in `"""abc"""`-style quoting).
1647
- function handleSame(state$1, token, allowTriple) {
1650
+ function handleSame(state$1, token, allowTriple, config) {
1651
+ let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
1648
1652
  let dont = null, changes = state$1.changeByRange(range => {
1649
1653
  if (!range.empty)
1650
1654
  return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
1651
1655
  effects: closeBracketEffect.of(range.to + token.length),
1652
1656
  range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
1653
- let pos = range.head, next = nextChar(state$1.doc, pos);
1657
+ let pos = range.head, next = nextChar(state$1.doc, pos), start;
1654
1658
  if (next == token) {
1655
1659
  if (nodeStart(state$1, pos)) {
1656
1660
  return { changes: { insert: token + token, from: pos },
@@ -1664,14 +1668,14 @@ function handleSame(state$1, token, allowTriple) {
1664
1668
  }
1665
1669
  }
1666
1670
  else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
1667
- nodeStart(state$1, pos - 2 * token.length)) {
1671
+ (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
1672
+ nodeStart(state$1, start)) {
1668
1673
  return { changes: { insert: token + token + token + token, from: pos },
1669
1674
  effects: closeBracketEffect.of(pos + token.length),
1670
1675
  range: state.EditorSelection.cursor(pos + token.length) };
1671
1676
  }
1672
1677
  else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
1673
- let prev = state$1.sliceDoc(pos - 1, pos);
1674
- if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
1678
+ if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
1675
1679
  return { changes: { insert: token + token, from: pos },
1676
1680
  effects: closeBracketEffect.of(pos + token.length),
1677
1681
  range: state.EditorSelection.cursor(pos + token.length) };
@@ -1687,12 +1691,15 @@ function nodeStart(state, pos) {
1687
1691
  let tree = language.syntaxTree(state).resolveInner(pos + 1);
1688
1692
  return tree.parent && tree.from == pos;
1689
1693
  }
1690
- function probablyInString(state, pos, quoteToken) {
1694
+ function probablyInString(state, pos, quoteToken, prefixes) {
1691
1695
  let node = language.syntaxTree(state).resolveInner(pos, -1);
1696
+ let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
1692
1697
  for (let i = 0; i < 5; i++) {
1693
- if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken) {
1698
+ let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
1699
+ let quotePos = start.indexOf(quoteToken);
1700
+ if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
1694
1701
  let first = node.firstChild;
1695
- while (first && first.from == node.from && first.to - first.from > quoteToken.length) {
1702
+ while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
1696
1703
  if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
1697
1704
  return false;
1698
1705
  first = first.firstChild;
@@ -1706,6 +1713,17 @@ function probablyInString(state, pos, quoteToken) {
1706
1713
  }
1707
1714
  return false;
1708
1715
  }
1716
+ function canStartStringAt(state$1, pos, prefixes) {
1717
+ let charCat = state$1.charCategorizer(pos);
1718
+ if (charCat(state$1.sliceDoc(pos - 1, pos)) != state.CharCategory.Word)
1719
+ return pos;
1720
+ for (let prefix of prefixes) {
1721
+ let start = pos - prefix.length;
1722
+ if (state$1.sliceDoc(start, pos) == prefix && charCat(state$1.sliceDoc(start - 1, start)) != state.CharCategory.Word)
1723
+ return start;
1724
+ }
1725
+ return -1;
1726
+ }
1709
1727
 
1710
1728
  /**
1711
1729
  Returns an extension that enables autocompletion.
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;
@@ -78,6 +79,13 @@ interface CompletionConfig {
78
79
  [`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
79
80
  */
80
81
  compareCompletions?: (a: Completion, b: Completion) => number;
82
+ /**
83
+ By default, commands relating to an open completion only take
84
+ effect 75 milliseconds after the completion opened, so that key
85
+ presses made before the user is aware of the tooltip don't go to
86
+ the tooltip. This option can be used to configure that delay.
87
+ */
88
+ interactionDelay?: number;
81
89
  }
82
90
 
83
91
  /**
@@ -397,6 +405,11 @@ interface CloseBracketConfig {
397
405
  whitespace. Defaults to `")]}:;>"`.
398
406
  */
399
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[];
400
413
  }
401
414
  /**
402
415
  Extension to enable bracket-closing behavior. When a closeable
package/dist/index.js CHANGED
@@ -327,7 +327,8 @@ const completionConfig = /*@__PURE__*/Facet.define({
327
327
  aboveCursor: false,
328
328
  icons: true,
329
329
  addToOptions: [],
330
- compareCompletions: (a, b) => a.label.localeCompare(b.label)
330
+ compareCompletions: (a, b) => a.label.localeCompare(b.label),
331
+ interactionDelay: 75
331
332
  }, {
332
333
  defaultKeymap: (a, b) => a && b,
333
334
  closeOnBlur: (a, b) => a && b,
@@ -504,7 +505,7 @@ class CompletionTooltip {
504
505
  let sel = this.dom.querySelector("[aria-selected]");
505
506
  if (!sel || !this.info)
506
507
  return null;
507
- let win = this.dom.ownerDocument.defaultView;
508
+ let win = this.dom.ownerDocument.defaultView || window;
508
509
  let listRect = this.dom.getBoundingClientRect();
509
510
  let infoRect = this.info.getBoundingClientRect();
510
511
  let selRect = sel.getBoundingClientRect();
@@ -822,7 +823,6 @@ const completionState = /*@__PURE__*/StateField.define({
822
823
  ]
823
824
  });
824
825
 
825
- const CompletionInteractMargin = 75;
826
826
  /**
827
827
  Returns a command that moves the completion selection forward or
828
828
  backward by the given amount.
@@ -830,7 +830,8 @@ backward by the given amount.
830
830
  function moveCompletionSelection(forward, by = "option") {
831
831
  return (view) => {
832
832
  let cState = view.state.field(completionState, false);
833
- if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
833
+ if (!cState || !cState.open ||
834
+ Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
834
835
  return false;
835
836
  let step = 1, tooltip;
836
837
  if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
@@ -851,8 +852,8 @@ Accept the current completion.
851
852
  */
852
853
  const acceptCompletion = (view) => {
853
854
  let cState = view.state.field(completionState, false);
854
- if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin ||
855
- cState.open.selected < 0)
855
+ if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
856
+ Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
856
857
  return false;
857
858
  applyCompletion(view, cState.open.options[cState.open.selected]);
858
859
  return true;
@@ -1086,6 +1087,7 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
1086
1087
  verticalAlign: "text-top",
1087
1088
  width: 0,
1088
1089
  height: "1.15em",
1090
+ display: "inline-block",
1089
1091
  margin: "0 -0.7px -.7em",
1090
1092
  borderLeft: "1.4px dotted #888"
1091
1093
  },
@@ -1469,7 +1471,8 @@ const completeAnyWord = context => {
1469
1471
 
1470
1472
  const defaults = {
1471
1473
  brackets: ["(", "[", "{", "'", '"'],
1472
- before: ")]}:;>"
1474
+ before: ")]}:;>",
1475
+ stringPrefixes: []
1473
1476
  };
1474
1477
  const closeBracketEffect = /*@__PURE__*/StateEffect.define({
1475
1478
  map(value, mapping) {
@@ -1585,7 +1588,7 @@ function insertBracket(state, bracket) {
1585
1588
  for (let tok of tokens) {
1586
1589
  let closed = closing(codePointAt(tok, 0));
1587
1590
  if (bracket == tok)
1588
- return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1)
1591
+ return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
1589
1592
  : handleOpen(state, tok, closed, conf.before || defaults.before);
1590
1593
  if (bracket == closed && closedBracketAt(state, state.selection.main.from))
1591
1594
  return handleClose(state, tok, closed);
@@ -1640,13 +1643,14 @@ function handleClose(state, _open, close) {
1640
1643
  }
1641
1644
  // Handles cases where the open and close token are the same, and
1642
1645
  // possibly triple quotes (as in `"""abc"""`-style quoting).
1643
- function handleSame(state, token, allowTriple) {
1646
+ function handleSame(state, token, allowTriple, config) {
1647
+ let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
1644
1648
  let dont = null, changes = state.changeByRange(range => {
1645
1649
  if (!range.empty)
1646
1650
  return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
1647
1651
  effects: closeBracketEffect.of(range.to + token.length),
1648
1652
  range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
1649
- let pos = range.head, next = nextChar(state.doc, pos);
1653
+ let pos = range.head, next = nextChar(state.doc, pos), start;
1650
1654
  if (next == token) {
1651
1655
  if (nodeStart(state, pos)) {
1652
1656
  return { changes: { insert: token + token, from: pos },
@@ -1660,14 +1664,14 @@ function handleSame(state, token, allowTriple) {
1660
1664
  }
1661
1665
  }
1662
1666
  else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
1663
- nodeStart(state, pos - 2 * token.length)) {
1667
+ (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
1668
+ nodeStart(state, start)) {
1664
1669
  return { changes: { insert: token + token + token + token, from: pos },
1665
1670
  effects: closeBracketEffect.of(pos + token.length),
1666
1671
  range: EditorSelection.cursor(pos + token.length) };
1667
1672
  }
1668
1673
  else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
1669
- let prev = state.sliceDoc(pos - 1, pos);
1670
- if (prev != token && state.charCategorizer(pos)(prev) != CharCategory.Word && !probablyInString(state, pos, token))
1674
+ if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
1671
1675
  return { changes: { insert: token + token, from: pos },
1672
1676
  effects: closeBracketEffect.of(pos + token.length),
1673
1677
  range: EditorSelection.cursor(pos + token.length) };
@@ -1683,12 +1687,15 @@ function nodeStart(state, pos) {
1683
1687
  let tree = syntaxTree(state).resolveInner(pos + 1);
1684
1688
  return tree.parent && tree.from == pos;
1685
1689
  }
1686
- function probablyInString(state, pos, quoteToken) {
1690
+ function probablyInString(state, pos, quoteToken, prefixes) {
1687
1691
  let node = syntaxTree(state).resolveInner(pos, -1);
1692
+ let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
1688
1693
  for (let i = 0; i < 5; i++) {
1689
- if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken) {
1694
+ let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
1695
+ let quotePos = start.indexOf(quoteToken);
1696
+ if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
1690
1697
  let first = node.firstChild;
1691
- while (first && first.from == node.from && first.to - first.from > quoteToken.length) {
1698
+ while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
1692
1699
  if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
1693
1700
  return false;
1694
1701
  first = first.firstChild;
@@ -1702,6 +1709,17 @@ function probablyInString(state, pos, quoteToken) {
1702
1709
  }
1703
1710
  return false;
1704
1711
  }
1712
+ function canStartStringAt(state, pos, prefixes) {
1713
+ let charCat = state.charCategorizer(pos);
1714
+ if (charCat(state.sliceDoc(pos - 1, pos)) != CharCategory.Word)
1715
+ return pos;
1716
+ for (let prefix of prefixes) {
1717
+ let start = pos - prefix.length;
1718
+ if (state.sliceDoc(start, pos) == prefix && charCat(state.sliceDoc(start - 1, start)) != CharCategory.Word)
1719
+ return start;
1720
+ }
1721
+ return -1;
1722
+ }
1705
1723
 
1706
1724
  /**
1707
1725
  Returns an extension that enables autocompletion.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.1.1",
3
+ "version": "6.3.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",