@codemirror/autocomplete 6.13.0 → 6.15.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.15.0 (2024-03-13)
2
+
3
+ ### New features
4
+
5
+ The new `filterStrict` option can be used to turn off fuzzy matching of completions.
6
+
7
+ ## 6.14.0 (2024-03-10)
8
+
9
+ ### New features
10
+
11
+ Completion results can now define a `map` method that can be used to adjust position-dependent information for document changes.
12
+
1
13
  ## 6.13.0 (2024-02-29)
2
14
 
3
15
  ### New features
package/dist/index.cjs CHANGED
@@ -214,7 +214,7 @@ class FuzzyMatcher {
214
214
  ret(score, matched) {
215
215
  this.score = score;
216
216
  this.matched = matched;
217
- return true;
217
+ return this;
218
218
  }
219
219
  // Matches a given word (completion) against the pattern (input).
220
220
  // Will return a boolean indicating whether there was a match and,
@@ -227,7 +227,7 @@ class FuzzyMatcher {
227
227
  if (this.pattern.length == 0)
228
228
  return this.ret(-100 /* Penalty.NotFull */, []);
229
229
  if (word.length < this.pattern.length)
230
- return false;
230
+ return null;
231
231
  let { chars, folded, any, precise, byWord } = this;
232
232
  // For single-character queries, only match when they occur right
233
233
  // at the start
@@ -238,7 +238,7 @@ class FuzzyMatcher {
238
238
  else if (first == folded[0])
239
239
  score += -200 /* Penalty.CaseFold */;
240
240
  else
241
- return false;
241
+ return null;
242
242
  return this.ret(score, [0, firstSize]);
243
243
  }
244
244
  let direct = word.indexOf(this.pattern);
@@ -254,7 +254,7 @@ class FuzzyMatcher {
254
254
  }
255
255
  // No match, exit immediately
256
256
  if (anyTo < len)
257
- return false;
257
+ return null;
258
258
  }
259
259
  // This tracks the extent of the precise (non-folded, not
260
260
  // necessarily adjacent) match
@@ -307,7 +307,7 @@ class FuzzyMatcher {
307
307
  if (byWordTo == len)
308
308
  return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0) + -700 /* Penalty.NotStart */ +
309
309
  (wordAdjacent ? 0 : -1100 /* Penalty.Gap */), byWord, word);
310
- return chars.length == 2 ? false
310
+ return chars.length == 2 ? null
311
311
  : this.result((any[0] ? -700 /* Penalty.NotStart */ : 0) + -200 /* Penalty.CaseFold */ + -1100 /* Penalty.Gap */, any, word);
312
312
  }
313
313
  result(score, positions, word) {
@@ -324,6 +324,25 @@ class FuzzyMatcher {
324
324
  return this.ret(score - word.length, result);
325
325
  }
326
326
  }
327
+ class StrictMatcher {
328
+ constructor(pattern) {
329
+ this.pattern = pattern;
330
+ this.matched = [];
331
+ this.score = 0;
332
+ this.folded = pattern.toLowerCase();
333
+ }
334
+ match(word) {
335
+ if (word.length < this.pattern.length)
336
+ return null;
337
+ let start = word.slice(0, this.pattern.length);
338
+ let match = start == this.pattern ? 0 : start.toLowerCase() == this.folded ? -200 /* Penalty.CaseFold */ : null;
339
+ if (match == null)
340
+ return null;
341
+ this.matched = [0, start.length];
342
+ this.score = match + (word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */);
343
+ return this;
344
+ }
345
+ }
327
346
 
328
347
  const completionConfig = state.Facet.define({
329
348
  combine(configs) {
@@ -341,6 +360,7 @@ const completionConfig = state.Facet.define({
341
360
  icons: true,
342
361
  addToOptions: [],
343
362
  positionInfo: defaultPositionInfo,
363
+ filterStrict: false,
344
364
  compareCompletions: (a, b) => a.label.localeCompare(b.label),
345
365
  interactionDelay: 75,
346
366
  updateSyncTime: 100
@@ -350,7 +370,8 @@ const completionConfig = state.Facet.define({
350
370
  icons: (a, b) => a && b,
351
371
  tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
352
372
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
353
- addToOptions: (a, b) => a.concat(b)
373
+ addToOptions: (a, b) => a.concat(b),
374
+ filterStrict: (a, b) => a || b,
354
375
  });
355
376
  }
356
377
  });
@@ -710,6 +731,7 @@ function sortOptions(active, state) {
710
731
  sections.push(typeof section == "string" ? { name } : section);
711
732
  }
712
733
  };
734
+ let conf = state.facet(completionConfig);
713
735
  for (let a of active)
714
736
  if (a.hasResult()) {
715
737
  let getMatch = a.result.getMatch;
@@ -719,11 +741,12 @@ function sortOptions(active, state) {
719
741
  }
720
742
  }
721
743
  else {
722
- let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to));
744
+ let pattern = state.sliceDoc(a.from, a.to), match;
745
+ let matcher = conf.filterStrict ? new StrictMatcher(pattern) : new FuzzyMatcher(pattern);
723
746
  for (let option of a.result.options)
