@codemirror/autocomplete 6.8.0 → 6.9.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.9.0 (2023-07-18)
2
+
3
+ ### New features
4
+
5
+ Completions may now provide a `displayLabel` property that overrides the way they are displayed in the completion list.
6
+
7
+ ## 6.8.1 (2023-06-23)
8
+
9
+ ### Bug fixes
10
+
11
+ `acceptCompletion` now returns false (allowing other handlers to take effect) when the completion popup is open but disabled.
12
+
1
13
  ## 6.8.0 (2023-06-12)
2
14
 
3
15
  ### New features
package/dist/index.cjs CHANGED
@@ -202,6 +202,8 @@ class FuzzyMatcher {
202
202
  this.any = [];
203
203
  this.precise = [];
204
204
  this.byWord = [];
205
+ this.score = 0;
206
+ this.matched = [];
205
207
  for (let p = 0; p < pattern.length;) {
206
208
  let char = state.codePointAt(pattern, p), size = state.codePointSize(char);
207
209
  this.chars.push(char);
@@ -211,18 +213,23 @@ class FuzzyMatcher {
211
213
  }
212
214
  this.astral = pattern.length != this.chars.length;
213
215
  }
216
+ ret(score, matched) {
217
+ this.score = score;
218
+ this.matched = matched;
219
+ return true;
220
+ }
214
221
  // Matches a given word (completion) against the pattern (input).
215
- // Will return null for no match, and otherwise an array that starts
216
- // with the match score, followed by any number of `from, to` pairs
217
- // indicating the matched parts of `word`.
222
+ // Will return a boolean indicating whether there was a match and,
223
+ // on success, set `this.score` to the score, `this.matched` to an
224
+ // array of `from, to` pairs indicating the matched parts of `word`.
218
225
  //
219
226
  // The score is a number that is more negative the worse the match
220
227
  // is. See `Penalty` above.
221
228
  match(word) {
222
229
  if (this.pattern.length == 0)
223
- return [-100 /* NotFull */];
230
+ return this.ret(-100 /* NotFull */, []);
224
231
  if (word.length < this.pattern.length)
225
- return null;
232
+ return false;
226
233
  let { chars, folded, any, precise, byWord } = this;
227
234
  // For single-character queries, only match when they occur right
228
235
  // at the start
@@ -233,12 +240,12 @@ class FuzzyMatcher {
233
240
  else if (first == folded[0])
234
241
  score += -200 /* CaseFold */;
235
242
  else
236
- return null;
237
- return [score, 0, firstSize];
243
+ return false;
244
+ return this.ret(score, [0, firstSize]);
238
245
  }
239
246
  let direct = word.indexOf(this.pattern);
240
247
  if (direct == 0)
241
- return [word.length == this.pattern.length ? 0 : -100 /* NotFull */, 0, this.pattern.length];
248
+ return this.ret(word.length == this.pattern.length ? 0 : -100 /* NotFull */, [0, this.pattern.length]);
242
249
  let len = chars.length, anyTo = 0;
243
250
  if (direct < 0) {
244
251
  for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
@@ -249,7 +256,7 @@ class FuzzyMatcher {
249
256
  }
250
257
  // No match, exit immediately
251
258
  if (anyTo < len)
252
- return null;
259
+ return false;
253
260
  }
254
261
  // This tracks the extent of the precise (non-folded, not
255
262
  // necessarily adjacent) match
@@ -294,28 +301,29 @@ class FuzzyMatcher {
294
301
  if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
295
302
  return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
296
303
  if (adjacentTo == len && adjacentStart == 0)
297
- return [-200 /* CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* NotFull */), 0, adjacentEnd];
304
+ return this.ret(-200 /* CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* NotFull */), [0, adjacentEnd]);
298
305
  if (direct > -1)
299
- return [-700 /* NotStart */ - word.length, direct, direct + this.pattern.length];
306
+ return this.ret(-700 /* NotStart */ - word.length, [direct, direct + this.pattern.length]);
300
307
  if (adjacentTo == len)
301
- return [-200 /* CaseFold */ + -700 /* NotStart */ - word.length, adjacentStart, adjacentEnd];
308
+ return this.ret(-200 /* CaseFold */ + -700 /* NotStart */ - word.length, [adjacentStart, adjacentEnd]);
302
309
  if (byWordTo == len)
303
310
  return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */ +
304
311
  (wordAdjacent ? 0 : -1100 /* Gap */), byWord, word);
305
- return chars.length == 2 ? null : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
312
+ return chars.length == 2 ? false
313
+ : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
306
314
  }
