@codemirror/autocomplete 0.19.7 → 0.19.11

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,35 @@
1
+ ## 0.19.11 (2022-01-11)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug that caused page up/down to only move the selection by two options in the completion tooltip.
6
+
7
+ ## 0.19.10 (2022-01-05)
8
+
9
+ ### Bug fixes
10
+
11
+ Make sure the info tooltip is hidden when the selected option is scrolled out of view.
12
+
13
+ Fix a bug in the completion ranking that would sometimes give options that match the input by word start chars higher scores than appropriate.
14
+
15
+ Options are now sorted (ascending) by length when their match score is otherwise identical.
16
+
17
+ ## 0.19.9 (2021-11-26)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix an issue where info tooltips would be visible in an inappropriate position when there was no room to place them properly.
22
+
23
+ ## 0.19.8 (2021-11-17)
24
+
25
+ ### Bug fixes
26
+
27
+ Give the completion tooltip a minimal width, and show ellipsis when completions overflow the tooltip width.
28
+
29
+ ### New features
30
+
31
+ `autocompletion` now accepts an `aboveCursor` option to make the completion tooltip show up above the cursor.
32
+
1
33
  ## 0.19.7 (2021-11-16)
2
34
 
3
35
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -246,7 +246,7 @@ class FuzzyMatcher {
246
246
  let byWordTo = 0, byWordFolded = false;
247
247
  // If we've found a partial adjacent match, these track its state
248
248
  let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1;
249
- let hasLower = /[a-z]/.test(word);
249
+ let hasLower = /[a-z]/.test(word), wordAdjacent = true;
250
250
  // Go over the option's text, scanning for the various kinds of matches
251
251
  for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* NonWord */; i < e && byWordTo < len;) {
252
252
  let next = text.codePointAt(word, i);
@@ -268,26 +268,30 @@ class FuzzyMatcher {
268
268
  let ch, type = next < 0xff
269
269
  ? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Lower */ : next >= 65 && next <= 90 ? 1 /* Upper */ : 0 /* NonWord */)
270
270
  : ((ch = text.fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Upper */ : ch != ch.toUpperCase() ? 2 /* Lower */ : 0 /* NonWord */);
271
- if ((type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) &&
272
- (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true))))
273
- byWord[byWordTo++] = i;
271
+ if (!i || type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) {
272
+ if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
273
+ byWord[byWordTo++] = i;
274
+ else if (byWord.length)
275
+ wordAdjacent = false;
276
+ }
274
277
  prevType = type;
275
278
  i += text.codePointSize(next);
276
279
  }
277
- if (byWordTo == len && byWord[0] == 0)
280
+ if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
278
281
  return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
279
282
  if (adjacentTo == len && adjacentStart == 0)
280
- return [-200 /* CaseFold */, 0, adjacentEnd];
283
+ return [-200 /* CaseFold */ - word.length, 0, adjacentEnd];
281
284
  if (direct > -1)
282
- return [-700 /* NotStart */, direct, direct + this.pattern.length];
285
+ return [-700 /* NotStart */ - word.length, direct, direct + this.pattern.length];
283
286
  if (adjacentTo == len)
284
- return [-200 /* CaseFold */ + -700 /* NotStart */, adjacentStart, adjacentEnd];
287
+ return [-200 /* CaseFold */ + -700 /* NotStart */ - word.length, adjacentStart, adjacentEnd];
285
288
  if (byWordTo == len)
286
- return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */, byWord, word);
289
+ return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */ +
290
+ (wordAdjacent ? 0 : -1100 /* Gap */), byWord, word);
287
291
  return chars.length == 2 ? null : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
288
292
  }