724
- if (matcher.match(option.label)) {
725
- let matched = !option.displayLabel ? matcher.matched : getMatch ? getMatch(option, matcher.matched) : [];
726
- addOption(new Option(option, a.source, matched, matcher.score + (option.boost || 0)));
747
+ if (match = matcher.match(option.label)) {
748
+ let matched = !option.displayLabel ? match.matched : getMatch ? getMatch(option, match.matched) : [];
749
+ addOption(new Option(option, a.source, matched, match.score + (option.boost || 0)));
727
750
  }
728
751
  }
729
752
  }
@@ -741,7 +764,7 @@ function sortOptions(active, state) {
741
764
  }
742
765
  }
743
766
  let result = [], prev = null;
744
- let compare = state.facet(completionConfig).compareCompletions;
767
+ let compare = conf.compareCompletions;
745
768
  for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
746
769
  let cur = opt.completion;
747
770
  if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
@@ -909,26 +932,33 @@ class ActiveResult extends ActiveSource {
909
932
  hasResult() { return true; }
910
933
  handleUserEvent(tr, type, conf) {
911
934
  var _a;
935
+ let result = this.result;
936
+ if (result.map && !tr.changes.empty)
937
+ result = result.map(result, tr.changes);
912
938
  let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
913
939
  let pos = cur(tr.state);
914
940
  if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
915
- pos > to ||
941
+ pos > to || !result ||
916
942
  type == "delete" && cur(tr.startState) == this.from)
917
943
  return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* State.Pending */ : 0 /* State.Inactive */);
