@codemirror/autocomplete 0.19.9 → 0.19.13

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,33 @@
1
+ ## 0.19.13 (2022-02-18)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the completion tooltip stayed open if it was explicitly opened and the user backspaced past its start.
6
+
7
+ Stop snippet filling when a change happens across one of the snippet fields' boundaries.
8
+
9
+ ## 0.19.12 (2022-01-11)
10
+
11
+ ### Bug fixes
12
+
13
+ Fix completion navigation with PageUp/Down when the completion tooltip isn't part of the view DOM.
14
+
15
+ ## 0.19.11 (2022-01-11)
16
+
17
+ ### Bug fixes
18
+
19
+ Fix a bug that caused page up/down to only move the selection by two options in the completion tooltip.
20
+
21
+ ## 0.19.10 (2022-01-05)
22
+
23
+ ### Bug fixes
24
+
25
+ Make sure the info tooltip is hidden when the selected option is scrolled out of view.
26
+
27
+ Fix a bug in the completion ranking that would sometimes give options that match the input by word start chars higher scores than appropriate.
28
+
29
+ Options are now sorted (ascending) by length when their match score is otherwise identical.
30
+
1
31
  ## 0.19.9 (2021-11-26)
2
32
 
3
33
  ### 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)
@@ -483,12 +487,14 @@ class CompletionTooltip {
483
487
  let sel = this.dom.querySelector("[aria-selected]");
484
488
  if (!sel || !this.info)
485
489
  return null;
486
- let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
487
- if (rect.top > innerHeight - 10 || rect.bottom < 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;
489
- let top = Math.max(0, Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height)) - rect.top;
495
+ let top = Math.max(0, Math.min(selRect.top, innerHeight - infoRect.height)) - listRect.top;
490
496
  let left = this.view.textDirection == view.Direction.RTL;
491
- let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
497
+ let spaceLeft = listRect.left, spaceRight = innerWidth - listRect.right;
492
498
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
493
499
  left = false;
494
500
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
@@ -735,7 +741,9 @@ class ActiveResult extends ActiveSource {
735
741
  handleUserEvent(tr, type, conf) {
736
742
  let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
737
743
  let pos = cur(tr.state);
738
- if ((this.explicitPos > -1 ? pos < from : pos <= from) || pos > to)
744
+ if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
745
+ pos > to ||
746
+ type == "delete" && cur(tr.startState) == this.from)
739
747
  return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
740
748
  let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
741
749
  if (this.span && (from == to || this.span.test(tr.state.sliceDoc(from, to))))
@@ -775,9 +783,10 @@ function moveCompletionSelection(forward, by = "option") {
775
783
  let cState = view.state.field(completionState, false);
776
784
  if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
777
785
  return false;
778
- let step = 1, tooltip;
779
- if (by == "page" && (tooltip = view.dom.querySelector(".cm-tooltip-autocomplete")))
780
- step = Math.max(2, Math.floor(tooltip.offsetHeight / tooltip.firstChild.offsetHeight));
786
+ let step = 1, tooltip$1;
787
+ if (by == "page" && (tooltip$1 = tooltip.getTooltip(view, cState.open.tooltip)))
788
+ step = Math.max(2, Math.floor(tooltip$1.dom.offsetHeight /
789
+ tooltip$1.dom.querySelector("li").offsetHeight) - 1);
781
790
  let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
782
791
  if (selected < 0)
783
792
  selected = by == "page" ? 0 : length - 1;
@@ -991,7 +1000,7 @@ const baseTheme = view.EditorView.baseTheme({
991
1000
  }
992
1001
  },
993
1002
  "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
994
- background: "#39e",
1003
+ background: "#17c",
995
1004
  color: "white",
996
1005
  },
997
1006
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
@@ -1086,7 +1095,9 @@ class FieldRange {
1086
1095
  this.to = to;
1087
1096
  }
1088
1097
  map(changes) {
1089
- return new FieldRange(this.field, changes.mapPos(this.from, -1), changes.mapPos(this.to, 1));
1098
+ let from = changes.mapPos(this.from, -1, state.MapMode.TrackDel);
1099
+ let to = changes.mapPos(this.to, 1, state.MapMode.TrackDel);
1100
+ return from == null || to == null ? null : new FieldRange(this.field, from, to);
1090
1101
  }
1091
1102
  }
1092
1103
  class Snippet {
@@ -1155,7 +1166,14 @@ class ActiveSnippet {
1155
1166
  this.deco = view.Decoration.set(ranges.map(r => (r.from == r.to ? fieldMarker : fieldRange).range(r.from, r.to)));
1156
1167
  }
1157
1168
  map(changes) {
1158
- return new ActiveSnippet(this.ranges.map(r => r.map(changes)), this.active);
1169
+ let ranges = [];
1170
+ for (let r of this.ranges) {
1171
+ let mapped = r.map(changes);
1172
+ if (!mapped)
1173
+ return null;
1174
+ ranges.push(mapped);
1175
+ }
1176
+ return new ActiveSnippet(ranges, this.active);
1159
1177
  }
1160
1178
  selectionInsideField(sel) {
1161
1179
  return sel.ranges.every(range => this.ranges.some(r => r.field == this.active && r.from <= range.from && r.to >= range.to));
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Annotation, Facet, combineConfig, StateEffect, StateField, Prec, EditorSelection, Text } from '@codemirror/state';
1
+ import { Annotation, Facet, combineConfig, StateEffect, StateField, Prec, EditorSelection, Text, MapMode } from '@codemirror/state';
2
2
  import { Direction, logException, EditorView, ViewPlugin, Decoration, WidgetType, keymap } from '@codemirror/view';
3
- import { showTooltip } from '@codemirror/tooltip';
3
+ import { showTooltip, getTooltip } from '@codemirror/tooltip';
4
4
  import { syntaxTree, indentUnit } from '@codemirror/language';
5
5
  import { codePointAt, codePointSize, fromCodePoint } from '@codemirror/text';
6
6
 
@@ -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)
@@ -479,12 +483,14 @@ class CompletionTooltip {
479
483
  let sel = this.dom.querySelector("[aria-selected]");
480
484
  if (!sel || !this.info)
481
485
  return null;
482
- let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
483
- if (rect.top > innerHeight - 10 || rect.bottom < 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;
485
- let top = Math.max(0, Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height)) - rect.top;
491
+ let top = Math.max(0, Math.min(selRect.top, innerHeight - infoRect.height)) - listRect.top;
486
492
  let left = this.view.textDirection == Direction.RTL;
487
- let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
493
+ let spaceLeft = listRect.left, spaceRight = innerWidth - listRect.right;
488
494
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
489
495
  left = false;
490
496
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
@@ -731,7 +737,9 @@ class ActiveResult extends ActiveSource {
731
737
  handleUserEvent(tr, type, conf) {
732
738
  let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
733
739
  let pos = cur(tr.state);
734
- if ((this.explicitPos > -1 ? pos < from : pos <= from) || pos > to)
740
+ if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
741
+ pos > to ||
742
+ type == "delete" && cur(tr.startState) == this.from)
735
743
  return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
736
744
  let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
737
745
  if (this.span && (from == to || this.span.test(tr.state.sliceDoc(from, to))))
@@ -772,8 +780,9 @@ function moveCompletionSelection(forward, by = "option") {
772
780
  if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
773
781
  return false;
774
782
  let step = 1, tooltip;
775
- if (by == "page" && (tooltip = view.dom.querySelector(".cm-tooltip-autocomplete")))
776
- step = Math.max(2, Math.floor(tooltip.offsetHeight / tooltip.firstChild.offsetHeight));
783
+ if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
784
+ step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
785
+ tooltip.dom.querySelector("li").offsetHeight) - 1);
777
786
  let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
778
787
  if (selected < 0)
779
788
  selected = by == "page" ? 0 : length - 1;
@@ -987,7 +996,7 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
987
996
  }
988
997
  },