289
293
  result(score, positions, word) {
290
- let result = [score], i = 1;
294
+ let result = [score - word.length], i = 1;
291
295
  for (let pos of positions) {
292
296
  let to = pos + (this.astral ? text.codePointSize(text.codePointAt(word, pos)) : 1);
293
297
  if (i > 1 && result[i - 1] == pos)
@@ -309,6 +313,7 @@ const completionConfig = state.Facet.define({
309
313
  maxRenderedOptions: 100,
310
314
  defaultKeymap: true,
311
315
  optionClass: () => "",
316
+ aboveCursor: false,
312
317
  icons: true,
313
318
  addToOptions: []
314
319
  }, {
@@ -482,12 +487,14 @@ class CompletionTooltip {
482
487
  let sel = this.dom.querySelector("[aria-selected]");
483
488
  if (!sel || !this.info)
484
489
  return null;
485
- let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
486
- let top = Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height) - rect.top;
487
- if (top < 0 || top > this.list.clientHeight - 10)
490
+ let listRect = this.dom.getBoundingClientRect();
491
+ let infoRect = this.info.getBoundingClientRect();
492
+ let selRect = sel.getBoundingClientRect();
493
+ if (selRect.top > Math.min(innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
488
494
  return null;
495
+ let top = Math.max(0, Math.min(selRect.top, innerHeight - infoRect.height)) - listRect.top;
489
496
  let left = this.view.textDirection == view.Direction.RTL;
490
- let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
497
+ let spaceLeft = listRect.left, spaceRight = innerWidth - listRect.right;
491
498
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
492
499
  left = false;
493
500
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
@@ -495,10 +502,12 @@ class CompletionTooltip {
495
502
  return { top, left };
496
503
  }
497
504
  positionInfo(pos) {
498
- if (this.info && pos) {
499
- this.info.style.top = pos.top + "px";
500
- this.info.classList.toggle("cm-completionInfo-left", pos.left);
501
- this.info.classList.toggle("cm-completionInfo-right", !pos.left);
505
+ if (this.info) {
506
+ this.info.style.top = (pos ? pos.top : -1e6) + "px";
507
+ if (pos) {
508
+ this.info.classList.toggle("cm-completionInfo-left", pos.left);
509
+ this.info.classList.toggle("cm-completionInfo-right", !pos.left);
510
+ }
502
511
  }
503
512
  }
504
513
  createListBox(options, id, range) {
@@ -591,7 +600,7 @@ class CompletionDialog {
591
600
  return selected == this.selected || selected >= this.options.length ? this
592
601
  : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
593
602
  }
594
- static build(active, state, id, prev) {
603
+ static build(active, state, id, prev, conf) {
595
604
  let options = sortOptions(active, state);
596
605
  if (!options.length)
597
606
  return null;
@@ -605,7 +614,8 @@ class CompletionDialog {
605
614
  }
606
615
  return new CompletionDialog(options, makeAttrs(id, selected), {
607
616
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
608
- create: completionTooltip(completionState)
617
+ create: completionTooltip(completionState),
618
+ above: conf.aboveCursor,
609
619
  }, prev ? prev.timestamp : Date.now(), selected);
610
620
  }
611
621
  map(changes) {
@@ -633,7 +643,7 @@ class CompletionState {
633
643
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
634
644
  active = this.active;
635
645
  let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
636
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open)
646
+ !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
637
647
  : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
638
648
  if (!open && active.every(a => a.state != 1 /* Pending */) && active.some(a => a.hasResult()))
639
649
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* Inactive */) : a);
@@ -773,7 +783,7 @@ function moveCompletionSelection(forward, by = "option") {
773
783
  return false;
774
784
  let step = 1, tooltip;
775
785
  if (by == "page" && (tooltip = view.dom.querySelector(".cm-tooltip-autocomplete")))
776
- step = Math.max(2, Math.floor(tooltip.offsetHeight / tooltip.firstChild.offsetHeight));
786
+ step = Math.max(2, Math.floor(tooltip.offsetHeight / tooltip.querySelector("li").offsetHeight) - 1);
777
787
  let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
778
788
  if (selected < 0)
779
789
  selected = by == "page" ? 0 : length - 1;
@@ -969,22 +979,25 @@ const baseTheme = view.EditorView.baseTheme({
969
979
  "& > ul": {
970
980
  fontFamily: "monospace",
971
981
  whiteSpace: "nowrap",
972
- overflow: "auto",
982
+ overflow: "hidden auto",
973
983
  maxWidth_fallback: "700px",
974
984
  maxWidth: "min(700px, 95vw)",
985
+ minWidth: "250px",
975
986
  maxHeight: "10em",
976
987
  listStyle: "none",
977
988
  margin: 0,
978
989
  padding: 0,
979
990
  "& > li": {
991
+ overflowX: "hidden",
992
+ textOverflow: "ellipsis",
980
993
  cursor: "pointer",
981
- padding: "1px 1em 1px 3px",
994
+ padding: "1px 3px",
982
995
  lineHeight: 1.2
983
996
  },
984
997
  }
985
998
  },
986
999
  "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
987
- background: "#39e",
1000
+ background: "#17c",
988
1001
  color: "white",
989
1002
  },
990
1003
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
package/dist/index.d.ts CHANGED
@@ -30,6 +30,12 @@ interface CompletionConfig {
30
30
  */
31
31
  defaultKeymap?: boolean;
32
32
  /**
33
+ By default, completions are shown below the cursor when there is
34
+ space. Setting this to true will make the extension put the
35
+ completions above the cursor when possible.
36
+ */
37
+ aboveCursor?: boolean;
38
+ /**
33
39
  This can be used to add additional CSS classes to completion
34
40
  options.
35
41
  */
package/dist/index.js CHANGED
@@ -242,7 +242,7 @@ class FuzzyMatcher {
242
242
  let byWordTo = 0, byWordFolded = false;
243
243
  // If we've found a partial adjacent match, these track its state
244
244
  let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1;
245
- let hasLower = /[a-z]/.test(word);
245
+ let hasLower = /[a-z]/.test(word), wordAdjacent = true;
246
246
  // Go over the option's text, scanning for the various kinds of matches
247
247
  for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* NonWord */; i < e && byWordTo < len;) {
248
248
  let next = codePointAt(word, i);
@@ -264,26 +264,30 @@ class FuzzyMatcher {
264
264
  let ch, type = next < 0xff
265
265
  ? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Lower */ : next >= 65 && next <= 90 ? 1 /* Upper */ : 0 /* NonWord */)
266
266
  : ((ch = fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Upper */ : ch != ch.toUpperCase() ? 2 /* Lower */ : 0 /* NonWord */);
267
- if ((type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) &&
268
- (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true))))
269
- byWord[byWordTo++] = i;
267
+ if (!i || type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) {
268
+ if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
269
+ byWord[byWordTo++] = i;
270
+ else if (byWord.length)
271
+ wordAdjacent = false;
272
+ }
270
273
  prevType = type;
271
274
  i += codePointSize(next);
272
275
  }
273
- if (byWordTo == len && byWord[0] == 0)
276
+ if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
274
277
  return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
275
278
  if (adjacentTo == len && adjacentStart == 0)
276
- return [-200 /* CaseFold */, 0, adjacentEnd];
279
+ return [-200 /* CaseFold */ - word.length, 0, adjacentEnd];
277
280
  if (direct > -1)
278
- return [-700 /* NotStart */, direct, direct + this.pattern.length];
281
+ return [-700 /* NotStart */ - word.length, direct, direct + this.pattern.length];
279
282
  if (adjacentTo == len)
280
- return [-200 /* CaseFold */ + -700 /* NotStart */, adjacentStart, adjacentEnd];
283
+ return [-200 /* CaseFold */ + -700 /* NotStart */ - word.length, adjacentStart, adjacentEnd];
281
284
  if (byWordTo == len)
282
- return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */, byWord, word);
285
+ return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */ +
286
+ (wordAdjacent ? 0 : -1100 /* Gap */), byWord, word);
283
287
  return chars.length == 2 ? null : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
284
288
  }
285
289
  result(score, positions, word) {
286
- let result = [score], i = 1;
290
+ let result = [score - word.length], i = 1;
287
291
  for (let pos of positions) {
288
292
  let to = pos + (this.astral ? codePointSize(codePointAt(word, pos)) : 1);
289
293
  if (i > 1 && result[i - 1] == pos)
@@ -305,6 +309,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
305
309
  maxRenderedOptions: 100,
306
310
  defaultKeymap: true,
307
311
  optionClass: () => "",
312
+ aboveCursor: false,
308
313
  icons: true,
309
314
  addToOptions: []
310
315
  }, {
@@ -478,12 +483,14 @@ class CompletionTooltip {
478
483
  let sel = this.dom.querySelector("[aria-selected]");
479
484
  if (!sel || !this.info)
480
485
  return null;
481
- let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
482
- let top = Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height) - rect.top;
483
- if (top < 0 || top > this.list.clientHeight - 10)
486
+ let listRect = this.dom.getBoundingClientRect();
487
+ let infoRect = this.info.getBoundingClientRect();
488
+ let selRect = sel.getBoundingClientRect();
489
+ if (selRect.top > Math.min(innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
484
490
  return null;
491
+ let top = Math.max(0, Math.min(selRect.top, innerHeight - infoRect.height)) - listRect.top;
485
492
  let left = this.view.textDirection == Direction.RTL;
486
- let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
493
+ let spaceLeft = listRect.left, spaceRight = innerWidth - listRect.right;
487
494
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
488
495
  left = false;
489
496
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
@@ -491,10 +498,12 @@ class CompletionTooltip {
491
498
  return { top, left };
492
499
  }
493
500
  positionInfo(pos) {
494
- if (this.info && pos) {
495
- this.info.style.top = pos.top + "px";
496
- this.info.classList.toggle("cm-completionInfo-left", pos.left);
497
- this.info.classList.toggle("cm-completionInfo-right", !pos.left);
501
+ if (this.info) {
502
+ this.info.style.top = (pos ? pos.top : -1e6) + "px";
503
+ if (pos) {
504
+ this.info.classList.toggle("cm-completionInfo-left", pos.left);
505
+ this.info.classList.toggle("cm-completionInfo-right", !pos.left);
506
+ }
498
507
  }
499
508
  }
500
509
  createListBox(options, id, range) {
@@ -587,7 +596,7 @@ class CompletionDialog {
587
596
  return selected == this.selected || selected >= this.options.length ? this
588
597
  : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
589
598
  }
590
- static build(active, state, id, prev) {
599
+ static build(active, state, id, prev, conf) {
591
600
  let options = sortOptions(active, state);
592
601
  if (!options.length)
593
602
  return null;
@@ -601,7 +610,8 @@ class CompletionDialog {
601
610
  }
602
611
  return new CompletionDialog(options, makeAttrs(id, selected), {
603
612
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
604
- create: completionTooltip(completionState)
613
+ create: completionTooltip(completionState),
614
+ above: conf.aboveCursor,
605
615
  }, prev ? prev.timestamp : Date.now(), selected);
606
616
  }
607
617
  map(changes) {
@@ -629,7 +639,7 @@ class CompletionState {
629
639
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
630
640
  active = this.active;
631
641
  let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
632
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open)
642
+ !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
633
643
  : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
634
644
  if (!open && active.every(a => a.state != 1 /* Pending */) && active.some(a => a.hasResult()))
635
645
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* Inactive */) : a);
@@ -769,7 +779,7 @@ function moveCompletionSelection(forward, by = "option") {
769
779
  return false;
770
780
  let step = 1, tooltip;
771
781
  if (by == "page" && (tooltip = view.dom.querySelector(".cm-tooltip-autocomplete")))
772
- step = Math.max(2, Math.floor(tooltip.offsetHeight / tooltip.firstChild.offsetHeight));
782
+ step = Math.max(2, Math.floor(tooltip.offsetHeight / tooltip.querySelector("li").offsetHeight) - 1);
773
783
  let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
774
784
  if (selected < 0)
775
785
  selected = by == "page" ? 0 : length - 1;
@@ -965,22 +975,25 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
965
975
  "& > ul": {
966
976
  fontFamily: "monospace",
967
977
  whiteSpace: "nowrap",
968
- overflow: "auto",
978
+ overflow: "hidden auto",
969
979
  maxWidth_fallback: "700px",
970
980
  maxWidth: "min(700px, 95vw)",
981
+ minWidth: "250px",
971
982
  maxHeight: "10em",
972
983
  listStyle: "none",
973
984
  margin: 0,
974
985
  padding: 0,
975
986
  "& > li": {
987
+ overflowX: "hidden",
988
+ textOverflow: "ellipsis",
976
989
  cursor: "pointer",
977
- padding: "1px 1em 1px 3px",
990
+ padding: "1px 3px",
978
991
  lineHeight: 1.2
979
992
  },
980
993
  }
981
994
  },
982
995
  "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
983
- background: "#39e",
996
+ background: "#17c",
984
997
  color: "white",
985
998
  },
986
999
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "0.19.7",
3
+ "version": "0.19.11",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",