918
- let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
919
- if (checkValid(this.result.validFor, tr.state, from, to))
920
- return new ActiveResult(this.source, explicitPos, this.result, from, to);
921
- if (this.result.update &&
922
- (updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
923
- return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
944
+ let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
945
+ if (checkValid(result.validFor, tr.state, from, to))
946
+ return new ActiveResult(this.source, explicitPos, result, from, to);
947
+ if (result.update &&
948
+ (result = result.update(result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
949
+ return new ActiveResult(this.source, explicitPos, result, result.from, (_a = result.to) !== null && _a !== void 0 ? _a : cur(tr.state));
924
950
  return new ActiveSource(this.source, 1 /* State.Pending */, explicitPos);
925
951
  }
926
952
  handleChange(tr) {
927
953
  return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource(this.source, 0 /* State.Inactive */) : this.map(tr.changes);
928
954
  }
929
955
  map(mapping) {
930
- return mapping.empty ? this :
931
- new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
956
+ if (mapping.empty)
957
+ return this;
958
+ let result = this.result.map ? this.result.map(this.result, mapping) : this.result;
959
+ if (!result)
960
+ return new ActiveSource(this.source, 0 /* State.Inactive */);
961
+ return new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
932
962
  }
933
963
  }
934
964
  function checkValid(validFor, state, from, to) {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { EditorState, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
2
+ import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
3
3
  import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
4
4
  import * as _lezer_common from '@lezer/common';
5
5
 
@@ -261,6 +261,14 @@ interface CompletionResult {
261
261
  */
262
262
  update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null;
263
263
  /**
264
+ When results contain position-dependent information in, for
265
+ example, `apply` methods, you can provide this method to update
266
+ the result for transactions that happen after the query. It is
267
+ not necessary to update `from` and `to`—those are tracked
268
+ automatically.
269
+ */
270
+ map?: (current: CompletionResult, changes: ChangeDesc) => CompletionResult | null;
271
+ /**
264
272
  Set a default set of [commit
265
273
  characters](https://codemirror.net/6/docs/ref/#autocomplete.Completion.commitCharacters) for all
266
274
  options in this result.
@@ -382,6 +390,13 @@ interface CompletionConfig {
382
390
  */
383
391
  compareCompletions?: (a: Completion, b: Completion) => number;
384
392
  /**
393
+ When set to true (the default is false), turn off fuzzy matching
394
+ of completions and only show those that start with the text the
395
+ user typed. Only takes effect for results where
396
+ [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) isn't false.
397
+ */
398
+ filterStrict?: boolean;
399
+ /**
385
400
  By default, commands relating to an open completion only take
386
401
  effect 75 milliseconds after the completion opened, so that key
387
402
  presses made before the user is aware of the tooltip don't go to
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { EditorState, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
2
+ import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
3
3
  import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
4
4
  import * as _lezer_common from '@lezer/common';
5
5
 
@@ -261,6 +261,14 @@ interface CompletionResult {
261
261
  */
262
262
  update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null;
263
263
  /**
264
+ When results contain position-dependent information in, for
265
+ example, `apply` methods, you can provide this method to update
266
+ the result for transactions that happen after the query. It is
267
+ not necessary to update `from` and `to`—those are tracked
268
+ automatically.
269
+ */
270
+ map?: (current: CompletionResult, changes: ChangeDesc) => CompletionResult | null;
271
+ /**
264
272
  Set a default set of [commit
265
273
  characters](https://codemirror.net/6/docs/ref/#autocomplete.Completion.commitCharacters) for all
266
274
  options in this result.
@@ -382,6 +390,13 @@ interface CompletionConfig {
382
390
  */
383
391
  compareCompletions?: (a: Completion, b: Completion) => number;
384
392
  /**
393
+ When set to true (the default is false), turn off fuzzy matching
394
+ of completions and only show those that start with the text the
395
+ user typed. Only takes effect for results where
396
+ [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) isn't false.
397
+ */
398
+ filterStrict?: boolean;
399
+ /**
385
400
  By default, commands relating to an open completion only take
386
401
  effect 75 milliseconds after the completion opened, so that key
387
402
  presses made before the user is aware of the tooltip don't go to
package/dist/index.js CHANGED
@@ -212,7 +212,7 @@ class FuzzyMatcher {
212
212
  ret(score, matched) {
213
213
  this.score = score;
214
214
  this.matched = matched;
215
- return true;
215
+ return this;
216
216
  }
217
217
  // Matches a given word (completion) against the pattern (input).
218
218
  // Will return a boolean indicating whether there was a match and,
@@ -225,7 +225,7 @@ class FuzzyMatcher {
225
225
  if (this.pattern.length == 0)
226
226
  return this.ret(-100 /* Penalty.NotFull */, []);
227
227
  if (word.length < this.pattern.length)
228
- return false;
228
+ return null;
229
229
  let { chars, folded, any, precise, byWord } = this;
230
230
  // For single-character queries, only match when they occur right
231
231
  // at the start
@@ -236,7 +236,7 @@ class FuzzyMatcher {
236
236
  else if (first == folded[0])
237
237
  score += -200 /* Penalty.CaseFold */;
238
238
  else
239
- return false;
239
+ return null;
240
240
  return this.ret(score, [0, firstSize]);
241
241
  }
242
242
  let direct = word.indexOf(this.pattern);
@@ -252,7 +252,7 @@ class FuzzyMatcher {
252
252
  }
253
253
  // No match, exit immediately
254
254
  if (anyTo < len)
255
- return false;
255
+ return null;
256
256
  }
257
257
  // This tracks the extent of the precise (non-folded, not
258
258
  // necessarily adjacent) match
@@ -305,7 +305,7 @@ class FuzzyMatcher {
305
305
  if (byWordTo == len)
306
306
  return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0) + -700 /* Penalty.NotStart */ +
307
307
  (wordAdjacent ? 0 : -1100 /* Penalty.Gap */), byWord, word);
308
- return chars.length == 2 ? false
308
+ return chars.length == 2 ? null
309
309
  : this.result((any[0] ? -700 /* Penalty.NotStart */ : 0) + -200 /* Penalty.CaseFold */ + -1100 /* Penalty.Gap */, any, word);
310
310
  }
311
311
  result(score, positions, word) {
@@ -322,6 +322,25 @@ class FuzzyMatcher {
322
322
  return this.ret(score - word.length, result);
323
323
  }
324
324
  }
325
+ class StrictMatcher {
326
+ constructor(pattern) {
327
+ this.pattern = pattern;
328
+ this.matched = [];
329
+ this.score = 0;
330
+ this.folded = pattern.toLowerCase();
331
+ }
332
+ match(word) {
333
+ if (word.length < this.pattern.length)
334
+ return null;
335
+ let start = word.slice(0, this.pattern.length);
336
+ let match = start == this.pattern ? 0 : start.toLowerCase() == this.folded ? -200 /* Penalty.CaseFold */ : null;
337
+ if (match == null)
338
+ return null;
339
+ this.matched = [0, start.length];
340
+ this.score = match + (word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */);
341
+ return this;
342
+ }
343
+ }
325
344
 
326
345
  const completionConfig = /*@__PURE__*/Facet.define({
327
346
  combine(configs) {
@@ -339,6 +358,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
339
358
  icons: true,
340
359
  addToOptions: [],
341
360
  positionInfo: defaultPositionInfo,
361
+ filterStrict: false,
342
362
  compareCompletions: (a, b) => a.label.localeCompare(b.label),
343
363
  interactionDelay: 75,
344
364
  updateSyncTime: 100
@@ -348,7 +368,8 @@ const completionConfig = /*@__PURE__*/Facet.define({
348
368
  icons: (a, b) => a && b,
349
369
  tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
350
370
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
351
- addToOptions: (a, b) => a.concat(b)
371
+ addToOptions: (a, b) => a.concat(b),
372
+ filterStrict: (a, b) => a || b,
352
373
  });
353
374
  }
354
375
  });
@@ -708,6 +729,7 @@ function sortOptions(active, state) {
708
729
  sections.push(typeof section == "string" ? { name } : section);
709
730
  }
710
731
  };
732
+ let conf = state.facet(completionConfig);
711
733
  for (let a of active)
712
734
  if (a.hasResult()) {
713
735
  let getMatch = a.result.getMatch;
@@ -717,11 +739,12 @@ function sortOptions(active, state) {
717
739
  }
718
740
  }
719
741
  else {
720
- let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to));
742
+ let pattern = state.sliceDoc(a.from, a.to), match;
743
+ let matcher = conf.filterStrict ? new StrictMatcher(pattern) : new FuzzyMatcher(pattern);
721
744
  for (let option of a.result.options)