307
315
  result(score, positions, word) {
308
- let result = [score - word.length], i = 1;
316
+ let result = [], i = 0;
309
317
  for (let pos of positions) {
310
318
  let to = pos + (this.astral ? state.codePointSize(state.codePointAt(word, pos)) : 1);
311
- if (i > 1 && result[i - 1] == pos)
319
+ if (i && result[i - 1] == pos)
312
320
  result[i - 1] = to;
313
321
  else {
314
322
  result[i++] = pos;
315
323
  result[i++] = to;
316
324
  }
317
325
  }
318
- return result;
326
+ return this.ret(score - word.length, result);
319
327
  }
320
328
  }
321
329
 
@@ -398,8 +406,8 @@ function optionContent(config) {
398
406
  render(completion, _s, match) {
399
407
  let labelElt = document.createElement("span");
400
408
  labelElt.className = "cm-completionLabel";
401
- let { label } = completion, off = 0;
402
- for (let j = 1; j < match.length;) {
409
+ let label = completion.displayLabel || completion.label, off = 0;
410
+ for (let j = 0; j < match.length;) {
403
411
  let from = match[j++], to = match[j++];
404
412
  if (from > off)
405
413
  labelElt.appendChild(document.createTextNode(label.slice(off, from)));
@@ -697,21 +705,18 @@ function sortOptions(active, state) {
697
705
  };
698
706
  for (let a of active)
699
707
  if (a.hasResult()) {
708
+ let getMatch = a.result.getMatch;
700
709
  if (a.result.filter === false) {
701
- let getMatch = a.result.getMatch;
702
710
  for (let option of a.result.options) {
703
- let match = [1e9 - options.length];
704
- if (getMatch)
705
- for (let n of getMatch(option))
706
- match.push(n);
707
- addOption(new Option(option, a.source, match, match[0]));
711
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
708
712
  }
709
713
  }
710
714
  else {
711
- let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
715
+ let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to));
712
716
  for (let option of a.result.options)
713
- if (match = matcher.match(option.label)) {
714
- addOption(new Option(option, a.source, match, match[0] + (option.boost || 0)));
717
+ if (matcher.match(option.label)) {
718
+ let matched = !option.displayLabel ? matcher.matched : getMatch ? getMatch(option, matcher.matched) : [];
719
+ addOption(new Option(option, a.source, matched, matcher.score + (option.boost || 0)));
715
720
  }
716
721
  }
717
722
  }
@@ -731,9 +736,10 @@ function sortOptions(active, state) {
731
736
  let result = [], prev = null;
732
737
  let compare = state.facet(completionConfig).compareCompletions;
733
738
  for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
734
- if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
735
- (prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
736
- prev.apply != opt.completion.apply)
739
+ let cur = opt.completion;
740
+ if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
741
+ (prev.type != null && cur.type != null && prev.type != cur.type) ||
742
+ prev.apply != cur.apply || prev.boost != cur.boost)
737
743
  result.push(opt);
738
744
  else if (score(opt.completion) > score(prev))
739
745
  result[result.length - 1] = opt;
@@ -977,12 +983,10 @@ Accept the current completion.
977
983
  */
978
984
  const acceptCompletion = (view) => {
979
985
  let cState = view.state.field(completionState, false);
980
- if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
986
+ if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 || cState.open.disabled ||
981
987
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
982
988
  return false;
983
- if (!cState.open.disabled)
984
- return applyCompletion(view, cState.open.options[cState.open.selected]);
985
- return true;
989
+ return applyCompletion(view, cState.open.options[cState.open.selected]);
986
990
  };
987
991
  /**
988
992
  Explicitly start autocompletion.
package/dist/index.d.cts CHANGED
@@ -14,6 +14,13 @@ interface Completion {
14
14
  */
15
15
  label: string;
16
16
  /**
17
+ An optional override for the completion's visible label. When
18
+ using this, matched characters will only be highlighted if you
19
+ provide a [`getMatch`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.getMatch)
20
+ function.
21
+ */
22
+ displayLabel?: string;
23
+ /**
17
24
  An optional short piece of information to show (with a different
18
25
  style) after the label.
19
26
  */
@@ -230,12 +237,15 @@ interface CompletionResult {
230
237
  filter?: boolean;
231
238
  /**
232
239
  When [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) is set to
233
- `false`, this may be provided to compute the ranges on the label
234
- that match the input. Should return an array of numbers where
235
- each pair of adjacent numbers provide the start and end of a
236
- range.
237
- */
238
- getMatch?: (completion: Completion) => readonly number[];
240
+ `false` or a completion has a
241
+ [`displayLabel`](https://codemirror.net/6/docs/ref/#autocomplete.Completion.displayLabel), this
242
+ may be provided to compute the ranges on the label that match
243
+ the input. Should return an array of numbers where each pair of
244
+ adjacent numbers provide the start and end of a range. The
245
+ second argument, the match found by the library, is only passed
246
+ when `filter` isn't `false`.
247
+ */
248
+ getMatch?: (completion: Completion, matched?: readonly number[]) => readonly number[];
239
249
  /**
240
250
  Synchronously update the completion result after typing or
241
251
  deletion. If given, this should not do any expensive work, since
package/dist/index.d.ts CHANGED
@@ -14,6 +14,13 @@ interface Completion {
14
14
  */
15
15
  label: string;
16
16
  /**
17
+ An optional override for the completion's visible label. When
18
+ using this, matched characters will only be highlighted if you
19
+ provide a [`getMatch`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.getMatch)
20
+ function.
21
+ */
22
+ displayLabel?: string;
23
+ /**
17
24
  An optional short piece of information to show (with a different
18
25
  style) after the label.
19
26
  */
@@ -230,12 +237,15 @@ interface CompletionResult {
230
237
  filter?: boolean;
231
238
  /**
232
239
  When [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) is set to
233
- `false`, this may be provided to compute the ranges on the label
234
- that match the input. Should return an array of numbers where
235
- each pair of adjacent numbers provide the start and end of a
236
- range.
237
- */
238
- getMatch?: (completion: Completion) => readonly number[];
240
+ `false` or a completion has a
241
+ [`displayLabel`](https://codemirror.net/6/docs/ref/#autocomplete.Completion.displayLabel), this
242
+ may be provided to compute the ranges on the label that match
243
+ the input. Should return an array of numbers where each pair of
244
+ adjacent numbers provide the start and end of a range. The
245
+ second argument, the match found by the library, is only passed
246
+ when `filter` isn't `false`.
247
+ */
248
+ getMatch?: (completion: Completion, matched?: readonly number[]) => readonly number[];
239
249
  /**
240
250
  Synchronously update the completion result after typing or
241
251
  deletion. If given, this should not do any expensive work, since
package/dist/index.js CHANGED
@@ -198,6 +198,8 @@ class FuzzyMatcher {
198
198
  this.any = [];
199
199
  this.precise = [];
200
200
  this.byWord = [];
201
+ this.score = 0;
202
+ this.matched = [];
201
203
  for (let p = 0; p < pattern.length;) {
202
204
  let char = codePointAt(pattern, p), size = codePointSize(char);
203
205
  this.chars.push(char);
@@ -207,18 +209,23 @@ class FuzzyMatcher {
207
209
  }
208
210
  this.astral = pattern.length != this.chars.length;
209
211
  }
212
+ ret(score, matched) {
213
+ this.score = score;
214
+ this.matched = matched;
215
+ return true;
216
+ }
210
217
  // Matches a given word (completion) against the pattern (input).
211
- // Will return null for no match, and otherwise an array that starts
212
- // with the match score, followed by any number of `from, to` pairs
213
- // indicating the matched parts of `word`.
218
+ // Will return a boolean indicating whether there was a match and,
219
+ // on success, set `this.score` to the score, `this.matched` to an
220
+ // array of `from, to` pairs indicating the matched parts of `word`.
214
221
  //
215
222
  // The score is a number that is more negative the worse the match
216
223
  // is. See `Penalty` above.
217
224
  match(word) {
218
225
  if (this.pattern.length == 0)
219
- return [-100 /* NotFull */];
226
+ return this.ret(-100 /* NotFull */, []);
220
227
  if (word.length < this.pattern.length)
221
- return null;
228
+ return false;
222
229
  let { chars, folded, any, precise, byWord } = this;
223
230
  // For single-character queries, only match when they occur right
224
231
  // at the start
@@ -229,12 +236,12 @@ class FuzzyMatcher {
229
236
  else if (first == folded[0])
230
237
  score += -200 /* CaseFold */;
231
238
  else
232
- return null;
233
- return [score, 0, firstSize];
239
+ return false;
240
+ return this.ret(score, [0, firstSize]);
234
241
  }
235
242
  let direct = word.indexOf(this.pattern);
236
243
  if (direct == 0)
237
- return [word.length == this.pattern.length ? 0 : -100 /* NotFull */, 0, this.pattern.length];
244
+ return this.ret(word.length == this.pattern.length ? 0 : -100 /* NotFull */, [0, this.pattern.length]);
238
245
  let len = chars.length, anyTo = 0;
239
246
  if (direct < 0) {
240
247
  for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
@@ -245,7 +252,7 @@ class FuzzyMatcher {
245
252
  }
246
253
  // No match, exit immediately
247
254
  if (anyTo < len)
248
- return null;
255
+ return false;
249
256
  }
250
257
  // This tracks the extent of the precise (non-folded, not
251
258
  // necessarily adjacent) match
@@ -290,28 +297,29 @@ class FuzzyMatcher {
290
297
  if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
291
298
  return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
292
299
  if (adjacentTo == len && adjacentStart == 0)
293
- return [-200 /* CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* NotFull */), 0, adjacentEnd];
300
+ return this.ret(-200 /* CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* NotFull */), [0, adjacentEnd]);
294
301
  if (direct > -1)
295
- return [-700 /* NotStart */ - word.length, direct, direct + this.pattern.length];
302
+ return this.ret(-700 /* NotStart */ - word.length, [direct, direct + this.pattern.length]);
296
303
  if (adjacentTo == len)
297
- return [-200 /* CaseFold */ + -700 /* NotStart */ - word.length, adjacentStart, adjacentEnd];
304
+ return this.ret(-200 /* CaseFold */ + -700 /* NotStart */ - word.length, [adjacentStart, adjacentEnd]);
298
305
  if (byWordTo == len)
299
306
  return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */ +
300
307
  (wordAdjacent ? 0 : -1100 /* Gap */), byWord, word);
301
- return chars.length == 2 ? null : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
308
+ return chars.length == 2 ? false
309
+ : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
302
310
  }
303
311
  result(score, positions, word) {
304
- let result = [score - word.length], i = 1;
312
+ let result = [], i = 0;
305
313
  for (let pos of positions) {
306
314
  let to = pos + (this.astral ? codePointSize(codePointAt(word, pos)) : 1);
307
- if (i > 1 && result[i - 1] == pos)
315
+ if (i && result[i - 1] == pos)
308
316
  result[i - 1] = to;
309
317
  else {
310
318
  result[i++] = pos;
311
319
  result[i++] = to;
312
320
  }
313
321
  }
314
- return result;
322
+ return this.ret(score - word.length, result);
315
323
  }
316
324
  }
317
325
 
@@ -394,8 +402,8 @@ function optionContent(config) {
394
402
  render(completion, _s, match) {
395
403
  let labelElt = document.createElement("span");
396
404
  labelElt.className = "cm-completionLabel";
397
- let { label } = completion, off = 0;
398
- for (let j = 1; j < match.length;) {
405
+ let label = completion.displayLabel || completion.label, off = 0;
406
+ for (let j = 0; j < match.length;) {
399
407
  let from = match[j++], to = match[j++];
400
408
  if (from > off)
401
409
  labelElt.appendChild(document.createTextNode(label.slice(off, from)));
@@ -693,21 +701,18 @@ function sortOptions(active, state) {
693
701
  };
694
702
  for (let a of active)
695
703
  if (a.hasResult()) {
704
+ let getMatch = a.result.getMatch;
696
705
  if (a.result.filter === false) {
697
- let getMatch = a.result.getMatch;
698
706
  for (let option of a.result.options) {
699
- let match = [1e9 - options.length];
700
- if (getMatch)
701
- for (let n of getMatch(option))
702
- match.push(n);
703
- addOption(new Option(option, a.source, match, match[0]));
707
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
704
708
  }
705
709
  }
706
710
  else {
707
- let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
711
+ let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to));
708
712
  for (let option of a.result.options)
709
- if (match = matcher.match(option.label)) {
710
- addOption(new Option(option, a.source, match, match[0] + (option.boost || 0)));
713
+ if (matcher.match(option.label)) {
714
+ let matched = !option.displayLabel ? matcher.matched : getMatch ? getMatch(option, matcher.matched) : [];
715
+ addOption(new Option(option, a.source, matched, matcher.score + (option.boost || 0)));
711
716
  }
712
717
  }
713
718
  }
@@ -727,9 +732,10 @@ function sortOptions(active, state) {
727
732
  let result = [], prev = null;
728
733
  let compare = state.facet(completionConfig).compareCompletions;
729
734
  for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
730
- if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
731
- (prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
732
- prev.apply != opt.completion.apply)
735
+ let cur = opt.completion;
736
+ if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
737
+ (prev.type != null && cur.type != null && prev.type != cur.type) ||
738
+ prev.apply != cur.apply || prev.boost != cur.boost)
733
739
  result.push(opt);
734
740
  else if (score(opt.completion) > score(prev))
735
741
  result[result.length - 1] = opt;
@@ -973,12 +979,10 @@ Accept the current completion.
973
979
  */
974
980
  const acceptCompletion = (view) => {
975
981
  let cState = view.state.field(completionState, false);
976
- if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
982
+ if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 || cState.open.disabled ||
977
983
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
978
984
  return false;
979
- if (!cState.open.disabled)
980
- return applyCompletion(view, cState.open.options[cState.open.selected]);
981
- return true;
985
+ return applyCompletion(view, cState.open.options[cState.open.selected]);
982
986
  };
983
987
  /**
984
988
  Explicitly start autocompletion.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.8.0",
3
+ "version": "6.9.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",