989
998
  "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
990
- background: "#39e",
999
+ background: "#17c",
991
1000
  color: "white",
992
1001
  },
993
1002
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
@@ -1082,7 +1091,9 @@ class FieldRange {
1082
1091
  this.to = to;
1083
1092
  }
1084
1093
  map(changes) {
1085
- return new FieldRange(this.field, changes.mapPos(this.from, -1), changes.mapPos(this.to, 1));
1094
+ let from = changes.mapPos(this.from, -1, MapMode.TrackDel);
1095
+ let to = changes.mapPos(this.to, 1, MapMode.TrackDel);
1096
+ return from == null || to == null ? null : new FieldRange(this.field, from, to);
1086
1097
  }
1087
1098
  }
1088
1099
  class Snippet {
@@ -1151,7 +1162,14 @@ class ActiveSnippet {
1151
1162
  this.deco = Decoration.set(ranges.map(r => (r.from == r.to ? fieldMarker : fieldRange).range(r.from, r.to)));
1152
1163
  }
1153
1164
  map(changes) {
1154
- return new ActiveSnippet(this.ranges.map(r => r.map(changes)), this.active);
1165
+ let ranges = [];
1166
+ for (let r of this.ranges) {
1167
+ let mapped = r.map(changes);
1168
+ if (!mapped)
1169
+ return null;
1170
+ ranges.push(mapped);
1171
+ }
1172
+ return new ActiveSnippet(ranges, this.active);
1155
1173
  }
1156
1174
  selectionInsideField(sel) {
1157
1175
  return sel.ranges.every(range => this.ranges.some(r => r.field == this.active && r.from <= range.from && r.to >= range.to));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "0.19.9",
3
+ "version": "0.19.13",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -29,7 +29,7 @@
29
29
  "@codemirror/language": "^0.19.0",
30
30
  "@codemirror/state": "^0.19.4",
31
31
  "@codemirror/text": "^0.19.2",
32
- "@codemirror/tooltip": "^0.19.0",
32
+ "@codemirror/tooltip": "^0.19.12",
33
33
  "@codemirror/view": "^0.19.0",
34
34
  "@lezer/common": "^0.15.0"
35
35
  },