722
- if (matcher.match(option.label)) {
723
- let matched = !option.displayLabel ? matcher.matched : getMatch ? getMatch(option, matcher.matched) : [];
724
- addOption(new Option(option, a.source, matched, matcher.score + (option.boost || 0)));
745
+ if (match = matcher.match(option.label)) {
746
+ let matched = !option.displayLabel ? match.matched : getMatch ? getMatch(option, match.matched) : [];
747
+ addOption(new Option(option, a.source, matched, match.score + (option.boost || 0)));
725
748
  }
726
749
  }
727
750
  }
@@ -739,7 +762,7 @@ function sortOptions(active, state) {
739
762
  }
740
763
  }
741
764
  let result = [], prev = null;
742
- let compare = state.facet(completionConfig).compareCompletions;
765
+ let compare = conf.compareCompletions;
743
766
  for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
744
767
  let cur = opt.completion;
745
768
  if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
@@ -907,26 +930,33 @@ class ActiveResult extends ActiveSource {
907
930
  hasResult() { return true; }
908
931
  handleUserEvent(tr, type, conf) {
909
932
  var _a;
933
+ let result = this.result;
934
+ if (result.map && !tr.changes.empty)
935
+ result = result.map(result, tr.changes);
910
936
  let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
911
937
  let pos = cur(tr.state);
912
938
  if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
913
- pos > to ||
939
+ pos > to || !result ||
914
940
  type == "delete" && cur(tr.startState) == this.from)
915
941
  return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* State.Pending */ : 0 /* State.Inactive */);
916
- let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
917
- if (checkValid(this.result.validFor, tr.state, from, to))
918
- return new ActiveResult(this.source, explicitPos, this.result, from, to);
919
- if (this.result.update &&
920
- (updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
921
- return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
942
+ let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos);
943
+ if (checkValid(result.validFor, tr.state, from, to))
944
+ return new ActiveResult(this.source, explicitPos, result, from, to);
945
+ if (result.update &&
946
+ (result = result.update(result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
947
+ return new ActiveResult(this.source, explicitPos, result, result.from, (_a = result.to) !== null && _a !== void 0 ? _a : cur(tr.state));
922
948
  return new ActiveSource(this.source, 1 /* State.Pending */, explicitPos);
923
949
  }
924
950
  handleChange(tr) {
925
951
  return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource(this.source, 0 /* State.Inactive */) : this.map(tr.changes);
926
952
  }
927
953
  map(mapping) {
928
- return mapping.empty ? this :
929
- new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
954
+ if (mapping.empty)
955
+ return this;
956
+ let result = this.result.map ? this.result.map(this.result, mapping) : this.result;
957
+ if (!result)
958
+ return new ActiveSource(this.source, 0 /* State.Inactive */);
959
+ return new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
930
960
  }
931
961
  }
932
962
  function checkValid(validFor, state, from, to) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.13.0",
3
+ "version": "6.15.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",